Example #1
0
  public void reloadOffering(
      final OnlineSectioningServer server, OnlineSectioningHelper helper, Long offeringId) {
    // Load new students
    Map<Long, org.unitime.timetable.model.Student> newStudents =
        new HashMap<Long, org.unitime.timetable.model.Student>();
    /*
    for (org.unitime.timetable.model.Student student : (List<org.unitime.timetable.model.Student>)helper.getHibSession().createQuery(
                  "select s from Student s " +
                  "left join fetch s.courseDemands as cd " +
                  "left join fetch cd.courseRequests as cr " +
                  "left join fetch cr.courseOffering as co " +
                  "left join fetch cr.classWaitLists as cwl " +
                  "left join fetch s.classEnrollments as e " +
                  "left join fetch s.academicAreaClassifications as a " +
                  "left join fetch s.posMajors as mj " +
                  "left join fetch s.waitlists as w " +
                  "left join fetch s.groups as g " +
                  "where s.uniqueId in (select xe.student.uniqueId from StudentClassEnrollment xe where xe.courseOffering.instructionalOffering.uniqueId = :offeringId) " +
                  "or s.uniqueId in (select xr.courseDemand.student.uniqueId from CourseRequest xr where xr.courseOffering.instructionalOffering.uniqueId = :offeringId)"
                  ).setLong("offeringId", offeringId).list()) {
    	newStudents.put(student.getUniqueId(), student);
    }
    */
    for (org.unitime.timetable.model.Student student :
        (List<org.unitime.timetable.model.Student>)
            helper
                .getHibSession()
                .createQuery(
                    "select distinct s from Student s "
                        + "left join s.courseDemands as cd "
                        + "left join cd.courseRequests as cr "
                        + "left join fetch s.classEnrollments as e "
                        + "left join fetch s.academicAreaClassifications as a "
                        + "left join fetch s.posMajors as mj "
                        + "left join fetch s.waitlists as w "
                        + "left join fetch s.groups as g "
                        + "where cr.courseOffering.instructionalOffering.uniqueId = :offeringId")
                .setLong("offeringId", offeringId)
                .list()) {
      newStudents.put(student.getUniqueId(), student);
    }
    for (org.unitime.timetable.model.Student student :
        (List<org.unitime.timetable.model.Student>)
            helper
                .getHibSession()
                .createQuery(
                    "select distinct s from Student s "
                        + "left join fetch s.courseDemands as cd "
                        + "left join fetch cd.courseRequests as cr "
                        + "left join fetch cr.courseOffering as co "
                        + "left join fetch cr.classWaitLists as cwl "
                        + "left join fetch s.classEnrollments as e "
                        + "left join fetch s.academicAreaClassifications as a "
                        + "left join fetch s.posMajors as mj "
                        + "left join fetch s.waitlists as w "
                        + "left join fetch s.groups as g "
                        + "where e.courseOffering.instructionalOffering.uniqueId = :offeringId and e.courseRequest is null")
                .setLong("offeringId", offeringId)
                .list()) {
      newStudents.put(student.getUniqueId(), student);
    }

    // Persist expected spaces if needed
    if (server.needPersistExpectedSpaces(offeringId))
      PersistExpectedSpacesAction.persistExpectedSpaces(offeringId, false, server, helper);

    // Existing offering
    XOffering oldOffering = server.getOffering(offeringId);
    XEnrollments oldEnrollments = server.getEnrollments(offeringId);

    // New offering
    XOffering newOffering = null;
    InstructionalOffering io =
        InstructionalOfferingDAO.getInstance().get(offeringId, helper.getHibSession());
    List<XDistribution> distributions = new ArrayList<XDistribution>();
    if (io != null) {
      // Load linked sections and ignore student conflict constraints
      List<DistributionPref> distPrefs =
          helper
              .getHibSession()
              .createQuery(
                  "select distinct p from DistributionPref p inner join p.distributionObjects o, Department d, "
                      + "Class_ c inner join c.schedulingSubpart.instrOfferingConfig.instructionalOffering io "
                      + "where p.distributionType.reference in (:ref1, :ref2) and d.session.uniqueId = :sessionId "
                      + "and io.uniqueId = :offeringId and (o.prefGroup = c or o.prefGroup = c.schedulingSubpart) "
                      + "and p.owner = d and p.prefLevel.prefProlog = :pref")
              .setString("ref1", GroupConstraint.ConstraintType.LINKED_SECTIONS.reference())
              .setString("ref2", IgnoreStudentConflictsConstraint.REFERENCE)
              .setString("pref", PreferenceLevel.sRequired)
              .setLong("sessionId", server.getAcademicSession().getUniqueId())
              .setLong("offeringId", offeringId)
              .list();
      if (!distPrefs.isEmpty()) {
        for (DistributionPref pref : distPrefs) {
          int variant = 0;
          for (Collection<Class_> sections : ReloadAllData.getSections(pref)) {
            XDistributionType type = XDistributionType.IngoreConflicts;
            if (GroupConstraint.ConstraintType.LINKED_SECTIONS
                .reference()
                .equals(pref.getDistributionType().getReference()))
              type = XDistributionType.LinkedSections;
            XDistribution distribution =
                new XDistribution(type, pref.getUniqueId(), variant++, sections);
            distributions.add(distribution);
          }
        }
      }

      newOffering = ReloadAllData.loadOffering(io, distributions, server, helper);
      if (newOffering != null) server.update(newOffering);
      else if (oldOffering != null) server.remove(oldOffering);

      // Load sectioning info
      List<Object[]> infos =
          helper
              .getHibSession()
              .createQuery(
                  "select i.clazz.uniqueId, i.nbrExpectedStudents from SectioningInfo i where i.clazz.schedulingSubpart.instrOfferingConfig.instructionalOffering.uniqueId = :offeringId")
              .setLong("offeringId", offeringId)
              .list();
      XExpectations expectations = new XExpectations(offeringId);
      for (Object[] info : infos) {
        Long sectionId = (Long) info[0];
        Double expected = (Double) info[1];
        expectations.setExpectedSpace(sectionId, expected);
      }
      server.update(expectations);
    } else if (oldOffering != null) {
      server.remove(oldOffering);
    }

    List<XStudent[]> students = new ArrayList<XStudent[]>();

    if (oldEnrollments != null) {
      Set<Long> checked = new HashSet<Long>();
      for (XRequest old : oldEnrollments.getRequests()) {
        if (!checked.add(old.getStudentId())) continue;
        XStudent oldStudent = server.getStudent(old.getStudentId());
        org.unitime.timetable.model.Student student = newStudents.get(oldStudent.getStudentId());
        if (student == null)
          student = StudentDAO.getInstance().get(oldStudent.getStudentId(), helper.getHibSession());
        XStudent newStudent =
            (student == null ? null : ReloadAllData.loadStudent(student, null, server, helper));
        if (newStudent != null) server.update(newStudent, true);
        else server.remove(oldStudent);
        students.add(new XStudent[] {oldStudent, newStudent});
        newStudents.remove(oldStudent.getStudentId());
      }
    }
    for (org.unitime.timetable.model.Student student : newStudents.values()) {
      XStudent oldStudent = server.getStudent(student.getUniqueId());
      XStudent newStudent = ReloadAllData.loadStudent(student, null, server, helper);
      if (newStudent != null) server.update(newStudent, true);
      else if (oldStudent != null) server.remove(oldStudent);
      students.add(new XStudent[] {oldStudent, newStudent});
    }

    if (!server.getAcademicSession().isSectioningEnabled()) return;

    if (!CustomStudentEnrollmentHolder.isAllowWaitListing()) return;

    if (newOffering == null && oldOffering == null) return;

    Set<SectioningRequest> queue = new TreeSet<SectioningRequest>();

    Set<XCourseId> courseIds = new HashSet<XCourseId>();
    if (newOffering != null) courseIds.addAll(newOffering.getCourses());
    if (oldOffering != null) courseIds.addAll(oldOffering.getCourses());

    for (XCourseId course : courseIds) {
      for (XStudent[] student : students) {
        if (student[0] == null && student[1] == null) continue;
        XEnrollment oldEnrollment = null;
        XCourseRequest oldRequest = null;
        if (student[0] != null) {
          for (XRequest r : student[0].getRequests())
            if (r instanceof XCourseRequest) {
              XCourseRequest cr = (XCourseRequest) r;
              for (XCourseId c : cr.getCourseIds()) {
                if (c.equals(course)) {
                  oldRequest = cr;
                  if (cr.getEnrollment() != null
                      && offeringId.equals(cr.getEnrollment().getOfferingId()))
                    oldEnrollment = cr.getEnrollment();
                  break;
                }
              }
            }
        }
        XCourseRequest newRequest = null;
        XEnrollment newEnrollment = null;
        if (student[1] != null) {
          for (XRequest r : student[1].getRequests())
            if (r instanceof XCourseRequest) {
              XCourseRequest cr = (XCourseRequest) r;
              for (XCourseId c : cr.getCourseIds())
                if (c.equals(course)) {
                  newRequest = cr;
                  if (cr.getEnrollment() != null
                      && offeringId.equals(cr.getEnrollment().getOfferingId()))
                    newEnrollment = cr.getEnrollment();
                  break;
                }
            }
        }
        if (oldRequest == null && newRequest == null) continue;

        OnlineSectioningLog.Action.Builder action =
            helper.addAction(this, server.getAcademicSession());
        action.setStudent(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(
                    student[0] == null ? student[1].getStudentId() : student[0].getStudentId())
                .setExternalId(
                    student[0] == null ? student[1].getExternalId() : student[0].getExternalId()));
        action.addOther(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(offeringId)
                .setName(newOffering == null ? oldOffering.getName() : newOffering.getName())
                .setType(OnlineSectioningLog.Entity.EntityType.OFFERING));
        action.addOther(
            OnlineSectioningLog.Entity.newBuilder()
                .setUniqueId(course.getCourseId())
                .setName(course.getCourseName())
                .setType(OnlineSectioningLog.Entity.EntityType.COURSE));

        if (oldEnrollment != null) {
          OnlineSectioningLog.Enrollment.Builder enrollment =
              OnlineSectioningLog.Enrollment.newBuilder();
          enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.PREVIOUS);
          for (Long sectionId : oldEnrollment.getSectionIds())
            enrollment.addSection(
                OnlineSectioningHelper.toProto(oldOffering.getSection(sectionId), oldEnrollment));
          action.addEnrollment(enrollment);
          if (newRequest == null) action.addRequest(OnlineSectioningHelper.toProto(oldRequest));
        }

        if (newRequest == null) {
          // nothing to re-assign
          if (oldEnrollment != null && CustomStudentEnrollmentHolder.hasProvider()) {
            // there was a drop
            try {
              CustomStudentEnrollmentHolder.getProvider()
                  .resection(
                      server,
                      helper,
                      new SectioningRequest(newOffering, newRequest, student[1], action)
                          .setOldOffering(oldOffering)
                          .setOldStudent(student[0])
                          .setOldRequest(oldRequest)
                          .setLastEnrollment(oldEnrollment),
                      null);
            } catch (Exception ex) {
              action.setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
              action.addMessage(
                  OnlineSectioningLog.Message.newBuilder()
                      .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                      .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
              helper.error("Unable to resection student: " + ex.getMessage(), ex);
            }
          }
          action.setEndTime(System.currentTimeMillis());
          server.execute(
              server
                  .createAction(NotifyStudentAction.class)
                  .forStudent(
                      student[0] == null ? student[1].getStudentId() : student[0].getStudentId())
                  .oldEnrollment(oldOffering, course, oldEnrollment),
              helper.getUser());
          continue;
        } else {
          action.addRequest(OnlineSectioningHelper.toProto(newRequest));
        }

        if (oldEnrollment == null && newEnrollment == null) {
          if (student[1].canAssign(newRequest)
              && isWaitListed(student[1], newRequest, server, helper))
            queue.add(
                new SectioningRequest(newOffering, newRequest, student[1], action)
                    .setOldOffering(oldOffering)
                    .setOldRequest(oldRequest)
                    .setOldStudent(student[0]));
          continue;
        }

        if (newEnrollment != null) {
          // new enrollment is valid and / or has all the same times
          if (check(
              newOffering,
              course,
              distributions,
              student[1],
              newEnrollment,
              server)) { // || isSame(oldEnrollment, newEnrollment)) {
            OnlineSectioningLog.Enrollment.Builder enrollment =
                OnlineSectioningLog.Enrollment.newBuilder();
            enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.STORED);
            for (XSection assignment : newOffering.getSections(newEnrollment))
              enrollment.addSection(OnlineSectioningHelper.toProto(assignment, newEnrollment));
            action.addEnrollment(enrollment);

            // there may have been a change
            if (CustomStudentEnrollmentHolder.hasProvider()) {
              try {
                CustomStudentEnrollmentHolder.getProvider()
                    .resection(
                        server,
                        helper,
                        new SectioningRequest(newOffering, newRequest, student[1], action)
                            .setOldOffering(oldOffering)
                            .setOldStudent(student[0])
                            .setOldRequest(oldRequest)
                            .setLastEnrollment(oldEnrollment),
                        newEnrollment);
              } catch (Exception ex) {
                action.setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
                action.addMessage(
                    OnlineSectioningLog.Message.newBuilder()
                        .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                        .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
                helper.error("Unable to resection student: " + ex.getMessage(), ex);
              }
            }
            action.setEndTime(System.currentTimeMillis());

            if (!isVerySame(
                newEnrollment.getCourseId(),
                newOffering.getSections(newEnrollment),
                oldOffering.getSections(oldEnrollment)))
              server.execute(
                  server
                      .createAction(NotifyStudentAction.class)
                      .forStudent(
                          student[0] == null
                              ? student[1].getStudentId()
                              : student[0].getStudentId())
                      .oldEnrollment(oldOffering, course, oldEnrollment),
                  helper.getUser());
            continue;
          }
        }
        newRequest = server.assign(newRequest, null);
        queue.add(
            new SectioningRequest(newOffering, newRequest, student[1], action)
                .setOldOffering(oldOffering)
                .setOldRequest(oldRequest)
                .setOldStudent(student[0])
                .setLastEnrollment(oldEnrollment)
                .setNewEnrollment(newEnrollment));
      }
    }

    if (!queue.isEmpty()) {
      DataProperties properties = new DataProperties();
      ResectioningWeights w = new ResectioningWeights(properties);
      DistanceConflict dc = new DistanceConflict(server.getDistanceMetric(), properties);
      TimeOverlapsCounter toc = new TimeOverlapsCounter(null, properties);
      Date ts = new Date();
      for (SectioningRequest r : queue) {
        helper.debug(
            "Resectioning "
                + r.getRequest()
                + " (was "
                + (r.getLastEnrollment() == null
                    ? "not assigned"
                    : r.getLastEnrollment().getSectionIds())
                + ")");
        long c0 = OnlineSectioningHelper.getCpuTime();
        XEnrollment e = r.resection(server, w, dc, toc);

        if (e != null) {
          e.setTimeStamp(ts);
          OnlineSectioningLog.Enrollment.Builder enrollment =
              OnlineSectioningLog.Enrollment.newBuilder();
          enrollment.setType(OnlineSectioningLog.Enrollment.EnrollmentType.STORED);
          for (Long sectionId : e.getSectionIds())
            enrollment.addSection(
                OnlineSectioningHelper.toProto(newOffering.getSection(sectionId), e));
          r.getAction().addEnrollment(enrollment);
        }

        if (CustomStudentEnrollmentHolder.hasProvider()) {
          try {
            e = CustomStudentEnrollmentHolder.getProvider().resection(server, helper, r, e);
          } catch (Exception ex) {
            r.getAction().setResult(OnlineSectioningLog.Action.ResultType.FAILURE);
            r.getAction()
                .addMessage(
                    OnlineSectioningLog.Message.newBuilder()
                        .setLevel(OnlineSectioningLog.Message.Level.FATAL)
                        .setText(ex.getMessage() == null ? "null" : ex.getMessage()));
            helper.error("Unable to resection student: " + ex.getMessage(), ex);
            if (r.getNewEnrollment() != null) server.assign(r.getRequest(), r.getNewEnrollment());
            continue;
          }
        }

        if (e == null && r.getLastEnrollment() == null) {
          // remained unassigned
          continue;
        }

        if (e != null) {
          r.setRequest(server.assign(r.getRequest(), e));
        }
        helper.debug("New: " + (e == null ? "not assigned" : e.getSectionIds()));

        org.unitime.timetable.model.Student student =
            StudentDAO.getInstance().get(r.getRequest().getStudentId(), helper.getHibSession());
        Map<Long, StudentClassEnrollment> enrollmentMap =
            new HashMap<Long, StudentClassEnrollment>();
        String approvedBy = null;
        Date approvedDate = null;
        for (Iterator<StudentClassEnrollment> i = student.getClassEnrollments().iterator();
            i.hasNext(); ) {
          StudentClassEnrollment enrl = i.next();
          if ((enrl.getCourseRequest() != null
                  && enrl.getCourseRequest()
                      .getCourseDemand()
                      .getUniqueId()
                      .equals(r.getRequest().getRequestId()))
              || (enrl.getCourseOffering() != null
                  && enrl.getCourseOffering()
                      .getUniqueId()
                      .equals(r.getCourseId().getCourseId()))) {
            helper.debug("Deleting " + enrl.getClazz().getClassLabel());
            enrollmentMap.put(enrl.getClazz().getUniqueId(), enrl);
            if (approvedBy == null && enrl.getApprovedBy() != null) {
              approvedBy = enrl.getApprovedBy();
              approvedDate = enrl.getApprovedDate();
            }
            enrl.getClazz().getStudentEnrollments().remove(enrl);
            helper.getHibSession().delete(enrl);
            i.remove();
          }
        }
        CourseDemand cd = null;
        demands:
        for (CourseDemand x : student.getCourseDemands())
          for (org.unitime.timetable.model.CourseRequest q : x.getCourseRequests())
            if (q.getCourseOffering().getInstructionalOffering().getUniqueId().equals(offeringId)) {
              cd = x;
              break demands;
            }

        if (r.getRequest().getEnrollment() != null) { // save enrollment
          org.unitime.timetable.model.CourseRequest cr = null;
          CourseOffering co = null;
          if (co == null)
            for (CourseOffering x : io.getCourseOfferings())
              if (x.getUniqueId().equals(r.getRequest().getEnrollment().getCourseId())) co = x;
          for (Long sectionId : r.getRequest().getEnrollment().getSectionIds()) {
            Class_ clazz = Class_DAO.getInstance().get(sectionId, helper.getHibSession());
            if (cd != null && cr == null) {
              for (org.unitime.timetable.model.CourseRequest x : cd.getCourseRequests())
                if (x.getCourseOffering()
                    .getInstructionalOffering()
                    .getUniqueId()
                    .equals(offeringId)) {
                  cr = x;
                  break;
                }
            }
            if (co == null)
              co =
                  clazz
                      .getSchedulingSubpart()
                      .getInstrOfferingConfig()
                      .getInstructionalOffering()
                      .getControllingCourseOffering();
            StudentClassEnrollment enrl = new StudentClassEnrollment();
            enrl.setClazz(clazz);
            clazz.getStudentEnrollments().add(enrl);
            enrl.setCourseOffering(co);
            enrl.setCourseRequest(cr);
            StudentClassEnrollment old = enrollmentMap.get(sectionId);
            enrl.setTimestamp(old != null ? old.getTimestamp() : ts);
            enrl.setChangedBy(
                old != null
                    ? old.getChangedBy()
                    : helper.getUser() == null
                        ? StudentClassEnrollment.SystemChange.SYSTEM.toString()
                        : helper.getUser().getExternalId());
            enrl.setStudent(student);
            enrl.setApprovedBy(approvedBy);
            enrl.setApprovedDate(approvedDate);
            student.getClassEnrollments().add(enrl);
            /*
            if (cr != null) {
            	if (cr.getClassEnrollments() == null) cr.setClassEnrollments(new HashSet<StudentClassEnrollment>());
            	cr.getClassEnrollments().add(enrl);
            }
            */
            helper.debug("Adding " + enrl.getClazz().getClassLabel());
          }
        } else if (!r.getRequest().isAlternative()) { // wait-list
          if (cd != null && !cd.isWaitlist()) {
            cd.setWaitlist(true);
            helper.getHibSession().saveOrUpdate(cd);
          }
          if (!r.getRequest().isWaitlist()) r.setRequest(server.waitlist(r.getRequest(), true));
        }

        helper.getHibSession().save(student);

        EnrollStudent.updateSpace(
            server,
            r.getRequest().getEnrollment() == null
                ? null
                : SectioningRequest.convert(
                    r.getStudent(),
                    r.getRequest(),
                    server,
                    newOffering,
                    r.getRequest().getEnrollment()),
            r.getLastEnrollment() == null
                ? null
                : SectioningRequest.convert(
                    r.getOldStudent(),
                    r.getOldRequest(),
                    server,
                    oldOffering,
                    r.getLastEnrollment()),
            newOffering,
            oldOffering);
        server.persistExpectedSpaces(offeringId);

        server.execute(
            server
                .createAction(NotifyStudentAction.class)
                .forStudent(r.getRequest().getStudentId())
                .oldEnrollment(oldOffering, r.getCourseId(), r.getLastEnrollment()),
            helper.getUser());

        r.getAction()
            .setResult(
                e == null
                    ? OnlineSectioningLog.Action.ResultType.NULL
                    : OnlineSectioningLog.Action.ResultType.SUCCESS);
        r.getAction().setCpuTime(OnlineSectioningHelper.getCpuTime() - c0);
        r.getAction().setEndTime(System.currentTimeMillis());
      }
    }
  }
  /**
   * Build a html table with the list representing distribution prefs
   *
   * @param distPrefs
   * @param ordCol
   * @param editable
   * @return
   */
  public String toHtmlTable(
      HttpServletRequest request, SessionContext context, Collection distPrefs, String title)
      throws Exception {

    String backId =
        ("PreferenceGroup".equals(request.getParameter("backType"))
            ? request.getParameter("backId")
            : null);

    WebTable.setOrder(context, "examDistPrefsTable.ord", request.getParameter("order"), 4);

    WebTable tbl =
        new WebTable(
            3,
            title,
            "examDistributionPrefs.do?order=%%",
            new String[] {" Type ", " Exam ", " Class/Course "},
            new String[] {"left", "left", "left"},
            new boolean[] {true, true, true});

    int nrPrefs = 0;

    for (Iterator i1 = distPrefs.iterator(); i1.hasNext(); ) {
      DistributionPref dp = (DistributionPref) i1.next();

      if (!context.hasPermission(dp, Right.ExaminationDistributionPreferenceDetail)) continue;

      boolean prefEditable = context.hasPermission(dp, Right.ExaminationDistributionPreferenceEdit);

      nrPrefs++;

      String examStr = "";
      String objStr = "";

      for (Iterator i2 = dp.getOrderedSetOfDistributionObjects().iterator(); i2.hasNext(); ) {
        DistributionObject dO = (DistributionObject) i2.next();
        Exam exam = (Exam) dO.getPrefGroup();
        examStr += dO.preferenceText();
        for (Iterator i3 = exam.getOwners().iterator(); i3.hasNext(); ) {
          ExamOwner owner = (ExamOwner) i3.next();
          objStr += owner.getLabel();
          if (i3.hasNext()) {
            examStr += "<BR>";
            objStr += "<BR>";
          }
        }
        if (i2.hasNext()) {
          examStr += "<BR>";
          objStr += "<BR>";
        }
      }

      String distType = dp.getDistributionType().getLabel();
      String prefLevel = dp.getPrefLevel().getPrefName();
      String prefColor = dp.getPrefLevel().prefcolor();
      if (PreferenceLevel.sNeutral.equals(dp.getPrefLevel().getPrefProlog())) prefColor = "gray";
      String onClick = null;

      boolean gray = false;

      if (prefEditable) {
        onClick =
            "onClick=\"document.location='examDistributionPrefs.do"
                + "?dp="
                + dp.getUniqueId().toString()
                + "&op=view'\"";
      } // else gray = true;

      boolean back = dp.getUniqueId().toString().equals(backId);

      tbl.addLine(
          onClick,
          new String[] {
            (back ? "<A name=\"back\"</A>" : "")
                + (gray
                    ? "<span style='color:gray;'>"
                    : "<span style='color:"
                        + prefColor
                        + ";font-weight:bold;' title='"
                        + prefLevel
                        + " "
                        + distType
                        + "'>")
                + distType
                + "</span>",
            (gray ? "<span style='color:gray;'>" : "") + examStr + (gray ? "</span>" : ""),
            (gray ? "<span style='color:gray;'>" : "") + objStr + (gray ? "</span>" : "")
          },
          new Comparable[] {distType, examStr, objStr});
    }

    if (nrPrefs == 0) tbl.addLine(null, new String[] {"No preferences found", "", ""}, null);

    return tbl.printTable(WebTable.getOrder(context, "examDistPrefsTable.ord"));
  }