Skip to content

Commit b9998fe

Browse files
committed
merge branch 'test/course-section-block' into develop
2 parents e039d69 + 940beaf commit b9998fe

File tree

9 files changed

+233
-23
lines changed

9 files changed

+233
-23
lines changed

ucst-core/src/main/java/com/dvf/ucst/core/courseutils/Course.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,12 +280,22 @@ private CourseSection(final Element sectionElement) throws
280280
SecXml.OPTIONAL_WAITLIST_FLAG_ATTR.getXmlConstantValue()
281281
);
282282

283+
// parse out [CourseSectionBlock]s.
283284
final List<Element> blockElements = XmlUtils.getChildElementsByTagName(
284285
sectionElement, CourseSectionBlock.Xml.BLOCK_TAG
285286
);
286287
final Set<CourseSectionBlock> blocks = new HashSet<>(blockElements.size());
287288
for (final Element blockElement : blockElements) {
288-
blocks.add(new CourseSectionBlock(blockElement));
289+
final CourseSectionBlock blockObject = new CourseSectionBlock(blockElement);
290+
blocks.add(blockObject);
291+
}
292+
try {
293+
CourseSectionBlock.InternalConflictException.checkForConflicts(blocks);
294+
} catch (final CourseSectionBlock.InternalConflictException e) {
295+
throw new MalformedXmlDataException("Sections were verified not to have"
296+
+ " scheduling conflicts during fetching from UBC's registration"
297+
+ " site. Perhaps the user tampered with the xml files?", e
298+
);
289299
}
290300
this.blocks = Collections.unmodifiableSet(blocks);
291301
}

ucst-core/src/main/java/com/dvf/ucst/core/courseutils/CourseSectionBlock.java

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package com.dvf.ucst.core.courseutils;
22

33
import com.dvf.ucst.core.courseutils.UbcTimeUtils.BlockTime;
4-
import com.dvf.ucst.core.spider.CourseWip;
5-
import com.dvf.ucst.utils.general.WorkInProgress;
64
import com.dvf.ucst.utils.xml.MalformedXmlDataException;
75
import com.dvf.ucst.utils.xml.XmlUtils;
86
import org.w3c.dom.Attr;
97
import org.w3c.dom.Element;
108

9+
import java.util.Collections;
10+
import java.util.HashSet;
11+
import java.util.Set;
1112
import java.util.function.Function;
1213

14+
import static com.dvf.ucst.core.spider.CourseWip.CourseSectionWip.*;
15+
1316
/**
1417
* One of several blocks (typically 2 or 3) that describe
1518
* specific meeting places and times for a [CourseSection].
@@ -69,10 +72,10 @@ public BlockTime getEndTime() {
6972
return timeEnclosure.end;
7073
}
7174

72-
public static Element createXmlOfWorkInProgress(
75+
static Element createXmlOfWorkInProgress(
7376
final Function<XmlUtils.XmlConstant, Element> elementSupplier,
74-
final CourseWip.CourseSectionWip.CourseSectionBlockWip wip
75-
) throws WorkInProgress.IncompleteWipException, IllegalTimeEnclosureException {
77+
final CourseSectionBlockWip wip
78+
) throws IncompleteWipException, IllegalTimeEnclosureException {
7679
final Element blockElement = elementSupplier.apply(Xml.BLOCK_TAG);
7780
blockElement.setAttribute(
7881
Xml.DAY_OF_WEEK_ATTR.getXmlConstantValue(),
@@ -97,8 +100,75 @@ public static Element createXmlOfWorkInProgress(
97100
return blockElement;
98101
}
99102

103+
/**
104+
* @param elementSupplier A supplier of [Element]s with a [Document] anchor that
105+
* elements of the returned collection of [Elements] will eventually be added to.
106+
* @param wips A collection of [CourseSectionBlockWip]s that should be complete.
107+
* @return A collection of [Element]s representing [wips] through xml.
108+
* @throws IncompleteWipException If an element of [wips] did not contain all the
109+
* required information for this method to perform its function.
110+
* @throws IllegalTimeEnclosureException See [BlockTimeEnclosure].
111+
* @throws InternalConflictException If any of the provided [CourseSectionBlockWip]s
112+
* have a schedule conflict between them.
113+
*/
114+
public static Set<Element> createXmlOfWorksInProgress(
115+
final Function<XmlUtils.XmlConstant, Element> elementSupplier,
116+
final Set<CourseSectionBlockWip> wips
117+
) throws IncompleteWipException, IllegalTimeEnclosureException, InternalConflictException {
118+
// map the wips to xml elements:
119+
final Set<Element> blockElements = new HashSet<>();
120+
for (final CourseSectionBlockWip wip : wips) {
121+
blockElements.add(createXmlOfWorkInProgress(elementSupplier, wip));
122+
}
123+
124+
{ // check if the section block wips have any conflicts with each other:
125+
final Set<CourseSectionBlock> tempBlockObjects = new HashSet<>(blockElements.size());
126+
for (final Element blockElement : blockElements) {
127+
try {
128+
tempBlockObjects.add(new CourseSectionBlock(blockElement));
129+
} catch (MalformedXmlDataException e) {
130+
// this shouldn't happen
131+
throw new RuntimeException("Something is either wrong with"
132+
+ " ::createXmlOfWorkInProgress or the xml constructor", e
133+
);
134+
}
135+
}
136+
InternalConflictException.checkForConflicts(tempBlockObjects);
137+
}
138+
return blockElements;
139+
}
140+
100141

142+
/**
143+
* Thrown when some source of a collection of coexisting [CourseSectionBLock]s
144+
* contains blocks that conflict with each other.
145+
*/
146+
public static final class InternalConflictException extends Exception {
147+
// meirl
148+
private InternalConflictException(final CourseSectionBlock block1, final CourseSectionBlock block2) {
149+
super(String.format("The two blocks %s and %s cannot coexist"
150+
+ " because they conflict with one another",
151+
block1, block2
152+
));
153+
assert block1.overlapsWith(block2) : "Alright. who did this?";
154+
}
101155

156+
static void checkForConflicts(final Set<CourseSectionBlock> blocks) throws InternalConflictException {
157+
final Set<CourseSectionBlock> conflictFreeAccumulator = new HashSet<>();
158+
for (final CourseSectionBlock block : Collections.unmodifiableSet(blocks)) {
159+
for (final CourseSectionBlock addedBlock : conflictFreeAccumulator) {
160+
if (block.overlapsWith(addedBlock)) {
161+
throw new InternalConflictException(block, addedBlock);
162+
}
163+
}
164+
conflictFreeAccumulator.add(block);
165+
}
166+
}
167+
}
168+
169+
/**
170+
* A begin and end time with non-zero positive duration.
171+
*/
102172
static final class BlockTimeEnclosure {
103173

104174
private final BlockTime begin;

ucst-core/src/main/java/com/dvf/ucst/core/spider/CourseSectionSpider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public static CourseSectionWip fetchCourseSectionFromWeb(final String courseSect
2323
private static <T extends CourseSectionWip> T makeNonLectureSectionWip(final Element sectionDom, final T host) {
2424
// work goes here.
2525

26+
// see CourseSectionBlock::createXmlOfWorksInProgress
27+
2628
return host;
2729
}
2830

ucst-core/src/main/java/com/dvf/ucst/core/spider/CourseWip.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ CourseWip setDescriptionString(String descriptionString) {
4949
}
5050

5151
CourseWip setLectureSections(Set<CourseLectureSectionWip> lectureSections) {
52-
this.lectureSections = lectureSections;
52+
this.lectureSections = Collections.unmodifiableSet(lectureSections);
5353
return this;
5454
}
5555

5656
CourseWip setLabSections(Set<CourseSectionWip> labSections) {
57-
this.labSections = labSections;
57+
this.labSections = Collections.unmodifiableSet(labSections);
5858
return this;
5959
}
6060

6161
CourseWip setTutorialSections(Set<CourseSectionWip> tutorialSections) {
62-
this.tutorialSections = tutorialSections;
62+
this.tutorialSections = Collections.unmodifiableSet(tutorialSections);
6363
return this;
6464
}
6565

@@ -157,7 +157,7 @@ public CourseSectionWip setWaitlist(Boolean waitlist) {
157157
}
158158

159159
final CourseSectionWip setBlocks(Set<CourseSectionBlockWip> blocks) {
160-
this.blocks = blocks;
160+
this.blocks = Collections.unmodifiableSet(blocks);
161161
return this;
162162
}
163163

@@ -215,12 +215,12 @@ public static final class CourseLectureSectionWip extends CourseSectionWip {
215215
private Set<String> requiredTutorialOptionIdTokens;
216216

217217
CourseLectureSectionWip setRequiredLabOptionIdTokens(Set<String> requiredLabOptionIdTokens) {
218-
this.requiredLabOptionIdTokens = requiredLabOptionIdTokens;
218+
this.requiredLabOptionIdTokens = Collections.unmodifiableSet(requiredLabOptionIdTokens);
219219
return this;
220220
}
221221

222222
CourseLectureSectionWip setRequiredTutorialSectionOptions(Set<String> requiredTutorialOptionIdTokens) {
223-
this.requiredTutorialOptionIdTokens = requiredTutorialOptionIdTokens;
223+
this.requiredTutorialOptionIdTokens = Collections.unmodifiableSet(requiredTutorialOptionIdTokens);
224224
return this;
225225
}
226226

ucst-core/src/test/java/com/dvf/ucst/core/courseutils/CourseSectionBlockTest.java

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@
33
import com.dvf.ucst.core.courseutils.CourseSectionBlock.BlockTimeEnclosure;
44
import com.dvf.ucst.core.courseutils.CourseSectionBlock.IllegalTimeEnclosureException;
55
import com.dvf.ucst.core.courseutils.UbcTimeUtils.BlockTime;
6+
import com.dvf.ucst.core.spider.CourseWip.CourseSectionWip.CourseSectionBlockWip;
7+
import com.dvf.ucst.utils.general.WorkInProgress;
8+
import com.dvf.ucst.utils.xml.MalformedXmlDataException;
9+
import com.dvf.ucst.utils.xml.XmlIoUtils;
10+
import org.junit.jupiter.api.Assertions;
11+
import org.junit.jupiter.api.Nested;
612
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.function.ThrowingSupplier;
14+
import org.w3c.dom.Document;
15+
import org.w3c.dom.Element;
716

17+
import javax.xml.transform.TransformerException;
818
import java.util.ArrayList;
919
import java.util.List;
1020
import java.util.Map;
@@ -15,10 +25,91 @@
1525

1626
class CourseSectionBlockTest {
1727

28+
@Test
29+
void fromXml() {
30+
31+
}
32+
33+
// does not add element to doc.
34+
private Element createBasicBlockXml(final Document doc) {
35+
final CourseSectionBlockWip wip = new CourseSectionBlockWip()
36+
.setBeginTime(T0800)
37+
.setEndTime(T0900)
38+
.setRepetitionType(CourseSectionBlock.BlockRepetition.EVERY_WEEK)
39+
.setWeekDay(CourseUtils.WeekDay.MONDAY);
40+
try {
41+
return CourseSectionBlock.createXmlOfWorkInProgress(
42+
tagName -> doc.createElement(tagName.getXmlConstantValue()),
43+
wip
44+
);
45+
} catch (WorkInProgress.IncompleteWipException | IllegalTimeEnclosureException e) {
46+
fail("Encountered unexpected " + e.getClass() + " exception");
47+
return null;
48+
}
49+
}
50+
51+
52+
53+
/**
54+
*
55+
*/
56+
@Nested
57+
final class CourseSectionBlockWipTest {
58+
59+
@Test
60+
void makeWip() {
61+
final CourseSectionBlockWip blockWip = new CourseSectionBlockWip()
62+
.setBeginTime(T0800)
63+
.setEndTime(T0900)
64+
.setRepetitionType(CourseSectionBlock.BlockRepetition.EVERY_WEEK)
65+
.setWeekDay(CourseUtils.WeekDay.MONDAY);
66+
final Set<ThrowingSupplier<Object>> getters = Set.of(
67+
blockWip::getBeginTime,
68+
blockWip::getEndTime,
69+
blockWip::getRepetitionType,
70+
blockWip::getWeekDay
71+
);
72+
getters.forEach(Assertions::assertDoesNotThrow);
73+
}
74+
75+
@Test
76+
void assertThrowsIncomplete() {
77+
// make a block wip and don't populate its fields:
78+
final CourseSectionBlockWip blockWip = new CourseSectionBlockWip();
79+
final Set<ThrowingSupplier<Object>> getters = Set.of(
80+
blockWip::getBeginTime,
81+
blockWip::getEndTime,
82+
blockWip::getRepetitionType,
83+
blockWip::getWeekDay
84+
);
85+
getters.forEach(getter -> assertThrows(
86+
WorkInProgress.IncompleteWipException.class,
87+
getter::get
88+
));
89+
}
90+
91+
@Test
92+
void toXml() {
93+
final Document doc = XmlIoUtils.createNewXmlDocument();
94+
doc.appendChild(createBasicBlockXml(doc));
95+
try {
96+
assertEquals(
97+
"<Block begin=\"08:00\" day=\"mon\" end=\"09:00\"/>", // "\r\n"
98+
XmlIoUtils.printNodeToString(doc).trim()
99+
);
100+
} catch (final TransformerException e) {
101+
fail("Unexpected error serializing document to string");
102+
}
103+
}
104+
}
105+
106+
107+
18108
/**
19109
* Tests for the private class [BlockTimeEnclosure]
20110
*/
21-
static final class BlockTimeEnclosureTest {
111+
@Nested
112+
final class BlockTimeEnclosureTest {
22113

23114
@Test
24115
void assertNoOverlaps() {
@@ -115,7 +206,7 @@ void assertIllegalEnclosure() {
115206
}
116207

117208
// for sake of brevity:
118-
private static BlockTimeEnclosure bte(
209+
private BlockTimeEnclosure bte(
119210
final BlockTime begin,
120211
final BlockTime end
121212
) throws IllegalTimeEnclosureException {

ucst-utils/src/main/java/com/dvf/ucst/utils/general/WorkInProgress.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,28 @@
33
/**
44
* Ain't nobody here but us docs.
55
*
6+
*
67
* Implementations of this interface should have the following properties:
8+
*
79
* - are located in the same package as whoever will call the setters (assemble the WIP)
10+
* - unless necessary, should not provide or implement any constructors
11+
*
812
* - all fields are non-final and private
9-
* - all fields have public getters, collections must be wrapped as unmodifiable
13+
* - no primitive fields - box them with their respective objects
14+
* - fields may have default values as long is it is clearly documented in the class doc
15+
*
16+
* - all fields have public getters, collections must be wrapped as unmodifiable (even if redundant)
1017
* - getters should throw a [IncompleteWipException] if a property is known to be incomplete
18+
* - if a getter's field has a default value, it may choose to force the setter to use the
19+
* default value if the input would qualify as invalid (Ex. null), and forgo the exception
20+
* in the getter.
21+
*
1122
* - all fields have package-private setters that return the instance the setter was called off of
12-
* - unless necessary, should not provide or implement any constructors
23+
* - unless absolutely necessary, setters must wrap input collections as unmodifiable before assigning to fields
24+
* - setters may perform validation and throw exceptions as long as the validation does not
25+
* depend on other fields with setters (to prevent user of setters from having to write
26+
* ugly code that performs the same validation externally).
27+
*
1328
*/
1429
public interface WorkInProgress {
1530

@@ -22,6 +37,10 @@ public IncompleteWipException(final String message) {
2237
super(message);
2338
}
2439

40+
public IncompleteWipException(final String message, Exception e) {
41+
super(message, e);
42+
}
43+
2544
/**
2645
* @param wip the [WorkInProgress] considered to be missing the property
2746
* bound to a declared field of its own by the name [fieldName].

ucst-utils/src/main/java/com/dvf/ucst/utils/xml/MalformedXmlDataException.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@
88
*/
99
public class MalformedXmlDataException extends Exception {
1010

11-
public MalformedXmlDataException(String message) {
11+
public MalformedXmlDataException(final String message) {
1212
super(message);
1313
}
1414

15-
public MalformedXmlDataException(Exception rootCause) {
15+
public MalformedXmlDataException(final Exception rootCause) {
1616
super(rootCause);
1717
}
1818

19+
public MalformedXmlDataException(final String message, final Exception rootCause) {
20+
super(message, rootCause);
21+
}
22+
1923
public static MalformedXmlDataException noSuchUniqueChildElement(final Element host, final String tagName) {
2024
return new MalformedXmlDataException(String.format("A unique %s by the tag name"
2125
+ " \"%s\" could not be found as a direct child of the host %s",

0 commit comments

Comments
 (0)