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 (like(acm.getArea(), term)) return true; 139 } else if ("clasf".equals(attr) || "classification".equals(attr)) { 140 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 141 if (like(acm.getClassification(), term)) return true; 142 } else if ("campus".equals(attr)) { 143 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 144 if (like(acm.getCampus(), term)) return true; 145 } else if ("major".equals(attr)) { 146 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 147 if (like(acm.getMajor(), term)) return true; 148 } else if ("group".equals(attr)) { 149 for (StudentGroup aac: student().getGroups()) 150 if (like(aac.getReference(), term)) return true; 151 } else if ("accommodation".equals(attr)) { 152 for (String aac: student().getAccommodations()) 153 if (like(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 ("minor".equals(attr)) { 161 for (AreaClassificationMajor acm: student().getAreaClassificationMinors()) 162 if (like(acm.getMajor(), term)) return true; 163 } else if ("status".equals(attr)) { 164 if ("default".equalsIgnoreCase(term) || "Not Set".equalsIgnoreCase(term)) return student().getStatus() == null; 165 return like(student().getStatus(), term); 166 } else if ("concentration".equals(attr)) { 167 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 168 if (like(acm.getConcentration(), term)) return true; 169 } else if ("degree".equals(attr)) { 170 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 171 if (like(acm.getDegree(), term)) return true; 172 } else if ("program".equals(attr)) { 173 for (AreaClassificationMajor acm: student().getAreaClassificationMajors()) 174 if (like(acm.getProgram(), term)) return true; 175 } else if ("primary-area".equals(attr)) { 176 AreaClassificationMajor acm = student().getPrimaryMajor(); 177 if (acm != null && like(acm.getArea(), term)) return true; 178 } else if ("primary-clasf".equals(attr) || "primary-classification".equals(attr)) { 179 AreaClassificationMajor acm = student().getPrimaryMajor(); 180 if (acm != null && like(acm.getClassification(), term)) return true; 181 } else if ("primary-major".equals(attr)) { 182 AreaClassificationMajor acm = student().getPrimaryMajor(); 183 if (acm != null && like(acm.getMajor(), term)) return true; 184 } else if ("primary-concentration".equals(attr)) { 185 AreaClassificationMajor acm = student().getPrimaryMajor(); 186 if (acm != null && like(acm.getConcentration(), term)) return true; 187 } else if ("primary-degree".equals(attr)) { 188 AreaClassificationMajor acm = student().getPrimaryMajor(); 189 if (acm != null && like(acm.getDegree(), term)) return true; 190 } else if ("primary-program".equals(attr)) { 191 AreaClassificationMajor acm = student().getPrimaryMajor(); 192 if (acm != null && like(acm.getProgram(), term)) return true; 193 } else if ("primary-campus".equals(attr)) { 194 AreaClassificationMajor acm = student().getPrimaryMajor(); 195 if (acm != null && like(acm.getCampus(), term)) return true; 196 } else if (attr != null) { 197 for (StudentGroup aac: student().getGroups()) 198 if (eq(aac.getType(), attr.replace('_', ' ')) && like(aac.getReference(), term)) return true; 199 } 200 return false; 201 } 202 203 private boolean eq(String name, String term) { 204 if (name == null) return false; 205 return name.equalsIgnoreCase(term); 206 } 207 208 private boolean has(String name, String term) { 209 if (name == null) return false; 210 if (eq(name, term)) return true; 211 for (String t: name.split(" |,")) 212 if (t.equalsIgnoreCase(term)) return true; 213 return false; 214 } 215 216 private boolean like(String name, String term) { 217 if (name == null) return false; 218 if (term.indexOf('%') >= 0) { 219 return name.matches("(?i)" + term.replaceAll("%", ".*")); 220 } else { 221 return name.equalsIgnoreCase(term); 222 } 223 } 224} 225}