    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}