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("Visiting F2F", term)) { 198 return request().getRequestPriority() == RequestPriority.VisitingF2F; 199 } else if (eq("Assigned Visiting F2F", term)) { 200 return request().getRequestPriority() == RequestPriority.VisitingF2F && isAssigned(); 201 } else if (eq("Not Assigned Visiting F2F", term)) { 202 return request().getRequestPriority() == RequestPriority.VisitingF2F && !isAssigned(); 203 } else if (eq("LC", term)) { 204 return request().getRequestPriority() == RequestPriority.LC; 205 } else if (eq("Assigned LC", term)) { 206 return request().getRequestPriority() == RequestPriority.LC && isAssigned(); 207 } else if (eq("Not Assigned LC", term)) { 208 return request().getRequestPriority() == RequestPriority.LC && !isAssigned(); 209 } else if (eq("Important", term)) { 210 return request().getRequestPriority() == RequestPriority.Important; 211 } else if (eq("Assigned Important", term)) { 212 return request().getRequestPriority() == RequestPriority.Important && isAssigned(); 213 } else if (eq("Not Assigned Important", term)) { 214 return request().getRequestPriority() == RequestPriority.Important && !isAssigned(); 215 } else if (eq("No-Subs", term) || eq("No-Substitutes", term)) { 216 return cr() != null && cr().isWaitlist(); 217 } else if (eq("Assigned No-Subs", term) || eq("Assigned No-Substitutes", term)) { 218 return cr() != null && cr().isWaitlist() && isAssigned(); 219 } else if (eq("Not Assigned No-Subs", term) || eq("Not Assigned No-Substitutes", term)) { 220 return cr() != null && cr().isWaitlist() && !isAssigned(); 221 } 222 } 223 224 if ("assigned".equals(attr) || "scheduled".equals(attr)) { 225 if (eq("true", term) || eq("1", term)) 226 return isAssigned(); 227 else 228 return !isAssigned(); 229 } 230 231 if ("waitlisted".equals(attr) || "waitlist".equals(attr)) { 232 if (eq("true", term) || eq("1", term)) 233 return !isAssigned() && cr() != null && cr().isWaitlist(); 234 else 235 return isAssigned() && cr() != null && cr().isWaitlist(); 236 } 237 238 if ("no-substitutes".equals(attr) || "no-subs".equals(attr)) { 239 if (eq("true", term) || eq("1", term)) 240 return !isAssigned() && cr() != null && cr().isWaitlist(); 241 else 242 return isAssigned() && cr() != null && cr().isWaitlist(); 243 } 244 245 if ("reservation".equals(attr) || "reserved".equals(attr)) { 246 if (eq("true", term) || eq("1", term)) 247 return isAssigned() && enrollment().getReservation() != null; 248 else 249 return isAssigned() && enrollment().getReservation() == null; 250 } 251 252 if ("mode".equals(attr)) { 253 if (eq("My Students", term)) { 254 if (iUser == null) 255 return false; 256 for (Instructor a : student().getAdvisors()) 257 if (eq(a.getExternalId(), iUser)) 258 return true; 259 return false; 260 } 261 return true; 262 } 263 264 if ("status".equals(attr)) { 265 if ("default".equalsIgnoreCase(term) || "Not Set".equalsIgnoreCase(term)) 266 return student().getStatus() == null; 267 return like(student().getStatus(), term); 268 } 269 270 if ("credit".equals(attr)) { 271 float min = 0, max = Float.MAX_VALUE; 272 Credit prefix = Credit.eq; 273 String number = term; 274 if (number.startsWith("<=")) { 275 prefix = Credit.le; 276 number = number.substring(2); 277 } else if (number.startsWith(">=")) { 278 prefix = Credit.ge; 279 number = number.substring(2); 280 } else if (number.startsWith("<")) { 281 prefix = Credit.lt; 282 number = number.substring(1); 283 } else if (number.startsWith(">")) { 284 prefix = Credit.gt; 285 number = number.substring(1); 286 } else if (number.startsWith("=")) { 287 prefix = Credit.eq; 288 number = number.substring(1); 289 } 290 String im = null; 291 try { 292 float a = Float.parseFloat(number); 293 switch (prefix) { 294 case eq: 295 min = max = a; 296 break; // = a 297 case le: 298 max = a; 299 break; // <= a 300 case ge: 301 min = a; 302 break; // >= a 303 case lt: 304 max = a - 1; 305 break; // < a 306 case gt: 307 min = a + 1; 308 break; // > a 309 } 310 } catch (NumberFormatException e) { 311 Matcher m = Pattern.compile("([0-9]+\\.?[0-9]*)([^0-9\\.].*)").matcher(number); 312 if (m.matches()) { 313 float a = Float.parseFloat(m.group(1)); 314 im = m.group(2).trim(); 315 switch (prefix) { 316 case eq: 317 min = max = a; 318 break; // = a 319 case le: 320 max = a; 321 break; // <= a 322 case ge: 323 min = a; 324 break; // >= a 325 case lt: 326 max = a - 1; 327 break; // < a 328 case gt: 329 min = a + 1; 330 break; // > a 331 } 332 } 333 } 334 if (term.contains("..")) { 335 try { 336 String a = term.substring(0, term.indexOf('.')); 337 String b = term.substring(term.indexOf("..") + 2); 338 min = Float.parseFloat(a); 339 max = Float.parseFloat(b); 340 } catch (NumberFormatException e) { 341 Matcher m = Pattern.compile("([0-9]+\\.?[0-9]*)\\.\\.([0-9]+\\.?[0-9]*)([^0-9].*)") 342 .matcher(term); 343 if (m.matches()) { 344 min = Float.parseFloat(m.group(1)); 345 max = Float.parseFloat(m.group(2)); 346 im = m.group(3).trim(); 347 } 348 } 349 } 350 float credit = 0; 351 for (Request r : student().getRequests()) { 352 if (r instanceof CourseRequest) { 353 CourseRequest cr = (CourseRequest) r; 354 Enrollment e = iAssignment.getValue(cr); 355 if (e == null) 356 continue; 357 Config g = e.getConfig(); 358 if (g != null) { 359 if ("!".equals(im) && g.getInstructionalMethodReference() != null) 360 continue; 361 if (im != null && !"!".equals(im) 362 && !im.equalsIgnoreCase(g.getInstructionalMethodReference())) 363 continue; 364 if (g.hasCreditValue()) 365 credit += g.getCreditValue(); 366 else if (e.getCourse().hasCreditValue()) 367 credit += e.getCourse().getCreditValue(); 368 } 369 } 370 } 371 return min <= credit && credit <= max; 372 } 373 374 if ("rc".equals(attr) || "requested-credit".equals(attr)) { 375 int min = 0, max = Integer.MAX_VALUE; 376 Credit prefix = Credit.eq; 377 String number = term; 378 if (number.startsWith("<=")) { 379 prefix = Credit.le; 380 number = number.substring(2); 381 } else if (number.startsWith(">=")) { 382 prefix = Credit.ge; 383 number = number.substring(2); 384 } else if (number.startsWith("<")) { 385 prefix = Credit.lt; 386 number = number.substring(1); 387 } else if (number.startsWith(">")) { 388 prefix = Credit.gt; 389 number = number.substring(1); 390 } else if (number.startsWith("=")) { 391 prefix = Credit.eq; 392 number = number.substring(1); 393 } 394 try { 395 int a = Integer.parseInt(number); 396 switch (prefix) { 397 case eq: 398 min = max = a; 399 break; // = a 400 case le: 401 max = a; 402 break; // <= a 403 case ge: 404 min = a; 405 break; // >= a 406 case lt: 407 max = a - 1; 408 break; // < a 409 case gt: 410 min = a + 1; 411 break; // > a 412 } 413 } catch (NumberFormatException e) { 414 } 415 if (term.contains("..")) { 416 try { 417 String a = term.substring(0, term.indexOf('.')); 418 String b = term.substring(term.indexOf("..") + 2); 419 min = Integer.parseInt(a); 420 max = Integer.parseInt(b); 421 } catch (NumberFormatException e) { 422 } 423 } 424 if (min == 0 && max == Integer.MAX_VALUE) 425 return true; 426 float studentMinTot = 0f, studentMaxTot = 0f; 427 int nrCoursesTot = 0; 428 List<Float> minsTot = new ArrayList<Float>(); 429 List<Float> maxsTot = new ArrayList<Float>(); 430 for (Request r : student().getRequests()) { 431 if (r instanceof CourseRequest) { 432 CourseRequest cr = (CourseRequest) r; 433 Float minTot = null, maxTot = null; 434 for (Course c : cr.getCourses()) { 435 if (c.hasCreditValue()) { 436 if (minTot == null || minTot > c.getCreditValue()) 437 minTot = c.getCreditValue(); 438 if (maxTot == null || maxTot < c.getCreditValue()) 439 maxTot = c.getCreditValue(); 440 } 441 } 442 if (cr.isWaitlist()) { 443 if (minTot != null) { 444 studentMinTot += minTot; 445 studentMaxTot += maxTot; 446 } 447 } else { 448 if (minTot != null) { 449 minsTot.add(minTot); 450 maxsTot.add(maxTot); 451 if (!r.isAlternative()) 452 nrCoursesTot++; 453 } 454 } 455 } 456 } 457 Collections.sort(minsTot); 458 Collections.sort(maxsTot); 459 for (int i = 0; i < nrCoursesTot; i++) { 460 studentMinTot += minsTot.get(i); 461 studentMaxTot += maxsTot.get(maxsTot.size() - i - 1); 462 } 463 return min <= studentMaxTot && studentMinTot <= max; 464 } 465 466 if ("fc".equals(attr) || "first-choice-credit".equals(attr)) { 467 int min = 0, max = Integer.MAX_VALUE; 468 Credit prefix = Credit.eq; 469 String number = term; 470 if (number.startsWith("<=")) { 471 prefix = Credit.le; 472 number = number.substring(2); 473 } else if (number.startsWith(">=")) { 474 prefix = Credit.ge; 475 number = number.substring(2); 476 } else if (number.startsWith("<")) { 477 prefix = Credit.lt; 478 number = number.substring(1); 479 } else if (number.startsWith(">")) { 480 prefix = Credit.gt; 481 number = number.substring(1); 482 } else if (number.startsWith("=")) { 483 prefix = Credit.eq; 484 number = number.substring(1); 485 } 486 try { 487 int a = Integer.parseInt(number); 488 switch (prefix) { 489 case eq: 490 min = max = a; 491 break; // = a 492 case le: 493 max = a; 494 break; // <= a 495 case ge: 496 min = a; 497 break; // >= a 498 case lt: 499 max = a - 1; 500 break; // < a 501 case gt: 502 min = a + 1; 503 break; // > a 504 } 505 } catch (NumberFormatException e) { 506 } 507 if (term.contains("..")) { 508 try { 509 String a = term.substring(0, term.indexOf('.')); 510 String b = term.substring(term.indexOf("..") + 2); 511 min = Integer.parseInt(a); 512 max = Integer.parseInt(b); 513 } catch (NumberFormatException e) { 514 } 515 } 516 if (min == 0 && max == Integer.MAX_VALUE) 517 return true; 518 float credit = 0f; 519 for (Request r : student().getRequests()) { 520 if (r instanceof CourseRequest) { 521 CourseRequest cr = (CourseRequest) r; 522 for (Course c : cr.getCourses()) { 523 if (c != null && c.hasCreditValue()) { 524 credit += c.getCreditValue(); 525 break; 526 } 527 } 528 } 529 } 530 return min <= credit && credit <= max; 531 } 532 533 if ("rp".equals(attr)) { 534 if ("subst".equalsIgnoreCase(term)) 535 return request().isAlternative(); 536 int min = 0, max = Integer.MAX_VALUE; 537 Credit prefix = Credit.eq; 538 String number = term; 539 if (number.startsWith("<=")) { 540 prefix = Credit.le; 541 number = number.substring(2); 542 } else if (number.startsWith(">=")) { 543 prefix = Credit.ge; 544 number = number.substring(2); 545 } else if (number.startsWith("<")) { 546 prefix = Credit.lt; 547 number = number.substring(1); 548 } else if (number.startsWith(">")) { 549 prefix = Credit.gt; 550 number = number.substring(1); 551 } else if (number.startsWith("=")) { 552 prefix = Credit.eq; 553 number = number.substring(1); 554 } 555 try { 556 int a = Integer.parseInt(number); 557 switch (prefix) { 558 case eq: 559 min = max = a; 560 break; // = a 561 case le: 562 max = a; 563 break; // <= a 564 case ge: 565 min = a; 566 break; // >= a 567 case lt: 568 max = a - 1; 569 break; // < a 570 case gt: 571 min = a + 1; 572 break; // > a 573 } 574 } catch (NumberFormatException e) { 575 } 576 if (term.contains("..")) { 577 try { 578 String a = term.substring(0, term.indexOf('.')); 579 String b = term.substring(term.indexOf("..") + 2); 580 min = Integer.parseInt(a); 581 max = Integer.parseInt(b); 582 } catch (NumberFormatException e) { 583 } 584 } 585 if (min == 0 && max == Integer.MAX_VALUE) 586 return true; 587 return !request().isAlternative() && min <= request().getPriority() + 1 588 && request().getPriority() + 1 <= max; 589 } 590 591 if ("choice".equals(attr) || "ch".equals(attr)) { 592 if (cr() == null) 593 return false; 594 int min = 0, max = Integer.MAX_VALUE; 595 Credit prefix = Credit.eq; 596 String number = term; 597 if (number.startsWith("<=")) { 598 prefix = Credit.le; 599 number = number.substring(2); 600 } else if (number.startsWith(">=")) { 601 prefix = Credit.ge; 602 number = number.substring(2); 603 } else if (number.startsWith("<")) { 604 prefix = Credit.lt; 605 number = number.substring(1); 606 } else if (number.startsWith(">")) { 607 prefix = Credit.gt; 608 number = number.substring(1); 609 } else if (number.startsWith("=")) { 610 prefix = Credit.eq; 611 number = number.substring(1); 612 } 613 try { 614 int a = Integer.parseInt(number); 615 switch (prefix) { 616 case eq: 617 min = max = a; 618 break; // = a 619 case le: 620 max = a; 621 break; // <= a 622 case ge: 623 min = a; 624 break; // >= a 625 case lt: 626 max = a - 1; 627 break; // < a 628 case gt: 629 min = a + 1; 630 break; // > a 631 } 632 } catch (NumberFormatException e) { 633 } 634 if (term.contains("..")) { 635 try { 636 String a = term.substring(0, term.indexOf('.')); 637 String b = term.substring(term.indexOf("..") + 2); 638 min = Integer.parseInt(a); 639 max = Integer.parseInt(b); 640 } catch (NumberFormatException e) { 641 } 642 } 643 if (min == 0 && max == Integer.MAX_VALUE) 644 return true; 645 if (enrollment() != null) { 646 int choice = 1; 647 for (Course course : cr().getCourses()) { 648 if (course.equals(enrollment().getCourse())) { 649 return min <= choice && choice <= max; 650 } 651 choice++; 652 } 653 return false; 654 } else if (!request().isAlternative()) { 655 int choice = cr().getCourses().size(); 656 return min <= choice && choice <= max; 657 } else { 658 return false; 659 } 660 } 661 662 if ("btb".equals(attr)) { 663 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 664 return student().getBackToBackPreference() == BackToBackPreference.BTB_PREFERRED; 665 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 666 return student().getBackToBackPreference() == BackToBackPreference.BTB_DISCOURAGED; 667 else 668 return student().getBackToBackPreference() == BackToBackPreference.NO_PREFERENCE; 669 } 670 671 if ("online".equals(attr)) { 672 if ("prefer".equalsIgnoreCase(term) || "preferred".equalsIgnoreCase(term)) 673 return student().getModalityPreference() == ModalityPreference.ONLINE_PREFERRED; 674 else if ("require".equalsIgnoreCase(term) || "required".equalsIgnoreCase(term)) 675 return student().getModalityPreference() == ModalityPreference.ONLINE_REQUIRED; 676 else if ("disc".equalsIgnoreCase(term) || "discouraged".equalsIgnoreCase(term)) 677 return student().getModalityPreference() == ModalityPreference.ONILNE_DISCOURAGED; 678 else if ("no".equalsIgnoreCase(term) || "no-preference".equalsIgnoreCase(term)) 679 return student().getModalityPreference() == ModalityPreference.NO_PREFERENCE; 680 } 681 682 if ("online".equals(attr) || "face-to-face".equals(attr) || "f2f".equals(attr) || "no-time".equals(attr) 683 || "has-time".equals(attr)) { 684 int min = 0, max = Integer.MAX_VALUE; 685 Credit prefix = Credit.eq; 686 String number = term; 687 if (number.startsWith("<=")) { 688 prefix = Credit.le; 689 number = number.substring(2); 690 } else if (number.startsWith(">=")) { 691 prefix = Credit.ge; 692 number = number.substring(2); 693 } else if (number.startsWith("<")) { 694 prefix = Credit.lt; 695 number = number.substring(1); 696 } else if (number.startsWith(">")) { 697 prefix = Credit.gt; 698 number = number.substring(1); 699 } else if (number.startsWith("=")) { 700 prefix = Credit.eq; 701 number = number.substring(1); 702 } 703 boolean perc = false; 704 if (number.endsWith("%")) { 705 perc = true; 706 number = number.substring(0, number.length() - 1).trim(); 707 } 708 try { 709 int a = Integer.parseInt(number); 710 switch (prefix) { 711 case eq: 712 min = max = a; 713 break; // = a 714 case le: 715 max = a; 716 break; // <= a 717 case ge: 718 min = a; 719 break; // >= a 720 case lt: 721 max = a - 1; 722 break; // < a 723 case gt: 724 min = a + 1; 725 break; // > a 726 } 727 } catch (NumberFormatException e) { 728 } 729 if (term.contains("..")) { 730 try { 731 String a = term.substring(0, term.indexOf('.')); 732 String b = term.substring(term.indexOf("..") + 2); 733 min = Integer.parseInt(a); 734 max = Integer.parseInt(b); 735 } catch (NumberFormatException e) { 736 } 737 } 738 if (min == 0 && max == Integer.MAX_VALUE) 739 return true; 740 int match = 0, total = 0; 741 for (Request r : student().getRequests()) { 742 if (r instanceof CourseRequest) { 743 CourseRequest cr = (CourseRequest) r; 744 Enrollment e = iAssignment.getValue(cr); 745 if (e == null) 746 continue; 747 for (Section section : enrollment().getSections()) { 748 if ("online".equals(attr) && section.isOnline()) 749 match++; 750 else if (("face-to-face".equals(attr) || "f2f".equals(attr)) && !section.isOnline()) 751 match++; 752 else if ("no-time".equals(attr) 753 && (section.getTime() == null || section.getTime().getDayCode() == 0)) 754 match++; 755 else if ("has-time".equals(attr) && section.getTime() != null 756 && section.getTime().getDayCode() != 0) 757 match++; 758 total++; 759 } 760 } 761 } 762 if (total == 0) 763 return false; 764 if (perc) { 765 double percentage = 100.0 * match / total; 766 return min <= percentage && percentage <= max; 767 } else { 768 return min <= match && match <= max; 769 } 770 } 771 772 if ("overlap".equals(attr)) { 773 int min = 0, max = Integer.MAX_VALUE; 774 Credit prefix = Credit.eq; 775 String number = term; 776 if (number.startsWith("<=")) { 777 prefix = Credit.le; 778 number = number.substring(2); 779 } else if (number.startsWith(">=")) { 780 prefix = Credit.ge; 781 number = number.substring(2); 782 } else if (number.startsWith("<")) { 783 prefix = Credit.lt; 784 number = number.substring(1); 785 } else if (number.startsWith(">")) { 786 prefix = Credit.gt; 787 number = number.substring(1); 788 } else if (number.startsWith("=")) { 789 prefix = Credit.eq; 790 number = number.substring(1); 791 } 792 try { 793 int a = Integer.parseInt(number); 794 switch (prefix) { 795 case eq: 796 min = max = a; 797 break; // = a 798 case le: 799 max = a; 800 break; // <= a 801 case ge: 802 min = a; 803 break; // >= a 804 case lt: 805 max = a - 1; 806 break; // < a 807 case gt: 808 min = a + 1; 809 break; // > a 810 } 811 } catch (NumberFormatException e) { 812 } 813 if (term.contains("..")) { 814 try { 815 String a = term.substring(0, term.indexOf('.')); 816 String b = term.substring(term.indexOf("..") + 2); 817 min = Integer.parseInt(a); 818 max = Integer.parseInt(b); 819 } catch (NumberFormatException e) { 820 } 821 } 822 int share = 0; 823 for (Request r : student().getRequests()) { 824 if (r instanceof CourseRequest) { 825 CourseRequest cr = (CourseRequest) r; 826 Enrollment e = iAssignment.getValue(cr); 827 if (e == null) 828 continue; 829 for (Section section : e.getSections()) { 830 if (section.getTime() == null) 831 continue; 832 for (Request q : student().getRequests()) { 833 if (q.equals(request())) 834 continue; 835 Enrollment otherEnrollment = iAssignment.getValue(q); 836 if (otherEnrollment != null && otherEnrollment.getCourse() != null) { 837 for (Section otherSection : otherEnrollment.getSections()) { 838 if (otherSection.getTime() != null 839 && otherSection.getTime().hasIntersection(section.getTime())) { 840 share += 5 * section.getTime().nrSharedHours(otherSection.getTime()) 841 * section.getTime().nrSharedDays(otherSection.getTime()); 842 } 843 } 844 } 845 } 846 } 847 } 848 } 849 return min <= share && share <= max; 850 } 851 852 if ("prefer".equals(attr)) { 853 if (cr() == null) 854 return false; 855 if (eq("Any Preference", term)) 856 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 857 if (eq("Met Preference", term) || eq("Unmet Preference", term)) { 858 if (enrollment() == null) { 859 if (eq("Unmet Preference", term)) 860 return !cr().getSelectedChoices().isEmpty() || !cr().getRequiredChoices().isEmpty(); 861 return false; 862 } 863 if (eq("Met Preference", term)) 864 return enrollment().isSelected(); 865 else 866 return !enrollment().isSelected(); 867 } 868 return false; 869 } 870 871 if ("require".equals(attr)) { 872 if (cr() == null) 873 return false; 874 if (eq("Any Requirement", term)) { 875 return !cr().getRequiredChoices().isEmpty(); 876 } 877 if (eq("Met Requirement", term)) { 878 return enrollment() != null && enrollment().isRequired(); 879 } 880 if (eq("Unmet Requirement", term)) { 881 return enrollment() != null && !enrollment().isRequired(); 882 } 883 return false; 884 } 885 886 if ("im".equals(attr)) { 887 if (cr() == null) 888 return false; 889 if (enrollment() == null) { 890 for (Course course : cr().getCourses()) { 891 for (Config config : course.getOffering().getConfigs()) { 892 if (term.equals(config.getInstructionalMethodReference())) 893 return true; 894 } 895 break; 896 } 897 return false; 898 } else { 899 Config config = enrollment().getConfig(); 900 if (config == null) 901 return false; 902 return term.equals(config.getInstructionalMethodReference()); 903 } 904 } 905 906 if (enrollment() != null && enrollment().getCourse() != null) { 907 for (Section section : enrollment().getSections()) { 908 if (attr == null || attr.equals("crn") || attr.equals("id") || attr.equals("externalId") 909 || attr.equals("exid") || attr.equals("name")) { 910 if (section.getName(enrollment().getCourse().getId()) != null && section 911 .getName(enrollment().getCourse().getId()).toLowerCase().startsWith(term.toLowerCase())) 912 return true; 913 } 914 if (attr == null || attr.equals("day")) { 915 if (section.getTime() == null && term.equalsIgnoreCase("none")) 916 return true; 917 if (section.getTime() != null) { 918 int day = parseDay(term); 919 if (day > 0 && (section.getTime().getDayCode() & day) == day) 920 return true; 921 } 922 } 923 if (attr == null || attr.equals("time")) { 924 if (section.getTime() == null && term.equalsIgnoreCase("none")) 925 return true; 926 if (section.getTime() != null) { 927 int start = parseStart(term); 928 if (start >= 0 && section.getTime().getStartSlot() == start) 929 return true; 930 } 931 } 932 if (attr != null && attr.equals("before")) { 933 if (section.getTime() != null) { 934 int end = parseStart(term); 935 if (end >= 0 && section.getTime().getStartSlot() + section.getTime().getLength() 936 - section.getTime().getBreakTime() / 5 <= end) 937 return true; 938 } 939 } 940 if (attr != null && attr.equals("after")) { 941 if (section.getTime() != null) { 942 int start = parseStart(term); 943 if (start >= 0 && section.getTime().getStartSlot() >= start) 944 return true; 945 } 946 } 947 if (attr == null || attr.equals("room")) { 948 if ((section.getRooms() == null || section.getRooms().isEmpty()) 949 && term.equalsIgnoreCase("none")) 950 return true; 951 if (section.getRooms() != null) { 952 for (RoomLocation r : section.getRooms()) { 953 if (has(r.getName(), term)) 954 return true; 955 } 956 } 957 } 958 if (attr == null || attr.equals("instr") || attr.equals("instructor")) { 959 if (attr != null && section.getInstructors().isEmpty() && term.equalsIgnoreCase("none")) 960 return true; 961 for (Instructor instuctor : section.getInstructors()) { 962 if (has(instuctor.getName(), term) || eq(instuctor.getExternalId(), term)) 963 return true; 964 if (instuctor.getEmail() != null) { 965 String email = instuctor.getEmail(); 966 if (email.indexOf('@') >= 0) 967 email = email.substring(0, email.indexOf('@')); 968 if (eq(email, term)) 969 return true; 970 } 971 } 972 } 973 } 974 } 975 976 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 977 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 978 } 979 if ("title".equals(attr)) { 980 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 981 } 982 if ("subject".equals(attr)) { 983 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 984 } 985 if ("number".equals(attr)) { 986 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 987 } 988 989 return false; 990 } 991 992 private boolean eq(String name, String term) { 993 if (name == null) 994 return false; 995 return name.equalsIgnoreCase(term); 996 } 997 998 private boolean has(String name, String term) { 999 if (name == null) 1000 return false; 1001 if (eq(name, term)) 1002 return true; 1003 for (String t : name.split(" |,")) 1004 if (t.equalsIgnoreCase(term)) 1005 return true; 1006 return false; 1007 } 1008 1009 private boolean like(String name, String term) { 1010 if (name == null) 1011 return false; 1012 if (term.indexOf('%') >= 0) { 1013 return name.matches("(?i)" + term.replaceAll("%", ".*")); 1014 } else { 1015 return name.equalsIgnoreCase(term); 1016 } 1017 } 1018 1019 public static enum Credit { 1020 eq, lt, gt, le, ge 1021 } 1022 1023 public static String DAY_NAMES_CHARS[] = new String[] { "M", "T", "W", "R", "F", "S", "X" }; 1024 1025 private int parseDay(String token) { 1026 int days = 0; 1027 boolean found = false; 1028 do { 1029 found = false; 1030 for (int i = 0; i < Constants.DAY_NAMES_SHORT.length; i++) { 1031 if (token.toLowerCase().startsWith(Constants.DAY_NAMES_SHORT[i].toLowerCase())) { 1032 days |= Constants.DAY_CODES[i]; 1033 token = token.substring(Constants.DAY_NAMES_SHORT[i].length()); 1034 while (token.startsWith(" ")) 1035 token = token.substring(1); 1036 found = true; 1037 } 1038 } 1039 for (int i = 0; i < DAY_NAMES_CHARS.length; i++) { 1040 if (token.toLowerCase().startsWith(DAY_NAMES_CHARS[i].toLowerCase())) { 1041 days |= Constants.DAY_CODES[i]; 1042 token = token.substring(DAY_NAMES_CHARS[i].length()); 1043 while (token.startsWith(" ")) 1044 token = token.substring(1); 1045 found = true; 1046 } 1047 } 1048 } while (found); 1049 return (token.isEmpty() ? days : 0); 1050 } 1051 1052 private int parseStart(String token) { 1053 int startHour = 0, startMin = 0; 1054 String number = ""; 1055 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1056 number += token.substring(0, 1); 1057 token = token.substring(1); 1058 } 1059 if (number.isEmpty()) 1060 return -1; 1061 if (number.length() > 2) { 1062 startHour = Integer.parseInt(number) / 100; 1063 startMin = Integer.parseInt(number) % 100; 1064 } else { 1065 startHour = Integer.parseInt(number); 1066 } 1067 while (token.startsWith(" ")) 1068 token = token.substring(1); 1069 if (token.startsWith(":")) { 1070 token = token.substring(1); 1071 while (token.startsWith(" ")) 1072 token = token.substring(1); 1073 number = ""; 1074 while (!token.isEmpty() && token.charAt(0) >= '0' && token.charAt(0) <= '9') { 1075 number += token.substring(0, 1); 1076 token = token.substring(1); 1077 } 1078 if (number.isEmpty()) 1079 return -1; 1080 startMin = Integer.parseInt(number); 1081 } 1082 while (token.startsWith(" ")) 1083 token = token.substring(1); 1084 boolean hasAmOrPm = false; 1085 if (token.toLowerCase().startsWith("am")) { 1086 token = token.substring(2); 1087 hasAmOrPm = true; 1088 } 1089 if (token.toLowerCase().startsWith("a")) { 1090 token = token.substring(1); 1091 hasAmOrPm = true; 1092 } 1093 if (token.toLowerCase().startsWith("pm")) { 1094 token = token.substring(2); 1095 hasAmOrPm = true; 1096 if (startHour < 12) 1097 startHour += 12; 1098 } 1099 if (token.toLowerCase().startsWith("p")) { 1100 token = token.substring(1); 1101 hasAmOrPm = true; 1102 if (startHour < 12) 1103 startHour += 12; 1104 } 1105 if (startHour < 7 && !hasAmOrPm) 1106 startHour += 12; 1107 if (startMin % 5 != 0) 1108 startMin = 5 * ((startMin + 2) / 5); 1109 if (startHour == 7 && startMin == 0 && !hasAmOrPm) 1110 startHour += 12; 1111 return (60 * startHour + startMin) / 5; 1112 } 1113 } 1114 1115 public static class CourseMatcher implements Query.TermMatcher { 1116 private Course iCourse; 1117 1118 public CourseMatcher(Course course) { 1119 iCourse = course; 1120 } 1121 1122 public Course course() { return iCourse; } 1123 1124 @Override 1125 public boolean match(String attr, String term) { 1126 if (attr == null || "name".equals(attr) || "course".equals(attr)) { 1127 return course() != null && (course().getSubjectArea().equalsIgnoreCase(term) || course().getCourseNumber().equalsIgnoreCase(term) || (course().getSubjectArea() + " " + course().getCourseNumber()).equalsIgnoreCase(term)); 1128 } 1129 if ("title".equals(attr)) { 1130 return course() != null && course().getTitle().toLowerCase().contains(term.toLowerCase()); 1131 } 1132 if ("subject".equals(attr)) { 1133 return course() != null && course().getSubjectArea().equalsIgnoreCase(term); 1134 } 1135 if ("number".equals(attr)) { 1136 return course() != null && course().getCourseNumber().equalsIgnoreCase(term); 1137 } 1138 return true; 1139 } 1140 } 1141}