private void tagFrontLoadLargeTopics() {
   List<Topic> sortedTopicList = new ArrayList<Topic>(examination.getTopicList());
   Collections.sort(
       sortedTopicList,
       new Comparator<Topic>() {
         public int compare(Topic a, Topic b) {
           return new CompareToBuilder()
               .append(a.getStudentSize(), b.getStudentSize()) // Ascending
               .append(b.getId(), a.getId()) // Descending (according to spec)
               .toComparison();
         }
       });
   int frontLoadLargeTopicSize =
       examination.getInstitutionParametrization().getFrontLoadLargeTopicSize();
   if (frontLoadLargeTopicSize == 0) {
     return;
   }
   int minimumTopicId = sortedTopicList.size() - frontLoadLargeTopicSize;
   if (minimumTopicId < 0) {
     logger.warn(
         "The frontLoadLargeTopicSize ("
             + frontLoadLargeTopicSize
             + ") is bigger than topicListSize ("
             + sortedTopicList.size()
             + "). Tagging all topic as frontLoadLarge...");
     minimumTopicId = 0;
   }
   for (Topic topic : sortedTopicList.subList(minimumTopicId, sortedTopicList.size())) {
     topic.setFrontLoadLarge(true);
   }
 }
 private void readTopicListAndStudentList() throws IOException {
   coincidenceMap = new LinkedHashMap<Topic, Set<Topic>>();
   exclusionMap = new LinkedHashMap<Topic, Set<Topic>>();
   afterMap = new LinkedHashMap<Topic, Set<Topic>>();
   Map<Integer, Student> studentMap = new HashMap<Integer, Student>();
   int examSize = readHeaderWithNumber("Exams");
   List<Topic> topicList = new ArrayList<Topic>(examSize);
   for (int i = 0; i < examSize; i++) {
     Topic topic = new Topic();
     topic.setId((long) i);
     String line = bufferedReader.readLine();
     String[] lineTokens = line.split(SPLIT_REGEX);
     topic.setDuration(Integer.parseInt(lineTokens[0]));
     List<Student> topicStudentList = new ArrayList<Student>(lineTokens.length - 1);
     for (int j = 1; j < lineTokens.length; j++) {
       topicStudentList.add(findOrCreateStudent(studentMap, Integer.parseInt(lineTokens[j])));
     }
     topic.setStudentList(topicStudentList);
     topic.setFrontLoadLarge(false);
     topicList.add(topic);
     coincidenceMap.put(topic, new HashSet<Topic>());
     exclusionMap.put(topic, new HashSet<Topic>());
     afterMap.put(topic, new HashSet<Topic>());
   }
   examination.setTopicList(topicList);
   List<Student> studentList = new ArrayList<Student>(studentMap.values());
   examination.setStudentList(studentList);
 }
 private void createExamList() {
   List<Topic> topicList = examination.getTopicList();
   List<Exam> examList = new ArrayList<Exam>(topicList.size());
   Map<Topic, LeadingExam> leadingTopicToExamMap =
       new HashMap<Topic, LeadingExam>(topicList.size());
   for (Topic topic : topicList) {
     Exam exam;
     Topic leadingTopic = topic;
     for (Topic coincidenceTopic : coincidenceMap.get(topic)) {
       if (coincidenceTopic.getId() < leadingTopic.getId()) {
         leadingTopic = coincidenceTopic;
       }
     }
     if (leadingTopic == topic) {
       LeadingExam leadingExam = new LeadingExam();
       leadingExam.setFollowingExamList(new ArrayList<FollowingExam>(10));
       leadingTopicToExamMap.put(topic, leadingExam);
       exam = leadingExam;
     } else {
       FollowingExam followingExam = new FollowingExam();
       LeadingExam leadingExam = leadingTopicToExamMap.get(leadingTopic);
       if (leadingExam == null) {
         throw new IllegalStateException(
             "The followingExam ("
                 + topic.getId()
                 + ")'s leadingExam ("
                 + leadingExam
                 + ") cannot be null.");
       }
       followingExam.setLeadingExam(leadingExam);
       leadingExam.getFollowingExamList().add(followingExam);
       exam = followingExam;
     }
     exam.setId(topic.getId());
     exam.setTopic(topic);
     // Notice that we leave the PlanningVariable properties on null
     examList.add(exam);
   }
   examination.setExamList(examList);
 }
    private void readPeriodPenaltyList() throws IOException {
      readConstantLine("[PeriodHardConstraints]");
      List<Topic> topicList = examination.getTopicList();
      List<PeriodPenalty> periodPenaltyList = new ArrayList<PeriodPenalty>();
      String line = bufferedReader.readLine();
      int id = 0;
      while (!line.equals("[RoomHardConstraints]")) {
        String[] lineTokens = line.split(SPLIT_REGEX);
        if (lineTokens.length != 3) {
          throw new IllegalArgumentException(
              "Read line (" + line + ") is expected to contain 3 tokens.");
        }
        PeriodPenalty periodPenalty = new PeriodPenalty();
        periodPenalty.setId((long) id);
        id++;
        Topic leftTopic = topicList.get(Integer.parseInt(lineTokens[0]));
        periodPenalty.setLeftSideTopic(leftTopic);
        PeriodPenaltyType periodPenaltyType = PeriodPenaltyType.valueOf(lineTokens[1]);
        periodPenalty.setPeriodPenaltyType(periodPenaltyType);
        Topic rightTopic = topicList.get(Integer.parseInt(lineTokens[2]));
        periodPenalty.setRightSideTopic(rightTopic);
        boolean ignorePenalty = false;

        switch (periodPenaltyType) {
          case EXAM_COINCIDENCE:
            if (leftTopic.getId().equals(rightTopic.getId())) {
              logger.warn(
                  "  Filtering out periodPenalty ("
                      + periodPenalty
                      + ") because the left and right topic are the same.");
              ignorePenalty = true;
            } else if (!Collections.disjoint(
                leftTopic.getStudentList(), rightTopic.getStudentList())) {
              throw new IllegalStateException(
                  "PeriodPenalty ("
                      + periodPenalty
                      + ") for leftTopic ("
                      + leftTopic
                      + ") and rightTopic ("
                      + rightTopic
                      + ")'s left and right topic share students.");
            } else if (coincidenceMap.get(leftTopic).contains(rightTopic)) {
              logger.trace(
                  "  Filtering out periodPenalty ("
                      + periodPenalty
                      + ") for leftTopic ("
                      + leftTopic
                      + ") and rightTopic ("
                      + rightTopic
                      + ") because it is mentioned twice.");
              ignorePenalty = true;
            } else {
              boolean added =
                  coincidenceMap.get(leftTopic).add(rightTopic)
                      && coincidenceMap.get(rightTopic).add(leftTopic);
              if (!added) {
                throw new IllegalStateException(
                    "The periodPenaltyType ("
                        + periodPenaltyType
                        + ") for leftTopic ("
                        + leftTopic
                        + ") and rightTopic ("
                        + rightTopic
                        + ") was not successfully added twice.");
              }
            }
            break;
          case EXCLUSION:
            if (leftTopic.getId().equals(rightTopic.getId())) {
              logger.warn(
                  "  Filtering out periodPenalty ("
                      + periodPenalty
                      + ") for leftTopic ("
                      + leftTopic
                      + ") and rightTopic ("
                      + rightTopic
                      + ") because the left and right topic are the same.");
              ignorePenalty = true;
            } else if (exclusionMap.get(leftTopic).contains(rightTopic)) {
              logger.trace(
                  "  Filtering out periodPenalty ("
                      + periodPenalty
                      + ") for leftTopic ("
                      + leftTopic
                      + ") and rightTopic ("
                      + rightTopic
                      + ") because it is mentioned twice.");
              ignorePenalty = true;
            } else {
              boolean added =
                  exclusionMap.get(leftTopic).add(rightTopic)
                      && exclusionMap.get(rightTopic).add(leftTopic);
              if (!added) {
                throw new IllegalStateException(
                    "The periodPenaltyType ("
                        + periodPenaltyType
                        + ") for leftTopic ("
                        + leftTopic
                        + ") and rightTopic ("
                        + rightTopic
                        + ") was not successfully added twice.");
              }
            }
            break;
          case AFTER:
            if (afterMap.get(leftTopic).contains(rightTopic)) {
              ignorePenalty = true;
            } else {
              boolean added = afterMap.get(leftTopic).add(rightTopic);
              if (!added) {
                throw new IllegalStateException(
                    "The periodPenaltyType ("
                        + periodPenaltyType
                        + ") for leftTopic ("
                        + leftTopic
                        + ") and rightTopic ("
                        + rightTopic
                        + ") was not successfully added.");
              }
            }
            break;
          default:
            throw new IllegalStateException(
                "The periodPenaltyType ("
                    + periodPenalty.getPeriodPenaltyType()
                    + ") is not implemented.");
        }
        if (!ignorePenalty) {
          periodPenaltyList.add(periodPenalty);
        }
        line = bufferedReader.readLine();
      }
      // createIndirectPeriodPenalties of type EXAM_COINCIDENCE
      for (Map.Entry<Topic, Set<Topic>> entry : coincidenceMap.entrySet()) {
        Topic leftTopic = entry.getKey();
        Set<Topic> middleTopicSet = entry.getValue();
        for (Topic middleTopic : new ArrayList<Topic>(middleTopicSet)) {
          for (Topic rightTopic : new ArrayList<Topic>(coincidenceMap.get(middleTopic))) {
            if (rightTopic != leftTopic && !middleTopicSet.contains(rightTopic)) {
              PeriodPenalty indirectPeriodPenalty = new PeriodPenalty();
              indirectPeriodPenalty.setId((long) id);
              id++;
              indirectPeriodPenalty.setPeriodPenaltyType(PeriodPenaltyType.EXAM_COINCIDENCE);
              indirectPeriodPenalty.setLeftSideTopic(leftTopic);
              indirectPeriodPenalty.setRightSideTopic(rightTopic);
              periodPenaltyList.add(indirectPeriodPenalty);
              boolean added =
                  coincidenceMap.get(leftTopic).add(rightTopic)
                      && coincidenceMap.get(rightTopic).add(leftTopic);
              if (!added) {
                throw new IllegalStateException(
                    "The periodPenalty ("
                        + indirectPeriodPenalty
                        + ") for leftTopic ("
                        + leftTopic
                        + ") and rightTopic ("
                        + rightTopic
                        + ") was not successfully added twice.");
              }
            }
          }
        }
      }
      // createIndirectPeriodPenalties of type AFTER
      for (Map.Entry<Topic, Set<Topic>> entry : afterMap.entrySet()) {
        Topic leftTopic = entry.getKey();
        Set<Topic> afterLeftSet = entry.getValue();
        Queue<Topic> queue = new LinkedList<Topic>();
        for (Topic topic : afterMap.get(leftTopic)) {
          queue.add(topic);
          queue.addAll(coincidenceMap.get(topic));
        }
        while (!queue.isEmpty()) {
          Topic rightTopic = queue.poll();
          if (!afterLeftSet.contains(rightTopic)) {
            PeriodPenalty indirectPeriodPenalty = new PeriodPenalty();
            indirectPeriodPenalty.setId((long) id);
            id++;
            indirectPeriodPenalty.setPeriodPenaltyType(PeriodPenaltyType.AFTER);
            indirectPeriodPenalty.setLeftSideTopic(leftTopic);
            indirectPeriodPenalty.setRightSideTopic(rightTopic);
            periodPenaltyList.add(indirectPeriodPenalty);
            boolean added = afterMap.get(leftTopic).add(rightTopic);
            if (!added) {
              throw new IllegalStateException(
                  "The periodPenalty ("
                      + indirectPeriodPenalty
                      + ") for leftTopic ("
                      + leftTopic
                      + ") and rightTopic ("
                      + rightTopic
                      + ") was not successfully added.");
            }
          }
          for (Topic topic : afterMap.get(rightTopic)) {
            queue.add(topic);
            queue.addAll(coincidenceMap.get(topic));
          }
        }
      }
      examination.setPeriodPenaltyList(periodPenaltyList);
    }