001package org.cpsolver.studentsct.report; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006import java.util.regex.Matcher; 007import java.util.regex.Pattern; 008 009import org.cpsolver.coursett.Constants; 010import org.cpsolver.coursett.model.RoomLocation; 011import org.cpsolver.ifs.assignment.Assignment; 012import org.cpsolver.ifs.util.CSVFile; 013import org.cpsolver.ifs.util.DataProperties; 014import org.cpsolver.ifs.util.Query; 015import org.cpsolver.studentsct.StudentSectioningModel; 016import org.cpsolver.studentsct.model.Config; 017import org.cpsolver.studentsct.model.Course; 018import org.cpsolver.studentsct.model.CourseRequest; 019import org.cpsolver.studentsct.model.Enrollment; 020import org.cpsolver.studentsct.model.Instructor; 021import org.cpsolver.studentsct.model.Request; 022import org.cpsolver.studentsct.model.Request.RequestPriority; 023import org.cpsolver.studentsct.model.Section; 024import org.cpsolver.studentsct.model.Student; 025import org.cpsolver.studentsct.model.Student.BackToBackPreference; 026import org.cpsolver.studentsct.model.Student.ModalityPreference; 027import org.cpsolver.studentsct.reservation.UniversalOverride; 028 029/** 030 * Abstract student sectioning report. Adds filtering capabilities using the 031 * filter parameter. It also checks the lastlike and real parameters (whether to 032 * include projected and real students respectively) and passes on the useAmPm parameter 033 * as {@link #isUseAmPm()}. The filter replicates most of the capabilities available 034 * in UniTime on the Batch Student Solver Dashboard page. 035 * 036 * <br> 037 * <br> 038 * 039 * @author Tomáš Müller 040 * @version StudentSct 1.3 (Student Sectioning)<br> 041 * Copyright (C) 2007 - 2014 Tomáš Müller<br> 042 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 043 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 044 * <br> 045 * This library is free software; you can redistribute it and/or modify 046 * it under the terms of the GNU Lesser General Public License as 047 * published by the Free Software Foundation; either version 3 of the 048 * License, or (at your option) any later version. <br> 049 * <br> 050 * This library is distributed in the hope that it will be useful, but 051 * WITHOUT ANY WARRANTY; without even the implied warranty of 052 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 053 * Lesser General Public License for more details. <br> 054 * <br> 055 * You should have received a copy of the GNU Lesser General Public 056 * License along with this library; if not see 057 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 058 */ 059public abstract class AbstractStudentSectioningReport implements StudentSectioningReport, StudentSectioningReport.Filter { 060 private StudentSectioningModel iModel = null; 061 private Query iFilter = null; 062 private String iUser = null; 063 private Assignment<Request, Enrollment> iAssignment; 064 private boolean iIncludeLastLike = false; 065 private boolean iIncludeReal = true; 066 private boolean iUseAmPm = true; 067 068 public AbstractStudentSectioningReport(StudentSectioningModel model) { 069 iModel = model; 070 } 071 072 /** 073 * Returns the student sectioning model 074 */ 075 public StudentSectioningModel getModel() { 076 return iModel; 077 } 078 079 @Override 080 public CSVFile create(Assignment<Request, Enrollment> assignment, DataProperties properties) { 081 String filter = properties.getProperty("filter"); 082 if (filter != null && !filter.isEmpty()) 083 iFilter = new Query(filter); 084 iUser = properties.getProperty("user"); 085 iIncludeLastLike = properties.getPropertyBoolean("lastlike", false); 086 iIncludeReal = properties.getPropertyBoolean("real", true); 087 iUseAmPm = properties.getPropertyBoolean("useAmPm", true); 088 iAssignment = assignment; 089 return createTable(assignment, properties); 090 } 091 092 @Override 093 public boolean matches(Request r, Enrollment e) { 094 if (iFilter == null) 095 return true; 096 if (r.getStudent().isDummy() && !iIncludeLastLike) return false; 097 if (!r.getStudent().isDummy() && !iIncludeReal) return false; 098 return iFilter.match(new RequestMatcher(r, e, iAssignment, iUser)); 099 } 100 101 @Override 102 public boolean matches(Request r) { 103 if (iFilter == null) 104 return true; 105 if (r.getStudent().isDummy() && !iIncludeLastLike) return false; 106 if (!r.getStudent().isDummy() && !iIncludeReal) return false; 107 return iFilter.match(new RequestMatcher(r, iAssignment.getValue(r), iAssignment, iUser)); 108 } 109 110 @Override 111 public boolean matches(Course c) { 112 if (iFilter == null) return true; 113 return iFilter.match(new CourseMatcher(c)); 114 } 115 116 @Override 117 public boolean matches(Student student) { 118 for (Request r: student.getRequests()) 119 if (matches(r)) return true; 120 return false; 121 } 122 123 /** 124 * Time display settings 125 */ 126 public boolean isUseAmPm() { return iUseAmPm; } 127 128 public abstract CSVFile createTable(Assignment<Request, Enrollment> assignment, DataProperties properties); 129 130 public static class RequestMatcher extends UniversalOverride.StudentMatcher { 131 private Request iRequest; 132 private Enrollment iEnrollment; 133 private String iUser; 134 private Assignment<Request, Enrollment> iAssignment; 135 136 public RequestMatcher(Request request, Enrollment enrollment, Assignment<Request, Enrollment> assignment, String user) { 137 super(request.getStudent()); 138 iRequest = request; 139 iEnrollment = enrollment; 140 iAssignment = assignment; 141 iUser = user; 142 } 143 144 public boolean isAssigned() { 145 return iEnrollment != null; 146 } 147 148 public Enrollment enrollment() { 149 return iEnrollment; 150 } 151 152 public Request request() { 153 return iRequest; 154 } 155 156 public CourseRequest cr() { 157 return iRequest instanceof CourseRequest ? (CourseRequest) iRequest : null; 158 } 159 160 public Course course() { 161 if (enrollment() != null) 162 return enrollment().getCourse(); 163 else if (request() instanceof CourseRequest) 164 return ((CourseRequest) request()).getCourses().get(0); 165 else 166 return null; 167 } 168 169 @Override 170 public boolean match(String attr, String term) { 171 if (super.match(attr, term)) 172 return true; 173 174 if ("assignment".equals(attr)) { 175 if (eq("Assigned", term)) { 176 return isAssigned(); 177 } else if (eq("Reserved", term)) { 178 return isAssigned() && enrollment().getReservation() != null; 179 } else if (eq("Not Assigned", term)) { 180 return !isAssigned() && !request().isAlternative(); 181 } else if (eq("Wait-Listed", term)) { 182 if (enrollment() == null) 183 return cr() != null && cr().isWaitlist(); 184 else 185 return enrollment().isWaitlisted(); 186 } else if (eq("Critical", term)) { 187 return request().getRequestPriority() == RequestPriority.Critical; 188 } else if (eq("Assigned Critical", term)) { 189 return request().getRequestPriority() == RequestPriority.Critical && isAssigned(); 190 } else if (eq("Not Assigned Critical", term)) { 191 return request().getRequestPriority() == RequestPriority.Critical && !isAssigned(); 192 } else if (eq("Vital", term)) { 193 return request().getRequestPriority() == RequestPriority.Vital; 194 } else if (eq("Assigned Vital", term)) { 195 return request().getRequestPriority() == RequestPriority.Vital && isAssigned(); 196 } else if (eq("Not Assigned Vital", term)) { 197 return request().getRequestPriority() == RequestPriority.Vital && !isAssigned(); 198 } else if (eq("Visiting F2F", term)) { 199 return request().getRequestPriority() == RequestPriority.VisitingF2F; 200 } else if (eq("Assigned Visiting F2F", term)) { 201 return request().getRequestPriority() == RequestPriority.VisitingF2F && isAssigned(); 202 } else if (eq("Not Assigned Visiting F2F", term)) { 203 return request().getRequestPriority() == RequestPriority.VisitingF2F && !isAssigned(); 204 } else if (eq("LC", term)) { 205 return request().getRequestPriority() == RequestPriority.LC; 206 } else if (eq("Assigned LC", term)) { 207 return request().getRequestPriority() == RequestPriority.LC && isAssigned(); 208 } else if (eq("Not Assigned LC", term)) { 209 return request().getRequestPriority() == RequestPriority.LC && !isAssigned(); 210 } else if (eq("Important", term)) { 211 return request().getRequestPriority() == RequestPriority.Important; 212 } else if (eq("Assigned Important", term)) { 213 return request().getRequestPriority() == RequestPriority.Important && isAssigned(); 214 } else if (eq("Not Assigned Important", term)) { 215 return request().getRequestPriority() == RequestPriority.Important && !isAssigned(); 216 } else if (eq("No-Subs", term) || eq("No-Substitutes", term)) { 217 return cr() != null && cr().isWaitlist(); 218 } else if (eq("Assigned No-Subs", term) || eq("Assigned No-Substitutes", term)) { 219 return cr() != null && cr().isWaitlist() && isAssigned(); 220 } else if (eq("Not Assigned No-Subs", term) || eq("Not Assigned No-Substitutes", term)) { 221 return cr() != null && cr().isWaitlist() && !isAssigned(); 222 } 223 } 224 225 if ("assigned".equals(attr) || "scheduled".equals(attr)) { 226 if (eq("true", term) || eq("1", term)) 227 return isAssigned(); 228 else 229 return !isAssigned(); 230 } 231 232 if ("waitlisted".equals(attr) || "waitlist".equals(attr)) { 233 if (eq("true", term) || eq("1", term)) 234 return !isAssigned() && cr() != null && cr().isWaitlist(); 235 else 236 return isAssigned() && cr() != null && cr().isWaitlist(); 237 } 238 239 if ("no-substitutes".equals(attr) || "no-subs".equals(attr)) { 240 if (eq("true", term) || eq("1", term)) 241 return !isAssigned() && cr() != null && cr().isWaitlist(); 242 else 243 return isAssigned() && cr() != null && cr().isWaitlist(); 244 } 245 246 if ("reservation".equals(attr) || "reserved".equals(attr)) { 247 if (eq("true", term) || eq("1", term)) 248 return isAssigned() && enrollment().getReservation() != null; 249 else 250 return isAssigned() && enrollment().getReservation() == null; 251 } 252 253 if ("mode".equals(attr)) { 254 if (eq("My Students", term)) { 255 if (iUser == null) 256 return false; 257 for (Instructor a : student().getAdvisors()) 258 if (eq(a.getExternalId(), iUser)) 259 return true; 260 return false; 261 } 262 return true; 263 } 264 265 if ("status".equals(attr)) { 266 if ("default".equalsIgnoreCase(term) || "Not Set".equalsIgnoreCase(term)) 267 return student().getStatus() == null; 268 return like(student().getStatus(), term); 269 } 270 271 if ("credit".equals(attr)) { 272 float min = 0, max = Float.MAX_VALUE; 273 Credit prefix = Credit.eq; 274 String number = term; 275 if (number.startsWith("<=")) { 276 prefix = Credit.le; 277 number = number.substring(2); 278 } else if (number.startsWith(">=")) { 279 prefix = Credit.ge; 280 number = number.substring(2); 281 } else if (number.startsWith("<")) { 282 prefix = Credit.lt; 283 number = number.substring(1); 284 } else if (number.startsWith(">")) { 285 prefix = Credit.gt; 286 number = number.substring(1); 287 } else if (number.startsWith("=")) { 288 prefix = Credit.eq; 289 number = number.substring(1); 290 } 291 String im = null; 292 try { 293 float a = Float.parseFloat(number); 294 switch (prefix) { 295 case eq: 296 min = max = a; 297 break; // = a 298 case le: 299 max = a; 300 break; // <= a 301 case ge: 302 min = a; 303 break; // >= a 304 case lt: 305 max = a - 1; 306 break; // < a 307 case gt: 308 min = a + 1; 309 break; // > a 310 } 311 } catch (NumberFormatException e) { 312 Matcher m = Pattern.compile("([0-9]+\\.?[0-9]*)([^0-9\\.].*)").matcher(number); 313 if (m.matches()) { 314 float a = Float.parseFloat(m.group(1)); 315 im = m.group(2).trim(); 316 switch (prefix) { 317 case eq: 318 min = max = a; 319 break; // = a 320 case le: 321 max = a; 322 break; // <= a 323 case ge: 324 min = a; 325 break; // >= a 326 case lt: 327 max = a - 1; 328 break; // < a 329 case gt: 330 min = a + 1; 331 break; // > a 332 } 333 } 334 } 335 if (term.contains("..")) { 336 try { 337 String a = term.substring(0, term.indexOf('.')); 338 String b = term.substring(term.indexOf("..") + 2); 339 min = Float.parseFloat(a); 340 max = Float.parseFloat(b); 341 } catch (NumberFormatException e) { 342 Matcher m = Pattern.compile("([0-9]+\\.?[0-9]*)\\.\\.([0-9]+\\.?[0-9]*)([^0-9].*)") 343 .matcher(term); 344 if (m.matches()) { 345 min = Float.parseFloat(m.group(1)); 346 max = Float.parseFloat(m.group(2)); 347 im = m.group(3).trim(); 348 } 349 } 350 } 351 float credit = 0; 352 for (Request r : student().getRequests()) { 353 if (r instanceof CourseRequest) { 354 CourseRequest cr = (CourseRequest) r; 355 Enrollment e = iAssignment.getValue(cr); 356 if (e == null) 357 continue; 358 Config g = e.getConfig(); 359 if (g != null) { 360 if ("!".equals(im) && g.getInstructionalMethodReference() != null) 361 continue; 362 if (im != null && !"!".equals(im) 363 && !im.equalsIgnoreCase(g.getInstructionalMethodReference())) 364 continue; 365 if (g.hasCreditValue()) 366 credit += g.getCreditValue(); 367 else if (e.getCourse().hasCreditValue()) 368 credit += e.getCourse().getCreditValue(); 369 } 370 } 371 } 372 return min <= credit && credit <= max; 373 } 374 375 if ("rc".equals(attr) || "requested-credit".equals(attr)) { 376 int min = 0, max = Integer.MAX_VALUE; 377 Credit prefix = Credit.eq; 378 String number = term; 379 if (number.startsWith("<=")) { 380 prefix = Credit.le; 381 number = number.substring(2); 382 } else if (number.startsWith(">=")) { 383 prefix = Credit.ge; 384 number = number.substring(2); 385 } else if (number.startsWith("<")) { 386 prefix = Credit.lt; 387 number = number.substring(1); 388 } else if (number.startsWith(">")) { 389 prefix = Credit.gt; 390 number = number.substring(1); 391 } else if (number.startsWith("=")) { 392 prefix = Credit.eq; 393 number = number.substring(1); 394 } 395 try { 396 int a = Integer.parseInt(number); 397 switch (prefix) { 398 case eq: 399 min = max = a; 400 break; // = a 401 case le: 402 max = a; 403 break; // <= a 404 case ge: 405 min = a; 406 break; // >= a 407 case lt: 408 max = a - 1; 409 break; // < a 410 case gt: 411 min = a + 1; 412 break; // > a 413 } 414 } catch (NumberFormatException e) { 415 } 416 if (term.contains("..")) { 417 try { 418 String a = term.substring(0, term.indexOf('.')); 419 String b = term.substring(term.indexOf("..") + 2); 420 min = Integer.parseInt(a); 421 max = Integer.parseInt(b); 422 } catch (NumberFormatException e) { 423 } 424 } 425 if (min == 0 && max == Integer.MAX_VALUE) 426 return true; 427 float studentMinTot = 0f, studentMaxTot = 0f; 428 int nrCoursesTot = 0; 429 List<Float> minsTot = new ArrayList<Float>(); 430 List<Float> maxsTot = new ArrayList<Float>(); 431 for (Request r : student().getRequests()) { 432 if (r instanceof CourseRequest) { 433 CourseRequest cr = (CourseRequest) r; 434 Float minTot = null, maxTot = null; 435 for (Course c : cr.getCourses()) { 436 if (c.hasCreditValue()) { 437 if (minTot == null || minTot > c.getCreditValue()) 438 minTot = c.getCreditValue(); 439 if (maxTot == null || maxTot < c.getCreditValue()) 440 maxTot = c.getCreditValue(); 441 } 442 } 443 if (cr.isWaitlist()) { 444 if (minTot != null) { 445 studentMinTot += minTot; 446 studentMaxTot += maxTot; 447 } 448 } else { 449 if (minTot != null) { 450 minsTot.add(minTot); 451 maxsTot.add(maxTot); 452 if (!r.isAlternative()) 453 nrCoursesTot++; 454 } 455 } 456 } 457 } 458 Collections.sort(minsTot); 459 Collections.sort(maxsTot); 460 for (int i = 0; i < nrCoursesTot; i++) { 461 studentMinTot += minsTot.get(i); 462 studentMaxTot += maxsTot.get(maxsTot.size() - i - 1); 463 } 464 return min <= studentMaxTot && studentMinTot <= max; 465 } 466 467 if ("fc".equals(attr) || "first-choice-credit".equals(attr)) { 468 int min = 0, max = Integer.MAX_VALUE; 469 Credit prefix = Credit.eq; 470 String number = term; 471 if (number.startsWith("<=")) { 472 prefix = Credit.le; 473 number = number.substring(2); 474 } else if (number.startsWith(">=")) { 475 prefix = Credit.ge; 476 number = number.substring(2); 477 } else if (number.startsWith("<")) { 478 prefix = Credit.lt; 479 number = number.substring(1); 480 } else if (number.startsWith(">")) { 481 prefix = Credit.gt; 482 number = number.substring(1); 483 } else if (number.startsWith("=")) { 484 prefix = Credit.eq; 485 number = number.substring(1); 486 } 487 try { 488 int a = Integer.parseInt(number); 489 switch (prefix) { 490 case eq: 491 min = max = a; 492 break; // = a 493 case le: 494 max = a; 495 break; // <= a 496 case ge: 497 min = a; 498 break; // >= a 499 case lt: 500 max = a - 1; 501 break; // < a 502 case gt: 503 min = a + 1; 504 break; // > a 505 } 506 } catch (NumberFormatException e) { 507 } 508 if (term.contains("..")) { 509 try { 510 String a = term.substring(0, term.indexOf('.')); 511 String b = term.substring(term.indexOf("..") + 2); 512 min = Integer.parseInt(a); 513 max = Integer.parseInt(b); 514 } catch (NumberFormatException e) { 515 } 516 } 517 if (min == 0 && max == Integer.MAX_VALUE) 518 return true; 519 float credit = 0f; 520 for (Request r : student().getRequests()) { 521 if (r instanceof CourseRequest) { 522 CourseRequest cr = (CourseRequest) r; 523 for (Course c : cr.getCourses()) { 524 if (c != null && c.hasCreditValue()) { 525 credit += c.getCreditValue(); 526 break; 527 } 528 } 529 } 530 } 531 return min <= credit && credit <= max; 532 } 533 534 if ("rp".equals(attr)) { 535 if ("subst".equalsIgnoreCase(term)) 536 return request().isAlternative(); 537 int min = 0, max = Integer.MAX_VALUE; 538 Credit prefix = Credit.eq; 539 String number = term; 540 if (number.startsWith("<=")) { 541 prefix = Credit.le; 542 number = number.substring(2); 543 } else if (number.startsWith(">=")) { 544 prefix = Credit.ge; 545 number = number.substring(2); 546 } else if (number.startsWith("<")) { 547 prefix = Credit.lt; 548 number = number.substring(1); 549 } else if (number.startsWith(">")) { 550 prefix = Credit.gt; 551 number = number.substring(1); 552 } else if (number.startsWith("=")) { 553 prefix = Credit.eq; 554 number = number.substring(1); 555 } 556 try { 557 int a = Integer.parseInt(number); 558 switch (prefix) { 559 case eq: 560 min = max = a; 561 break; // = a 562 case le: 563 max = a; 564 break; // <= a 565 case ge: 566 min = a; 567 break; // >= a 568 case lt: 569 max = a - 1; 570 break; // < a 571 case gt: 572 min = a + 1; 573 break; // > a 574 } 575 } catch (NumberFormatException e) { 576 } 577 if (term.contains("..")) { 578 try { 579 String a = term.substring(0, term.indexOf('.')); 580 String b = term.substring(term.indexOf("..") + 2); 581 min = Integer.parseInt(a); 582 max = Integer.parseInt(b); 583 } catch (NumberFormatException e) { 584 } 585 } 586 if (min == 0 && max == Integer.MAX_VALUE) 587 return true; 588 return !request().isAlternative() && min <= request().getPriority() + 1 589 && request().getPriority() + 1 <= max; 590 } 591 592 if ("choice".equals(attr) || "ch".equals(attr)) { 593 if (cr() == null) 594 return false; 595 int min = 0, max = Integer.MAX_VALUE; 596 Credit prefix = Credit.eq; 597 String number = term; 598 if (number.startsWith("<=")) { 599 prefix = Credit.le; 600 number = number.substring(2); 601 } else if (number.startsWith(">=")) { 602 prefix = Credit.ge; 603 number = number.substring(2); 604 } else if (number.startsWith("<")) { 605 prefix = Credit.lt; 606 number = number.substring(1); 607 } else if (number.startsWith(">")) { 608 prefix = Credit.gt; 609 number = number.substring(1); 610 } else if (number.startsWith("=")) { 611 prefix = Credit.eq; 612 number = number.substring(1); 613 } 614 try { 615 int a = Integer.parseInt(number); 616 switch (prefix) { 617 case eq: 618 min = max = a; 619 break; // = a 620 case le: 621 max = a; 622 break; // <= a 623 case ge: 624 min = a; 625 break; // >= a 626 case lt: 627 max = a - 1; 628 break; // < a 629 case gt: 630 min = a + 1; 631 break; // > a 632 } 633 } catch (NumberFormatException e) { 634 } 635 if (term.contains("..")) { 636 try { 637 String a = term.substring(0, term.indexOf('.')); 638 String b = term.substring(term.indexOf("..") + 2); 639 min = Integer.parseInt(a); 640 max = Integer.parseInt(b); 641 } catch (NumberFormatException e) { 642 } 643 } 644 if (min == 0 && max == Integer.MAX_VALUE) 645 return true; 646 if (enrollment() != null) { 647 int choice = 1; 648 for (Course course : cr().getCourses()) { 649 if (course.equals(enrollment().getCourse())) { 650 return min <= choice && choice <= max; 651 } 652 choice++; 653 } 654 return false; 655 } else if (!request().isAlternative()) { 656 int choice = cr().getCourses().size(); 657 return min <= choice && choice <= max; 658 } else { 659 return false; 660 } 661 } 662 663 if ("btb".equals(attr)) { 664 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 665 return student().getBackToBackPreference() == BackToBackPreference.BTB_PREFERRED; 666 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 667 return student().getBackToBackPreference() == BackToBackPreference.BTB_DISCOURAGED; 668 else 669 return student().getBackToBackPreference() == BackToBackPreference.NO_PREFERENCE; 670 } 671 672 if ("online".equals(attr)) { 673 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 674 return student().getModalityPreference() == ModalityPreference.ONLINE_PREFERRED; 675 else if ("require".equalsIgnoreCase(term) || "required".equalsIgnoreCase(term)) 676 return student().getModalityPreference() == ModalityPreference.ONLINE_REQUIRED; 677 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 678 return student().getModalityPreference() == ModalityPreference.ONILNE_DISCOURAGED; 679 else if ("no".equalsIgnoreCase(term) || "no-preference".equalsIgnoreCase(term)) 680 return student().getModalityPreference() == ModalityPreference.NO_PREFERENCE; 681 } 682 683 if ("online".equals(attr) || "face-to-face".equals(attr) || "f2f".equals(attr) || "no-time".equals(attr) 684 || "has-time".equals(attr)) { 685 int min = 0, max = Integer.MAX_VALUE; 686 Credit prefix = Credit.eq; 687 String number = term; 688 if (number.startsWith("<=")) { 689 prefix = Credit.le; 690 number = number.substring(2); 691 } else if (number.startsWith(">=")) { 692 prefix = Credit.ge; 693 number = number.substring(2); 694 } else if (number.startsWith("<")) { 695 prefix = Credit.lt; 696 number = number.substring(1); 697 } else if (number.startsWith(">")) { 698 prefix = Credit.gt; 699 number = number.substring(1); 700 } else if (number.startsWith("=")) { 701 prefix = Credit.eq; 702 number = number.substring(1); 703 } 704 boolean perc = false; 705 if (number.endsWith("%")) { 706 perc = true; 707 number = number.substring(0, number.length() - 1).trim(); 708 } 709 try { 710 int a = Integer.parseInt(number); 711 switch (prefix) { 712 case eq: 713 min = max = a; 714 break; // = a 715 case le: 716 max = a; 717 break; // <= a 718 case ge: 719 min = a; 720 break; // >= a 721 case lt: 722 max = a - 1; 723 break; // < a 724 case gt: 725 min = a + 1; 726 break; // > a 727 } 728 } catch (NumberFormatException e) { 729 } 730 if (term.contains("..")) { 731 try { 732 String a = term.substring(0, term.indexOf('.')); 733 String b = term.substring(term.indexOf("..") + 2); 734 min = Integer.parseInt(a); 735 max = Integer.parseInt(b); 736 } catch (NumberFormatException e) { 737 } 738 } 739 if (min == 0 && max == Integer.MAX_VALUE) 740 return true; 741 int match = 0, total = 0; 742 for (Request r : student().getRequests()) { 743 if (r instanceof CourseRequest) { 744 CourseRequest cr = (CourseRequest) r; 745 Enrollment e = iAssignment.getValue(cr); 746 if (e == null) 747 continue; 748 for (Section section : enrollment().getSections()) { 749 if ("online".equals(attr) && section.isOnline()) 750 match++; 751 else if (("face-to-face".equals(attr) || "f2f".equals(attr)) && !section.isOnline()) 752 match++; 753 else if ("no-time".equals(attr) 754 && (section.getTime() == null || section.getTime().getDayCode() == 0)) 755 match++; 756 else if ("has-time".equals(attr) && section.getTime() != null 757 && section.getTime().getDayCode() != 0) 758 match++; 759 total++; 760 } 761 } 762 } 763 if (total == 0) 764 return false; 765 if (perc) { 766 double percentage = 100.0 * match / total; 767 return min <= percentage && percentage <= max; 768 } else { 769 return min <= match && match <= max; 770 } 771 } 772 773 if ("overlap".equals(attr)) { 774 int min = 0, max = Integer.MAX_VALUE; 775 Credit prefix = Credit.eq; 776 String number = term; 777 if (number.startsWith("<=")) { 778 prefix = Credit.le; 779 number = number.substring(2); 780 } else if (number.startsWith(">=")) { 781 prefix = Credit.ge; 782 number = number.substring(2); 783 } else if (number.startsWith("<")) { 784 prefix = Credit.lt; 785 number = number.substring(1); 786 } else if (number.startsWith(">")) { 787 prefix = Credit.gt; 788 number = number.substring(1); 789 } else if (number.startsWith("=")) { 790 prefix = Credit.eq; 791 number = number.substring(1); 792 } 793 try { 794 int a = Integer.parseInt(number); 795 switch (prefix) { 796 case eq: 797 min = max = a; 798 break; // = a 799 case le: 800 max = a; 801 break; // <= a 802 case ge: 803 min = a; 804 break; // >= a 805 case lt: 806 max = a - 1; 807 break; // < a 808 case gt: 809 min = a + 1; 810 break; // > a 811 } 812 } catch (NumberFormatException e) { 813 } 814 if (term.contains("..")) { 815 try { 816 String a = term.substring(0, term.indexOf('.')); 817 String b = term.substring(term.indexOf("..") + 2); 818 min = Integer.parseInt(a); 819 max = Integer.parseInt(b); 820 } catch (NumberFormatException e) { 821 } 822 } 823 int share = 0; 824 for (Request r : student().getRequests()) { 825 if (r instanceof CourseRequest) { 826 CourseRequest cr = (CourseRequest) r; 827 Enrollment e = iAssignment.getValue(cr); 828 if (e == null) 829 continue; 830 for (Section section : e.getSections()) { 831 if (section.getTime() == null) 832 continue; 833 for (Request q : student().getRequests()) { 834 if (q.equals(request())) 835 continue; 836 Enrollment otherEnrollment = iAssignment.getValue(q); 837 if (otherEnrollment != null && otherEnrollment.getCourse() != null) { 838 for (Section otherSection : otherEnrollment.getSections()) { 839 if (otherSection.getTime() != null 840 && otherSection.getTime().hasIntersection(section.getTime())) { 841 share += 5 * section.getTime().nrSharedHours(otherSection.getTime()) 842 * section.getTime().nrSharedDays(otherSection.getTime()); 843 } 844 } 845 } 846 } 847 } 848 } 849 } 850 return min <= share && share <= max; 851 } 852 853 if ("prefer".equals(attr)) { 854 if (cr() == null) 855 return false; 856 if (eq("Any Preference", term)) 857 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 858 if (eq("Met Preference", term) || eq("Unmet Preference", term)) { 859 if (enrollment() == null) { 860 if (eq("Unmet Preference", term)) 861 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 862 return false; 863 } 864 if (eq("Met Preference", term)) 865 return enrollment().isSelected(); 866 else 867 return !enrollment().isSelected(); 868 } 869 return false; 870 } 871 872 if ("require".equals(attr)) { 873 if (cr() == null) 874 return false; 875 if (eq("Any Requirement", term)) { 876 return !cr().getRequiredChoices().isEmpty(); 877 } 878 if (eq("Met Requirement", term)) { 879 return enrollment() != null && enrollment().isRequired(); 880 } 881 if (eq("Unmet Requirement", term)) { 882 return enrollment() != null && !enrollment().isRequired(); 883 } 884 return false; 885 } 886 887 if ("im".equals(attr)) { 888 if (cr() == null) 889 return false; 890 if (enrollment() == null) { 891 for (Course course : cr().getCourses()) { 892 for (Config config : course.getOffering().getConfigs()) { 893 if (term.equals(config.getInstructionalMethodReference())) 894 return true; 895 } 896 break; 897 } 898 return false; 899 } else { 900 Config config = enrollment().getConfig(); 901 if (config == null) 902 return false; 903 return term.equals(config.getInstructionalMethodReference()); 904 } 905 } 906 907 if (enrollment() != null && enrollment().getCourse() != null) { 908 for (Section section : enrollment().getSections()) { 909 if (attr == null || attr.equals("crn") || attr.equals("id") || attr.equals("externalId") 910 || attr.equals("exid") || attr.equals("name")) { 911 if (section.getName(enrollment().getCourse().getId()) != null && section 912 .getName(enrollment().getCourse().getId()).toLowerCase().startsWith(term.toLowerCase())) 913 return true; 914 } 915 if (attr == null || attr.equals("day")) { 916 if (section.getTime() == null && term.equalsIgnoreCase("none")) 917 return true; 918 if (section.getTime() != null) { 919 int day = parseDay(term); 920 if (day > 0 && (section.getTime().getDayCode() & day) == day) 921 return true; 922 } 923 } 924 if (attr == null || attr.equals("time")) { 925 if (section.getTime() == null && term.equalsIgnoreCase("none")) 926 return true; 927 if (section.getTime() != null) { 928 int start = parseStart(term); 929 if (start >= 0 && section.getTime().getStartSlot() == start) 930 return true; 931 } 932 } 933 if (attr != null && attr.equals("before")) { 934 if (section.getTime() != null) { 935 int end = parseStart(term); 936 if (end >= 0 && section.getTime().getStartSlot() + section.getTime().getLength() 937 - section.getTime().getBreakTime() / 5 <= end) 938 return true; 939 } 940 } 941 if (attr != null && attr.equals("after")) { 942 if (section.getTime() != null) { 943 int start = parseStart(term); 944 if (start >= 0 && section.getTime().getStartSlot() >= start) 945 return true; 946 } 947 } 948 if (attr == null || attr.equals("room")) { 949 if ((section.getRooms() == null || section.getRooms().isEmpty()) 950 && term.equalsIgnoreCase("none")) 951 return true; 952 if (section.getRooms() != null) { 953 for (RoomLocation r : section.getRooms()) { 954 if (has(r.getName(), term)) 955 return true; 956 } 957 } 958 } 959 if (attr == null || attr.equals("instr") || attr.equals("instructor")) { 960 if (attr != null && section.getInstructors().isEmpty() && term.equalsIgnoreCase("none")) 961 return true; 962 for (Instructor instuctor : section.getInstructors()) { 963 if (has(instuctor.getName(), term) || eq(instuctor.getExternalId(), term)) 964 return true; 965 if (instuctor.getEmail() != null) { 966 String email = instuctor.getEmail(); 967 if (email.indexOf('@') >= 0) 968 email = email.substring(0, email.indexOf('@')); 969 if (eq(email, term)) 970 return true; 971 } 972 } 973 } 974 } 975 } 976 977 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 978 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 979 } 980 if ("title".equals(attr)) { 981 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 982 } 983 if ("subject".equals(attr)) { 984 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 985 } 986 if ("number".equals(attr)) { 987 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 988 } 989 990 return false; 991 } 992 993 private boolean eq(String name, String term) { 994 if (name == null) 995 return false; 996 return name.equalsIgnoreCase(term); 997 } 998 999 private boolean has(String name, String term) { 1000 if (name == null) 1001 return false; 1002 if (eq(name, term)) 1003 return true; 1004 for (String t : name.split(" |,")) 1005 if (t.equalsIgnoreCase(term)) 1006 return true; 1007 return false; 1008 } 1009 1010 private boolean like(String name, String term) { 1011 if (name == null) 1012 return false; 1013 if (term.indexOf('%') >= 0) { 1014 return name.matches("(?i)" + term.replaceAll("%", ".*")); 1015 } else { 1016 return name.equalsIgnoreCase(term); 1017 } 1018 } 1019 1020 public static enum Credit { 1021 eq, lt, gt, le, ge 1022 } 1023 1024 public static String DAY_NAMES_CHARS[] = new String[] { "M", "T", "W", "R", "F", "S", "X" }; 1025 1026 private int parseDay(String token) { 1027 int days = 0; 1028 boolean found = false; 1029 do { 1030 found = false; 1031 for (int i = 0; i < Constants.DAY_NAMES_SHORT.length; i++) { 1032 if (token.toLowerCase().startsWith(Constants.DAY_NAMES_SHORT[i].toLowerCase())) { 1033 days |= Constants.DAY_CODES[i]; 1034 token = token.substring(Constants.DAY_NAMES_SHORT[i].length()); 1035 while (token.startsWith(" ")) 1036 token = token.substring(1); 1037 found = true; 1038 } 1039 } 1040 for (int i = 0; i < DAY_NAMES_CHARS.length; i++) { 1041 if (token.toLowerCase().startsWith(DAY_NAMES_CHARS[i].toLowerCase())) { 1042 days |= Constants.DAY_CODES[i]; 1043 token = token.substring(DAY_NAMES_CHARS[i].length()); 1044 while (token.startsWith(" ")) 1045 token = token.substring(1); 1046 found = true; 1047 } 1048 } 1049 } while (found); 1050 return (token.isEmpty() ? days : 0); 1051 } 1052 1053 private int parseStart(String token) { 1054 int startHour = 0, startMin = 0; 1055 String number = ""; 1056 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1057 number += token.substring(0, 1); 1058 token = token.substring(1); 1059 } 1060 if (number.isEmpty()) 1061 return -1; 1062 if (number.length() > 2) { 1063 startHour = Integer.parseInt(number) / 100; 1064 startMin = Integer.parseInt(number) % 100; 1065 } else { 1066 startHour = Integer.parseInt(number); 1067 } 1068 while (token.startsWith(" ")) 1069 token = token.substring(1); 1070 if (token.startsWith(":")) { 1071 token = token.substring(1); 1072 while (token.startsWith(" ")) 1073 token = token.substring(1); 1074 number = ""; 1075 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1076 number += token.substring(0, 1); 1077 token = token.substring(1); 1078 } 1079 if (number.isEmpty()) 1080 return -1; 1081 startMin = Integer.parseInt(number); 1082 } 1083 while (token.startsWith(" ")) 1084 token = token.substring(1); 1085 boolean hasAmOrPm = false; 1086 if (token.toLowerCase().startsWith("am")) { 1087 token = token.substring(2); 1088 hasAmOrPm = true; 1089 } 1090 if (token.toLowerCase().startsWith("a")) { 1091 token = token.substring(1); 1092 hasAmOrPm = true; 1093 } 1094 if (token.toLowerCase().startsWith("pm")) { 1095 token = token.substring(2); 1096 hasAmOrPm = true; 1097 if (startHour < 12) 1098 startHour += 12; 1099 } 1100 if (token.toLowerCase().startsWith("p")) { 1101 token = token.substring(1); 1102 hasAmOrPm = true; 1103 if (startHour < 12) 1104 startHour += 12; 1105 } 1106 if (startHour < 7 && !hasAmOrPm) 1107 startHour += 12; 1108 if (startMin % 5 != 0) 1109 startMin = 5 * ((startMin + 2) / 5); 1110 if (startHour == 7 && startMin == 0 && !hasAmOrPm) 1111 startHour += 12; 1112 return (60 * startHour + startMin) / 5; 1113 } 1114 } 1115 1116 public static class CourseMatcher implements Query.TermMatcher { 1117 private Course iCourse; 1118 1119 public CourseMatcher(Course course) { 1120 iCourse = course; 1121 } 1122 1123 public Course course() { return iCourse; } 1124 1125 @Override 1126 public boolean match(String attr, String term) { 1127 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 1128 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 1129 } 1130 if ("title".equals(attr)) { 1131 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 1132 } 1133 if ("subject".equals(attr)) { 1134 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 1135 } 1136 if ("number".equals(attr)) { 1137 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 1138 } 1139 return true; 1140 } 1141 } 1142}