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