    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}