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