Following task: I'm given a timetable containing several classes that have a weekday, as well as an hour at which they start and end. Also, they have a room assigned and are part of a curriculum for one (or more) majors.
The data is provided in a XML file. The schema of said XML file is provided via an XSD file. I use JAXB to create classes out of the XSD file, and to unmarshal data from the XML file.
Within a given timetable, I have to find scheduling conflicts. A scheduling conflict is defined as either
- two classes taking place in the same room at overlapping times, or
- two classes that both need to be visited for the same major at overlapping times.
Two classes overlapping that take place in different rooms and are not compulsory for the same major don't count as a scheduling conflict.
This is my result. My program output works as desired. But I'd be grateful for every hint I get, in particular regarding code presentation and standards, but also functionally if you can identify room for improvement. I'd be grateful for every hint I can get.
timetable.xml
<?xml version="1.0" encoding="UTF-8"?>
<university>
<lectures>
<lecture>
<id>l-0</id>
<name>Calculus 101</name>
<roombookings>
<booking>
<room>Audimax</room>
<weekday>Mon</weekday>
<startTime>10:00:00</startTime>
<endTime>12:00:00</endTime>
</booking>
<booking>
<room>Audimax</room>
<weekday>Wed</weekday>
<startTime>10:00:00</startTime>
<endTime>14:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-1</id>
<name>Counting 101</name>
<roombookings>
<booking>
<room>Audimax</room>
<weekday>Mon</weekday>
<startTime>15:00:00</startTime>
<endTime>17:00:00</endTime>
</booking>
<booking>
<room>Audimax</room>
<weekday>Fri</weekday>
<startTime>10:00:00</startTime>
<endTime>12:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-2</id>
<name>Tricky Problems 101</name>
<roombookings>
<booking>
<room>Room A</room>
<weekday>Tue</weekday>
<startTime>09:00:00</startTime>
<endTime>11:00:00</endTime>
</booking>
<booking>
<room>Room B</room>
<weekday>Thur</weekday>
<startTime>10:00:00</startTime>
<endTime>12:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-3</id>
<name>Timetabling 101</name>
<roombookings>
<booking>
<room>Room B</room>
<weekday>Thur</weekday>
<startTime>14:00:00</startTime>
<endTime>17:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-4</id>
<name>Computer Stuff 101</name>
<roombookings>
<booking>
<room>Room A</room>
<weekday>Tue</weekday>
<startTime>12:00:00</startTime>
<endTime>14:00:00</endTime>
</booking>
<booking>
<room>Room B</room>
<weekday>Wed</weekday>
<startTime>16:00:00</startTime>
<endTime>19:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-5</id>
<name>Cool Numbers 101</name>
<roombookings>
<booking>
<room>Room A</room>
<weekday>Thur</weekday>
<startTime>08:00:00</startTime>
<endTime>12:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-6</id>
<name>Quantum Leaps 101</name>
<roombookings>
<booking>
<room>Room A</room>
<weekday>Mon</weekday>
<startTime>14:00:00</startTime>
<endTime>18:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-7</id>
<name>Tiny Stuff 101</name>
<roombookings>
<booking>
<room>Room A</room>
<weekday>Tue</weekday>
<startTime>10:00:00</startTime>
<endTime>13:00:00</endTime>
</booking>
<booking>
<room>Room B</room>
<weekday>Fri</weekday>
<startTime>11:00:00</startTime>
<endTime>13:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-8</id>
<name>Big Stuff 101</name>
<roombookings>
<booking>
<room>Room B</room>
<weekday>Tue</weekday>
<startTime>13:00:00</startTime>
<endTime>15:00:00</endTime>
</booking>
<booking>
<room>Room B</room>
<weekday>Wed</weekday>
<startTime>16:00:00</startTime>
<endTime>18:00:00</endTime>
</booking>
</roombookings>
</lecture>
<lecture>
<id>l-9</id>
<name>Stars and Stuff 101</name>
<roombookings>
<booking>
<room>Room B</room>
<weekday>Wed</weekday>
<startTime>15:00:00</startTime>
<endTime>17:00:00</endTime>
</booking>
</roombookings>
</lecture>
</lectures>
<curricula>
<curriculum>
<name>Maths</name>
<lecture>l-0</lecture>
<lecture>l-1</lecture>
<lecture>l-2</lecture>
<lecture>l-3</lecture>
<lecture>l-4</lecture>
<lecture>l-5</lecture>
</curriculum>
<curriculum>
<name>Physics</name>
<lecture>l-0</lecture>
<lecture>l-1</lecture>
<lecture>l-6</lecture>
<lecture>l-7</lecture>
<lecture>l-8</lecture>
<lecture>l-9</lecture>
</curriculum>
</curricula>
</university>
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.xml.bind.JAXBException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
private static String ROOMCONFLICT_MSG = """
Course with ID %s and course with ID %s have a scheduling conflict for room %s
on %s %n""";
private static String PROGRAMCONFLICT_MSG = """
Course with ID %s and course with ID %s have a scheduling conflict for program %s
on %s %n""";
public static void main(String[] args) throws FileNotFoundException, JAXBException {
SpringApplication.run(Application.class, args);
List<ClassBooking> cbList =
UniversityFileParser.parseUniversityFile(
"./src/main/resources/timetable.xml");
ScheduleConflictDiscovery.findSchedulingConflicts(cbList);
resultPresentation();
}
private static void resultPresentation() {
if (!ScheduleConflictDiscovery.getRoomConflicts().isEmpty()) {
System.out.printf("There were %d room conflicts discovered: %n",
ScheduleConflictDiscovery.getRoomConflicts().size());
}
ScheduleConflictDiscovery.getRoomConflicts().forEach(
st -> printRoomConflict(st));
if (!ScheduleConflictDiscovery.getRoomConflicts().isEmpty()) {
System.out.printf("%nThere were %d program conflicts discovered: %n",
ScheduleConflictDiscovery.getProgramConflicts().size());
}
ScheduleConflictDiscovery.getProgramConflicts().forEach(
st -> printProgramConflict(st));
}
private static void printRoomConflict(List<ClassBooking> cbList) {
System.out.printf(ROOMCONFLICT_MSG, cbList.get(0).getId(), cbList.get(1).getId(),
cbList.get(0).getRoom(), cbList.get(0).getWeekday().toString());
}
private static void printProgramConflict(List<ClassBooking> cbList) {
String conflictingMajor = getConflictingMajor(cbList.get(0), cbList.get(1));
System.out.printf(PROGRAMCONFLICT_MSG, cbList.get(0).getId(),
cbList.get(1).getId(), conflictingMajor,
cbList.get(0).getWeekday().toString());
}
private static String getConflictingMajor(ClassBooking cb1, ClassBooking cb2) {
Set<String> copyCurriculum1 = new HashSet<>(cb1.getInCurriculum());
copyCurriculum1.retainAll(cb2.getInCurriculum());
return copyCurriculum1.stream().findFirst().get();
}
}
@Component
public class UniversityFileParser {
private static JAXBContext jaxbContext;
private static Map<String,List<String>> curriculumMap = new HashMap<>();
static final DateTimeFormatter WEEKDAYFORMATTER = DateTimeFormatter.ofPattern("E", Locale.US);
private UniversityFileParser() throws JAXBException {
jaxbContext = JAXBContext.newInstance(University.class);
}
public static List<ClassBooking> parseUniversityFile(String fileURL)
throws FileNotFoundException, JAXBException {
University university = (University) JAXBIntrospector.getValue(
jaxbContext.createUnmarshaller().unmarshal(new FileReader(fileURL)));
parseCurriculum(university);
return parseLectures(university);
}
private static void parseCurriculum(University university) {
for (Curriculum curr: university.getCurricula().getCurriculum()){
List<Lecture> newCurriculum =
curr.getLecture().stream().map(entry -> (Lecture) entry.getValue())
.toList();
List<String> newCurriculumString = newCurriculum.stream().
map(Lecture::getId).toList();
curriculumMap.put(curr.getName(), newCurriculumString);
}
}
private static List<ClassBooking> parseLectures(University university) {
List<List<ClassBooking>> cbList = university.getLectures().getLecture()
.stream().map(lecture -> parseBookings(lecture.getId(),
lecture.getRoombookings())).toList();
return cbList.stream().flatMap(Collection::stream).toList();
}
private static List<ClassBooking> parseBookings(String id,
Roombookings roomBookings) {
List<ClassBooking> classBookings = new ArrayList<>();
for (Booking b: roomBookings.getBooking()) {
ClassBooking cb = new ClassBooking(id,b.getRoom(),
getWeekday(b.getWeekday()),
LocalTime.of(b.getStartTime().getHour(), 0),
LocalTime.of(b.getEndTime().getHour(), 0),
addPrograms(id));
classBookings.add(cb);
}
return classBookings;
}
private static Set<String> addPrograms(String courseID) {
Set<String> programSet = new HashSet<>();
for (Entry<String, List<String>> degree: curriculumMap.entrySet()) {
if (degree.getValue().contains(courseID)) {
programSet.add(degree.getKey());
}
}
return programSet;
}
private static DayOfWeek getWeekday(String weekday) {
if ("Thur".equals(weekday)) {
return DayOfWeek.THURSDAY;
}
return DayOfWeek.from(WEEKDAYFORMATTER.parse(weekday));
}
}
private static Set<List<ClassBooking>> roomConflicts;
private static Set<List<ClassBooking>> programConflicts;
public static void findSchedulingConflicts(List<ClassBooking> cbList) {
roomConflicts = new HashSet<>();
programConflicts = new HashSet<>();
findOverlaps(cbList);
}
private static void findOverlaps(List<ClassBooking> cbList) {
for (int i = 0;i<cbList.size();i++) {
ClassBooking currCB = cbList.get(i);
List<ClassBooking> overlaps = IntStream.range(i + 1, cbList.size())
.filter(j -> hasOverLap(currCB, cbList.get(j)))
.mapToObj(cbList::get).toList();
findConflicts(currCB, overlaps);
}
}
private static void findConflicts(ClassBooking currentCB,
List<ClassBooking> overlaps) {
for (ClassBooking cb: overlaps) {
if (currentCB.getRoom().equals(cb.getRoom())) {
List<ClassBooking> cbSet = List.of(currentCB, cb);
roomConflicts.add(cbSet);
}
Set<String> currMajors = new HashSet<>(currentCB.getInCurriculum());
currMajors.retainAll(cb.getInCurriculum());
if (!currMajors.isEmpty()) {
List<ClassBooking> cbSet = List.of(currentCB, cb);
programConflicts.add(cbSet);
}
}
}
private static boolean hasOverLap(ClassBooking cb1, ClassBooking cb2) {
return (cb1.getWeekday() == cb2.getWeekday()) &&
(((cb1.getBeginTime().isBefore(cb2.getBeginTime()) ||
cb1.getBeginTime().equals(cb2.getBeginTime())) &&
cb1.getEndTime().isAfter(cb2.getBeginTime())) ||
((cb1.getBeginTime().isAfter(cb2.getBeginTime()) ||
cb1.getBeginTime().equals(cb2.getBeginTime())) &&
cb1.getBeginTime().isBefore(cb2.getEndTime())));
}
public static Set<List<ClassBooking>> getRoomConflicts() {
return roomConflicts;
}
public static Set<List<ClassBooking>> getProgramConflicts() {
return programConflicts;
}
}
private String id;
private String room;
private DayOfWeek weekday;
private LocalTime beginTime;
private LocalTime endTime;
private Set<String> inCurriculum;
public ClassBooking(String id, String room, DayOfWeek weekday,
LocalTime beginTime, LocalTime endTime, Set<String> inCurriculum) {
super();
this.id = id;
this.room = room;
this.weekday = weekday;
this.beginTime = beginTime;
this.endTime = endTime;
this.inCurriculum = inCurriculum;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRoom() {
return room;
}
public void setRoom(String room) {
this.room = room;
}
public DayOfWeek getWeekday() {
return weekday;
}
public void setWeekday(DayOfWeek weekday) {
this.weekday = weekday;
}
public LocalTime getBeginTime() {
return beginTime;
}
public void setBeginTime(LocalTime beginTime) {
this.beginTime = beginTime;
}
public LocalTime getEndTime() {
return endTime;
}
public void setEndTime(LocalTime endTime) {
this.endTime = endTime;
}
public Set<String> getInCurriculum() {
return inCurriculum;
}
public void setInCurriculum(Set<String> inCurriculum) {
this.inCurriculum = inCurriculum;
}
@Override
public int hashCode() {
return Objects.hash(beginTime, endTime, id, inCurriculum, room, weekday);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ClassBooking other = (ClassBooking) obj;
return Objects.equals(beginTime, other.beginTime) && Objects.equals(endTime, other.endTime)
&& Objects.equals(id, other.id) && Objects.equals(inCurriculum, other.inCurriculum)
&& Objects.equals(room, other.room) && weekday == other.weekday;
}
@Override
public String toString() {
return "ClassBooking [id=" + id + ", room=" + room + ", weekday=" + weekday + ", beginTime=" + beginTime
+ ", endTime=" + endTime + ", inCurriculum=" + inCurriculum + "]";
}
}```