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}