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(), enrollment == null ? null : enrollment.getCourse()); 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 = parseCredit(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 - 0.0001f; 306 break; // < a 307 case gt: 308 min = a + 0.0001f; 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 - 0.0001f; 328 break; // < a 329 case gt: 330 min = a + 0.0001f; 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 = parseCredit(a); 340 max = parseCredit(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 float min = 0, max = Float.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 float a = parseCredit(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 - 0.0001f; 409 break; // < a 410 case gt: 411 min = a + 0.0001f; 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 = parseCredit(a); 421 max = parseCredit(b); 422 } catch (NumberFormatException e) { 423 } 424 } 425 if (min == 0 && max == Float.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 float min = 0, max = Float.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 float a = parseCredit(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 - 0.0001f; 501 break; // < a 502 case gt: 503 min = a + 0.0001f; 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 = parseCredit(a); 513 max = parseCredit(b); 514 } catch (NumberFormatException e) { 515 } 516 } 517 if (min == 0 && max == Float.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 ("min-credit".equals(attr) || "max-credit".equals(attr)) { 535 float min = 0, max = Float.MAX_VALUE; 536 Credit prefix = Credit.eq; 537 String number = term; 538 if (number.startsWith("<=")) { 539 prefix = Credit.le; 540 number = number.substring(2); 541 } else if (number.startsWith(">=")) { 542 prefix = Credit.ge; 543 number = number.substring(2); 544 } else if (number.startsWith("<")) { 545 prefix = Credit.lt; 546 number = number.substring(1); 547 } else if (number.startsWith(">")) { 548 prefix = Credit.gt; 549 number = number.substring(1); 550 } else if (number.startsWith("=")) { 551 prefix = Credit.eq; 552 number = number.substring(1); 553 } 554 try { 555 float a = parseCredit(number); 556 switch (prefix) { 557 case eq: 558 min = max = a; 559 break; // = a 560 case le: 561 max = a; 562 break; // <= a 563 case ge: 564 min = a; 565 break; // >= a 566 case lt: 567 max = a - 0.0001f; 568 break; // < a 569 case gt: 570 min = a + 0.0001f; 571 break; // > a 572 } 573 } catch (NumberFormatException e) {} 574 if (term.contains("..")) { 575 try { 576 String a = term.substring(0, term.indexOf("..")); 577 String b = term.substring(term.indexOf("..") + 2); 578 min = parseCredit(a); 579 max = parseCredit(b); 580 } catch (NumberFormatException e) {} 581 } 582 float credit = 0f; 583 if ("min-credit".equals(attr)) { 584 if (!student().hasMinCredit()) return false; 585 credit = student().getMinCredit(); 586 } else { 587 if (!student().hasMaxCredit()) return false; 588 credit = student().getMaxCredit(); 589 } 590 return min <= credit && credit <= max; 591 } 592 593 if ("rp".equals(attr)) { 594 if ("subst".equalsIgnoreCase(term)) 595 return request().isAlternative(); 596 int min = 0, max = Integer.MAX_VALUE; 597 Credit prefix = Credit.eq; 598 String number = term; 599 if (number.startsWith("<=")) { 600 prefix = Credit.le; 601 number = number.substring(2); 602 } else if (number.startsWith(">=")) { 603 prefix = Credit.ge; 604 number = number.substring(2); 605 } else if (number.startsWith("<")) { 606 prefix = Credit.lt; 607 number = number.substring(1); 608 } else if (number.startsWith(">")) { 609 prefix = Credit.gt; 610 number = number.substring(1); 611 } else if (number.startsWith("=")) { 612 prefix = Credit.eq; 613 number = number.substring(1); 614 } 615 try { 616 int a = Integer.parseInt(number); 617 switch (prefix) { 618 case eq: 619 min = max = a; 620 break; // = a 621 case le: 622 max = a; 623 break; // <= a 624 case ge: 625 min = a; 626 break; // >= a 627 case lt: 628 max = a - 1; 629 break; // < a 630 case gt: 631 min = a + 1; 632 break; // > a 633 } 634 } catch (NumberFormatException e) { 635 } 636 if (term.contains("..")) { 637 try { 638 String a = term.substring(0, term.indexOf("..")); 639 String b = term.substring(term.indexOf("..") + 2); 640 min = Integer.parseInt(a); 641 max = Integer.parseInt(b); 642 } catch (NumberFormatException e) { 643 } 644 } 645 if (min == 0 && max == Integer.MAX_VALUE) 646 return true; 647 return !request().isAlternative() && min <= request().getPriority() + 1 648 && request().getPriority() + 1 <= max; 649 } 650 651 if ("choice".equals(attr) || "ch".equals(attr)) { 652 if (cr() == null) 653 return false; 654 int min = 0, max = Integer.MAX_VALUE; 655 Credit prefix = Credit.eq; 656 String number = term; 657 if (number.startsWith("<=")) { 658 prefix = Credit.le; 659 number = number.substring(2); 660 } else if (number.startsWith(">=")) { 661 prefix = Credit.ge; 662 number = number.substring(2); 663 } else if (number.startsWith("<")) { 664 prefix = Credit.lt; 665 number = number.substring(1); 666 } else if (number.startsWith(">")) { 667 prefix = Credit.gt; 668 number = number.substring(1); 669 } else if (number.startsWith("=")) { 670 prefix = Credit.eq; 671 number = number.substring(1); 672 } 673 try { 674 int a = Integer.parseInt(number); 675 switch (prefix) { 676 case eq: 677 min = max = a; 678 break; // = a 679 case le: 680 max = a; 681 break; // <= a 682 case ge: 683 min = a; 684 break; // >= a 685 case lt: 686 max = a - 1; 687 break; // < a 688 case gt: 689 min = a + 1; 690 break; // > a 691 } 692 } catch (NumberFormatException e) { 693 } 694 if (term.contains("..")) { 695 try { 696 String a = term.substring(0, term.indexOf("..")); 697 String b = term.substring(term.indexOf("..") + 2); 698 min = Integer.parseInt(a); 699 max = Integer.parseInt(b); 700 } catch (NumberFormatException e) { 701 } 702 } 703 if (min == 0 && max == Integer.MAX_VALUE) 704 return true; 705 if (enrollment() != null) { 706 int choice = 1; 707 for (Course course : cr().getCourses()) { 708 if (course.equals(enrollment().getCourse())) { 709 return min <= choice && choice <= max; 710 } 711 choice++; 712 } 713 return false; 714 } else if (!request().isAlternative()) { 715 int choice = cr().getCourses().size(); 716 return min <= choice && choice <= max; 717 } else { 718 return false; 719 } 720 } 721 722 if ("btb".equals(attr)) { 723 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 724 return student().getBackToBackPreference() == BackToBackPreference.BTB_PREFERRED; 725 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 726 return student().getBackToBackPreference() == BackToBackPreference.BTB_DISCOURAGED; 727 else 728 return student().getBackToBackPreference() == BackToBackPreference.NO_PREFERENCE; 729 } 730 731 if ("online".equals(attr)) { 732 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 733 return student().getModalityPreference() == ModalityPreference.ONLINE_PREFERRED; 734 else if ("require".equalsIgnoreCase(term) || "required".equalsIgnoreCase(term)) 735 return student().getModalityPreference() == ModalityPreference.ONLINE_REQUIRED; 736 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 737 return student().getModalityPreference() == ModalityPreference.ONILNE_DISCOURAGED; 738 else if ("no".equalsIgnoreCase(term) || "no-preference".equalsIgnoreCase(term)) 739 return student().getModalityPreference() == ModalityPreference.NO_PREFERENCE; 740 } 741 742 if ("online".equals(attr) || "face-to-face".equals(attr) || "f2f".equals(attr) || "no-time".equals(attr) 743 || "has-time".equals(attr)) { 744 int min = 0, max = Integer.MAX_VALUE; 745 Credit prefix = Credit.eq; 746 String number = term; 747 if (number.startsWith("<=")) { 748 prefix = Credit.le; 749 number = number.substring(2); 750 } else if (number.startsWith(">=")) { 751 prefix = Credit.ge; 752 number = number.substring(2); 753 } else if (number.startsWith("<")) { 754 prefix = Credit.lt; 755 number = number.substring(1); 756 } else if (number.startsWith(">")) { 757 prefix = Credit.gt; 758 number = number.substring(1); 759 } else if (number.startsWith("=")) { 760 prefix = Credit.eq; 761 number = number.substring(1); 762 } 763 boolean perc = false; 764 if (number.endsWith("%")) { 765 perc = true; 766 number = number.substring(0, number.length() - 1).trim(); 767 } 768 try { 769 int a = Integer.parseInt(number); 770 switch (prefix) { 771 case eq: 772 min = max = a; 773 break; // = a 774 case le: 775 max = a; 776 break; // <= a 777 case ge: 778 min = a; 779 break; // >= a 780 case lt: 781 max = a - 1; 782 break; // < a 783 case gt: 784 min = a + 1; 785 break; // > a 786 } 787 } catch (NumberFormatException e) { 788 } 789 if (term.contains("..")) { 790 try { 791 String a = term.substring(0, term.indexOf("..")); 792 String b = term.substring(term.indexOf("..") + 2); 793 min = Integer.parseInt(a); 794 max = Integer.parseInt(b); 795 } catch (NumberFormatException e) { 796 } 797 } 798 if (min == 0 && max == Integer.MAX_VALUE) 799 return true; 800 int match = 0, total = 0; 801 for (Request r : student().getRequests()) { 802 if (r instanceof CourseRequest) { 803 CourseRequest cr = (CourseRequest) r; 804 Enrollment e = iAssignment.getValue(cr); 805 if (e == null) 806 continue; 807 for (Section section : enrollment().getSections()) { 808 if ("online".equals(attr) && section.isOnline()) 809 match++; 810 else if (("face-to-face".equals(attr) || "f2f".equals(attr)) && !section.isOnline()) 811 match++; 812 else if ("no-time".equals(attr) 813 && (section.getTime() == null || section.getTime().getDayCode() == 0)) 814 match++; 815 else if ("has-time".equals(attr) && section.getTime() != null 816 && section.getTime().getDayCode() != 0) 817 match++; 818 total++; 819 } 820 } 821 } 822 if (total == 0) 823 return false; 824 if (perc) { 825 double percentage = 100.0 * match / total; 826 return min <= percentage && percentage <= max; 827 } else { 828 return min <= match && match <= max; 829 } 830 } 831 832 if ("overlap".equals(attr)) { 833 int min = 0, max = Integer.MAX_VALUE; 834 Credit prefix = Credit.eq; 835 String number = term; 836 if (number.startsWith("<=")) { 837 prefix = Credit.le; 838 number = number.substring(2); 839 } else if (number.startsWith(">=")) { 840 prefix = Credit.ge; 841 number = number.substring(2); 842 } else if (number.startsWith("<")) { 843 prefix = Credit.lt; 844 number = number.substring(1); 845 } else if (number.startsWith(">")) { 846 prefix = Credit.gt; 847 number = number.substring(1); 848 } else if (number.startsWith("=")) { 849 prefix = Credit.eq; 850 number = number.substring(1); 851 } 852 try { 853 int a = Integer.parseInt(number); 854 switch (prefix) { 855 case eq: 856 min = max = a; 857 break; // = a 858 case le: 859 max = a; 860 break; // <= a 861 case ge: 862 min = a; 863 break; // >= a 864 case lt: 865 max = a - 1; 866 break; // < a 867 case gt: 868 min = a + 1; 869 break; // > a 870 } 871 } catch (NumberFormatException e) { 872 } 873 if (term.contains("..")) { 874 try { 875 String a = term.substring(0, term.indexOf("..")); 876 String b = term.substring(term.indexOf("..") + 2); 877 min = Integer.parseInt(a); 878 max = Integer.parseInt(b); 879 } catch (NumberFormatException e) { 880 } 881 } 882 int share = 0; 883 for (Request r : student().getRequests()) { 884 if (r instanceof CourseRequest) { 885 CourseRequest cr = (CourseRequest) r; 886 Enrollment e = iAssignment.getValue(cr); 887 if (e == null) 888 continue; 889 for (Section section : e.getSections()) { 890 if (section.getTime() == null) 891 continue; 892 for (Request q : student().getRequests()) { 893 if (q.equals(request())) 894 continue; 895 Enrollment otherEnrollment = iAssignment.getValue(q); 896 if (otherEnrollment != null && otherEnrollment.getCourse() != null) { 897 for (Section otherSection : otherEnrollment.getSections()) { 898 if (otherSection.getTime() != null 899 && otherSection.getTime().hasIntersection(section.getTime())) { 900 share += 5 * section.getTime().nrSharedHours(otherSection.getTime()) 901 * section.getTime().nrSharedDays(otherSection.getTime()); 902 } 903 } 904 } 905 } 906 } 907 } 908 } 909 return min <= share && share <= max; 910 } 911 912 if ("prefer".equals(attr)) { 913 if (cr() == null) 914 return false; 915 if (eq("Any Preference", term)) 916 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 917 if (eq("Met Preference", term) || eq("Unmet Preference", term)) { 918 if (enrollment() == null) { 919 if (eq("Unmet Preference", term)) 920 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 921 return false; 922 } 923 if (eq("Met Preference", term)) 924 return enrollment().isSelected(); 925 else 926 return !enrollment().isSelected(); 927 } 928 return false; 929 } 930 931 if ("require".equals(attr)) { 932 if (cr() == null) 933 return false; 934 if (eq("Any Requirement", term)) { 935 return !cr().getRequiredChoices().isEmpty(); 936 } 937 if (eq("Met Requirement", term)) { 938 return enrollment() != null && enrollment().isRequired(); 939 } 940 if (eq("Unmet Requirement", term)) { 941 return enrollment() != null && !enrollment().isRequired(); 942 } 943 return false; 944 } 945 946 if ("im".equals(attr)) { 947 if (cr() == null) 948 return false; 949 if (enrollment() == null) { 950 for (Course course : cr().getCourses()) { 951 for (Config config : course.getOffering().getConfigs()) { 952 if (term.equals(config.getInstructionalMethodReference())) 953 return true; 954 } 955 break; 956 } 957 return false; 958 } else { 959 Config config = enrollment().getConfig(); 960 if (config == null) 961 return false; 962 return term.equals(config.getInstructionalMethodReference()); 963 } 964 } 965 966 if (enrollment() != null && enrollment().getCourse() != null) { 967 for (Section section : enrollment().getSections()) { 968 if (attr == null || attr.equals("crn") || attr.equals("id") || attr.equals("externalId") 969 || attr.equals("exid") || attr.equals("name")) { 970 if (section.getName(enrollment().getCourse().getId()) != null && section 971 .getName(enrollment().getCourse().getId()).toLowerCase().startsWith(term.toLowerCase())) 972 return true; 973 } 974 if (attr == null || attr.equals("day")) { 975 if (section.getTime() == null && term.equalsIgnoreCase("none")) 976 return true; 977 if (section.getTime() != null) { 978 int day = parseDay(term); 979 if (day > 0 && (section.getTime().getDayCode() & day) == day) 980 return true; 981 } 982 } 983 if (attr == null || attr.equals("time")) { 984 if (section.getTime() == null && term.equalsIgnoreCase("none")) 985 return true; 986 if (section.getTime() != null) { 987 int start = parseStart(term); 988 if (start >= 0 && section.getTime().getStartSlot() == start) 989 return true; 990 } 991 } 992 if (attr != null && attr.equals("before")) { 993 if (section.getTime() != null) { 994 int end = parseStart(term); 995 if (end >= 0 && section.getTime().getStartSlot() + section.getTime().getLength() 996 - section.getTime().getBreakTime() / 5 <= end) 997 return true; 998 } 999 } 1000 if (attr != null && attr.equals("after")) { 1001 if (section.getTime() != null) { 1002 int start = parseStart(term); 1003 if (start >= 0 && section.getTime().getStartSlot() >= start) 1004 return true; 1005 } 1006 } 1007 if (attr == null || attr.equals("room")) { 1008 if ((section.getRooms() == null || section.getRooms().isEmpty()) 1009 && term.equalsIgnoreCase("none")) 1010 return true; 1011 if (section.getRooms() != null) { 1012 for (RoomLocation r : section.getRooms()) { 1013 if (has(r.getName(), term)) 1014 return true; 1015 } 1016 } 1017 } 1018 if (attr == null || attr.equals("instr") || attr.equals("instructor")) { 1019 if (attr != null && section.getInstructors().isEmpty() && term.equalsIgnoreCase("none")) 1020 return true; 1021 for (Instructor instuctor : section.getInstructors()) { 1022 if (has(instuctor.getName(), term) || eq(instuctor.getExternalId(), term)) 1023 return true; 1024 if (instuctor.getEmail() != null) { 1025 String email = instuctor.getEmail(); 1026 if (email.indexOf('@') >= 0) 1027 email = email.substring(0, email.indexOf('@')); 1028 if (eq(email, term)) 1029 return true; 1030 } 1031 } 1032 } 1033 } 1034 } 1035 1036 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 1037 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 1038 } 1039 if ("title".equals(attr)) { 1040 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 1041 } 1042 if ("subject".equals(attr)) { 1043 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 1044 } 1045 if ("number".equals(attr)) { 1046 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 1047 } 1048 1049 return false; 1050 } 1051 1052 protected float parseCredit(String number) throws NumberFormatException { 1053 if ("min".equalsIgnoreCase(number)) { 1054 if (student().hasMinCredit()) 1055 return student().getMinCredit(); 1056 else 1057 return 0f; 1058 } else if ("max".equalsIgnoreCase(number)) { 1059 if (student().hasMaxCredit()) 1060 return student().getMaxCredit(); 1061 else 1062 return 1000f; 1063 } else { 1064 return Float.parseFloat(number); 1065 } 1066 } 1067 1068 private boolean eq(String name, String term) { 1069 if (name == null) 1070 return false; 1071 return name.equalsIgnoreCase(term); 1072 } 1073 1074 private boolean has(String name, String term) { 1075 if (name == null) 1076 return false; 1077 if (eq(name, term)) 1078 return true; 1079 for (String t : name.split(" |,")) 1080 if (t.equalsIgnoreCase(term)) 1081 return true; 1082 return false; 1083 } 1084 1085 private boolean like(String name, String term) { 1086 if (name == null) 1087 return false; 1088 if (term.indexOf('%') >= 0) { 1089 return name.matches("(?i)" + term.replaceAll("%", ".*")); 1090 } else { 1091 return name.equalsIgnoreCase(term); 1092 } 1093 } 1094 1095 public static enum Credit { 1096 eq, lt, gt, le, ge 1097 } 1098 1099 public static String DAY_NAMES_CHARS[] = new String[] { "M", "T", "W", "R", "F", "S", "X" }; 1100 1101 private int parseDay(String token) { 1102 int days = 0; 1103 boolean found = false; 1104 do { 1105 found = false; 1106 for (int i = 0; i < Constants.DAY_NAMES_SHORT.length; i++) { 1107 if (token.toLowerCase().startsWith(Constants.DAY_NAMES_SHORT[i].toLowerCase())) { 1108 days |= Constants.DAY_CODES[i]; 1109 token = token.substring(Constants.DAY_NAMES_SHORT[i].length()); 1110 while (token.startsWith(" ")) 1111 token = token.substring(1); 1112 found = true; 1113 } 1114 } 1115 for (int i = 0; i < DAY_NAMES_CHARS.length; i++) { 1116 if (token.toLowerCase().startsWith(DAY_NAMES_CHARS[i].toLowerCase())) { 1117 days |= Constants.DAY_CODES[i]; 1118 token = token.substring(DAY_NAMES_CHARS[i].length()); 1119 while (token.startsWith(" ")) 1120 token = token.substring(1); 1121 found = true; 1122 } 1123 } 1124 } while (found); 1125 return (token.isEmpty() ? days : 0); 1126 } 1127 1128 private int parseStart(String token) { 1129 int startHour = 0, startMin = 0; 1130 String number = ""; 1131 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1132 number += token.substring(0, 1); 1133 token = token.substring(1); 1134 } 1135 if (number.isEmpty()) 1136 return -1; 1137 if (number.length() > 2) { 1138 startHour = Integer.parseInt(number) / 100; 1139 startMin = Integer.parseInt(number) % 100; 1140 } else { 1141 startHour = Integer.parseInt(number); 1142 } 1143 while (token.startsWith(" ")) 1144 token = token.substring(1); 1145 if (token.startsWith(":")) { 1146 token = token.substring(1); 1147 while (token.startsWith(" ")) 1148 token = token.substring(1); 1149 number = ""; 1150 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1151 number += token.substring(0, 1); 1152 token = token.substring(1); 1153 } 1154 if (number.isEmpty()) 1155 return -1; 1156 startMin = Integer.parseInt(number); 1157 } 1158 while (token.startsWith(" ")) 1159 token = token.substring(1); 1160 boolean hasAmOrPm = false; 1161 if (token.toLowerCase().startsWith("am")) { 1162 token = token.substring(2); 1163 hasAmOrPm = true; 1164 } 1165 if (token.toLowerCase().startsWith("a")) { 1166 token = token.substring(1); 1167 hasAmOrPm = true; 1168 } 1169 if (token.toLowerCase().startsWith("pm")) { 1170 token = token.substring(2); 1171 hasAmOrPm = true; 1172 if (startHour < 12) 1173 startHour += 12; 1174 } 1175 if (token.toLowerCase().startsWith("p")) { 1176 token = token.substring(1); 1177 hasAmOrPm = true; 1178 if (startHour < 12) 1179 startHour += 12; 1180 } 1181 if (startHour < 7 && !hasAmOrPm) 1182 startHour += 12; 1183 if (startMin % 5 != 0) 1184 startMin = 5 * ((startMin + 2) / 5); 1185 if (startHour == 7 && startMin == 0 && !hasAmOrPm) 1186 startHour += 12; 1187 return (60 * startHour + startMin) / 5; 1188 } 1189 } 1190 1191 public static class CourseMatcher implements Query.TermMatcher { 1192 private Course iCourse; 1193 1194 public CourseMatcher(Course course) { 1195 iCourse = course; 1196 } 1197 1198 public Course course() { return iCourse; } 1199 1200 @Override 1201 public boolean match(String attr, String term) { 1202 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 1203 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 1204 } 1205 if ("title".equals(attr)) { 1206 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 1207 } 1208 if ("subject".equals(attr)) { 1209 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 1210 } 1211 if ("number".equals(attr)) { 1212 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 1213 } 1214 return true; 1215 } 1216 } 1217}