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