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