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}