001package org.cpsolver.studentsct.reservation;
002
003import org.cpsolver.ifs.util.Query;
004import org.cpsolver.studentsct.model.AreaClassificationMajor;
005import org.cpsolver.studentsct.model.Instructor;
006import org.cpsolver.studentsct.model.Offering;
007import org.cpsolver.studentsct.model.Student;
008import org.cpsolver.studentsct.model.StudentGroup;
009
010/**
011 * Universal reservation override. A reservation override using a student filter.
012 * Student filter contains a boolean expression with the following attributes:
013 * area, classification, major, minor, group, accommodation, campus,
014 * advisor or student external id, and status. For example:
015 * major:M1 or major:M2 would match all students with majors M1 or M2.
016 * <br>
017 * <br>
018 * 
019 * @author  Tomáš Müller
020 * @version StudentSct 1.3 (Student Sectioning)<br>
021 *          Copyright (C) 2007 - 2020 Tomáš Müller<br>
022 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
023 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
024 * <br>
025 *          This library is free software; you can redistribute it and/or modify
026 *          it under the terms of the GNU Lesser General Public License as
027 *          published by the Free Software Foundation; either version 3 of the
028 *          License, or (at your option) any later version. <br>
029 * <br>
030 *          This library is distributed in the hope that it will be useful, but
031 *          WITHOUT ANY WARRANTY; without even the implied warranty of
032 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
033 *          Lesser General Public License for more details. <br>
034 * <br>
035 *          You should have received a copy of the GNU Lesser General Public
036 *          License along with this library; if not see
037 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
038 */
039public class UniversalOverride extends Reservation {
040    private double iLimit;
041    private String iFilter;
042    private boolean iOverride;
043    private transient Query iStudentQuery;
044    
045    /**
046     * Reservation priority (lower than individual and group reservations)
047     */
048    public static final int DEFAULT_PRIORITY = 350;
049    /**
050     * Reservation override does not need to be used by default
051     */
052    public static final boolean DEFAULT_MUST_BE_USED = false;
053    /**
054     * Reservation override cannot be assigned over the limit by default.
055     */
056    public static final boolean DEFAULT_CAN_ASSIGN_OVER_LIMIT = false;
057    /**
058     * Overlaps are not allowed for group reservation overrides by default. 
059     */
060    public static final boolean DEFAULT_ALLOW_OVERLAP = false;
061
062    public UniversalOverride(long id, boolean override, double limit, Offering offering, String filter) {
063        super(id, offering, DEFAULT_PRIORITY, DEFAULT_MUST_BE_USED, DEFAULT_CAN_ASSIGN_OVER_LIMIT, DEFAULT_ALLOW_OVERLAP);
064        iOverride = override;
065        iLimit = limit;
066        iFilter = filter;
067    }
068    
069    @Override
070    /**
071     * Override reservation ignore expiration date when checking if they must be used.
072     */
073    public boolean mustBeUsed() {
074        if (iOverride) return mustBeUsedIgnoreExpiration();
075        return super.mustBeUsed();
076    }
077    
078    /**
079     * Reservation limit (-1 for unlimited)
080     */
081    @Override
082    public double getReservationLimit() {
083        return iLimit;
084    }
085
086    /**
087     * Set reservation limit (-1 for unlimited)
088     * @param limit reservation limit, -1 for unlimited
089     */
090    public void setReservationLimit(double limit) {
091        iLimit = limit;
092    }
093
094    public boolean isOverride() {
095        return iOverride;
096    }
097    
098    /**
099     * Student filter
100     * @return student filter
101     */
102    public String getFilter() {
103        return iFilter;
104    }
105
106    /**
107     * Check the area, classifications and majors
108     */
109    @Override
110    public boolean isApplicable(Student student) {
111        return iFilter != null && !iFilter.isEmpty() && getStudentQuery().match(new StudentMatcher(student));
112    }
113    
114    public Query getStudentQuery() {
115        if (iStudentQuery == null)
116            iStudentQuery = new Query(iFilter);
117        return iStudentQuery;
118    }
119    
120    /**
121     * Student matcher matching the student's area, classification, major, minor, group, accommodation, campus,
122     * advisor or student external id, or status.
123     */
124    public static class StudentMatcher implements Query.TermMatcher {
125        private Student iStudent;
126        
127        public StudentMatcher(Student student) {
128                iStudent = student;
129        }
130
131        public Student student() { return iStudent; }
132        
133        @Override
134        public boolean match(String attr, String term) {
135                if (attr == null && term.isEmpty()) return true;
136                if ("limit".equals(attr)) return true;
137                if ("area".equals(attr)) {
138                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
139                        if (like(acm.getArea(), term)) return true;
140                } else if ("clasf".equals(attr) || "classification".equals(attr)) {
141                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
142                        if (like(acm.getClassification(), term)) return true;
143                } else if ("campus".equals(attr)) {
144                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
145                        if (like(acm.getCampus(), term)) return true;
146                } else if ("major".equals(attr)) {
147                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
148                        if (like(acm.getMajor(), term)) return true;
149                } else if ("group".equals(attr)) {
150                    for (StudentGroup aac: student().getGroups())
151                        if (like(aac.getReference(), term)) return true;
152                } else if ("accommodation".equals(attr)) {
153                    for (String aac: student().getAccommodations())
154                        if (like(aac, term)) return true;
155                } else if  ("student".equals(attr)) {
156                    return has(student().getName(), term) || eq(student().getExternalId(), term) || eq(student().getName(), term);
157                } else if  ("advisor".equals(attr)) {
158                    for (Instructor a: student().getAdvisors())
159                        if (eq(a.getExternalId(), term)) return true;
160                    return false;
161                } else if ("minor".equals(attr)) {
162                    for (AreaClassificationMajor acm: student().getAreaClassificationMinors())
163                        if (like(acm.getMajor(), term)) return true;
164                } else if ("status".equals(attr)) {
165                    if ("default".equalsIgnoreCase(term) || "Not Set".equalsIgnoreCase(term)) return student().getStatus() == null;
166                    return like(student().getStatus(), term);
167                } else if ("concentration".equals(attr)) {
168                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
169                        if (like(acm.getConcentration(), term)) return true;
170                } else if ("degree".equals(attr)) {
171                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
172                        if (like(acm.getDegree(), term)) return true;
173                } else if ("program".equals(attr)) {
174                    for (AreaClassificationMajor acm: student().getAreaClassificationMajors())
175                        if (like(acm.getProgram(), term)) return true;
176                } else if ("primary-area".equals(attr)) {
177                    AreaClassificationMajor acm = student().getPrimaryMajor();
178                    if (acm != null && like(acm.getArea(), term)) return true;
179                } else if ("primary-clasf".equals(attr) || "primary-classification".equals(attr)) {
180                    AreaClassificationMajor acm = student().getPrimaryMajor();
181                    if (acm != null && like(acm.getClassification(), term)) return true;
182                } else if ("primary-major".equals(attr)) {
183                    AreaClassificationMajor acm = student().getPrimaryMajor();
184                    if (acm != null && like(acm.getMajor(), term)) return true;
185                } else if ("primary-concentration".equals(attr)) {
186                    AreaClassificationMajor acm = student().getPrimaryMajor();
187                    if (acm != null && like(acm.getConcentration(), term)) return true;
188                } else if ("primary-degree".equals(attr)) {
189                    AreaClassificationMajor acm = student().getPrimaryMajor();
190                    if (acm != null && like(acm.getDegree(), term)) return true;
191                } else if ("primary-program".equals(attr)) {
192                    AreaClassificationMajor acm = student().getPrimaryMajor();
193                    if (acm != null && like(acm.getProgram(), term)) return true;
194                } else if ("primary-campus".equals(attr)) {
195                    AreaClassificationMajor acm = student().getPrimaryMajor();
196                    if (acm != null && like(acm.getCampus(), term)) return true;
197                } else if (attr != null) {
198                    for (StudentGroup aac: student().getGroups())
199                        if (eq(aac.getType(), attr.replace('_', ' ')) && like(aac.getReference(), term)) return true;
200                }
201                return false;
202        }
203        
204        private boolean eq(String name, String term) {
205            if (name == null) return false;
206            return name.equalsIgnoreCase(term);
207        }
208
209        private boolean has(String name, String term) {
210            if (name == null) return false;
211            if (eq(name, term)) return true;
212            for (String t: name.split(" |,"))
213                if (t.equalsIgnoreCase(term)) return true;
214            return false;
215        }
216        
217        private boolean like(String name, String term) {
218            if (name == null) return false;
219            if (term.indexOf('%') >= 0) {
220                return name.matches("(?i)" + term.replaceAll("%", ".*"));
221            } else {
222                return name.equalsIgnoreCase(term);
223            }
224        }
225}
226}