OOP2

Major refactor of code + add info about design choices

Author
Vngngdn
Date
Dec. 16, 2016, 2:38 a.m.
Hash
3c3f7b0921297dda9295fdf1db792c697d92e189
Parent
f68e848416b19091f2f20166cbd19ebec9fa4bc3
Modified files
Challenge 6/Bed.java
Challenge 6/Reservation.java
Challenge 6/ReservationController.java
Challenge 6/ReservationView.java
Challenge 6/Room.java
Challenge 6/RoomController.java
Challenge 6/Window.java
Challenge 6/ontwerpkeuzes2.md

Challenge 6/Bed.java

11 additions and 10 deletions.

View changes Hide changes
1
1
import java.util.Map;
2
2
import java.util.HashMap;
3
3
4
4
/**
+
5
/**
5
6
 * Class representing a bed in the hostel.
6
7
 * Even though beds in a hostel could simply be an integer in a Room, these Beds
7
8
 * need to remember on what dates they are reserved.
8
9
 * @author Maarten Vangeneugden - 1438256
9
10
 */
10
11
public class Bed {
11
12
12
13
	/* Rationale:
13
14
	 * I know that a mapping is not necessarily the best solution for storing
14
15
	 * periods, because it doesn't offer built-in protection yadda yadda.
15
16
	 * However, that's Java's fault, as it doesn't provide a Pair<L,R> thing in
16
17
	 * the STL.
17
18
	 * The best solution is actually a collection of tuples: (Begin, End), but
18
19
	 * that doesn't exist in Java, and thus requires a custom implementation.
19
20
	 * All things considered, a Map is the least bad choice.
20
21
	 */
21
22
	private Map<Date, Date> reservedPeriods;
22
23
23
24
	public Bed() {
24
25
		this.reservedPeriods = new HashMap<>();
25
26
	}
26
27
27
28
	/**
28
29
	 * Reserves this Bed for the given period.
29
30
	 * This method will mark the given period for this Bed as "reserved", which
30
31
	 * can then be cancelled or queried later.
31
32
	 * @param begin The time where the reservation begins.
32
33
	 * @param end The time where the reservation ends.
33
34
	 * @pre No parameter may be a null pointer.
34
35
	 * @pre The given period mustn't overlap with a period already marked as
35
36
	 * reserved.
36
37
	 * @pre begin must come before end.
37
38
	 * @pre begin and end mustn't be equal.
38
39
	 * @post The given period will be marked as "Reserved", and will have to be
39
40
	 * manually cancelled/removed in order to reserve again.
40
41
	 * @throws NullPointerException if any parameter is a null pointer.
41
42
	 * @throws IllegalArgumentException if begin is equal to, or comes after
42
43
	 * end.
43
44
	 * @throws DateTimeException if the given period overlaps with an already
44
45
	 * reserved period.
45
46
	 */
46
47
	public void reservePeriod(Date begin, Date end) {
47
48
		if(!begin.before(end)) {
48
49
			throw IllegalArgumentException("The begin date occurs after the end date.");
49
-
		}
+
50
		}
50
51
		if(!this.isFree(begin, end)) {
51
52
			throw DateTimeException("This period overlaps with a reserved period.");
52
-
		}
+
53
		}
53
54
		// Contract validated, execute method
54
55
		this.reservedPeriods.put(begin, end);
55
56
	}
56
57
57
58
	/**
58
59
	 * Remove a previous registration from this Bed.
59
60
	 * This method will remove/cancel the given reservation period from the
60
61
	 * Bed, opening it up for reservation again.
61
62
	 * @param begin The time where the reservation begins.
62
63
	 * @param end The time where the reservation ends.
63
64
	 * @pre No parameter may be a null pointer.
64
65
	 * @pre The given period must already be marked as "Reserved" in this Bed.
65
66
	 * @pre begin must come before end.
66
67
	 * @pre begin and end mustn't be equal.
67
68
	 * @post The given period will lose its "Reserved" mark, allowing the period
68
69
	 * to be reserved again.
69
70
	 * @throws NullPointerException if any parameter is a null pointer.
70
71
	 * @throws IllegalArgumentException if begin is equal to, or comes after
71
72
	 * end; or, if the given period is not reserved.
72
73
	 */
73
74
	public void removeReservationPeriod(Date begin, Date end) {
74
75
		if(!begin.before(end)) {
75
76
			throw IllegalArgumentException("The begin date occurs after the end date.");
76
-
		}
+
77
		}
77
78
		if(!this.isFree(begin, end)) {
78
79
			throw DateTimeException("This period overlaps with a reserved period.");
79
-
		}
+
80
		}
80
81
		// Contract partially validated; further validation occurs while looking
81
82
		// for the reservation.
82
83
		// XXX: Check if Java correctly handles equality: 2 different Date
83
84
		// objects with the same date representation should be equal!
84
85
		boolean reservationFound = this.reservedPeriods.remove(begin, end);
85
86
		if(!reservationFound) {
86
87
			throw IllegalArgumentException("The given period was not marked as reserved.");
87
-
		}
+
88
		}
88
89
	}
89
90
		
90
91
	/**
91
92
	 * Checks whether this Bed can be reserved in the given period.
92
93
	 * Use this method whenever you need to inform yourself about any
93
94
	 * conflicting reservation period.
94
95
	 * @param begin The time where the reservation begins.
95
96
	 * @param end The time where the reservation ends.
96
97
	 * @pre No parameter must be a null pointer.
97
98
	 * @pre begin must come before end.
98
99
	 * @pre begin and end mustn't be equal.
99
100
	 * @throws NullPointerException if any parameter is a null pointer.
100
101
	 * @throws IllegalArgumentException if begin is equal to, or comes after
101
102
	 * end.
102
103
	 * @return True if the given period does not overlap with any reservation, false otherwise.
103
104
	 */
104
105
	public boolean isFree(Date begin, Date end) {
105
106
		if(!begin.before(end)) {
106
107
			throw IllegalArgumentException("The begin date occurs after the end date.");
107
-
		}
+
108
		}
108
109
		if(!this.isFree(begin, end)) {
109
110
			throw DateTimeException("This period overlaps with a reserved period.");
110
-
		}
+
111
		}
111
112
		// Contract validated, execute method
112
113
		for(Map.Entry<Date, Date> reservedPeriod: this.reservedPeriods.EntrySet()) {
113
-
			Date reservedBegin = reservedPeriod.key();
114
-
			Date reservedEnd = reservedPeriod.value();
115
-
			/* Forbidden possibilities:
+
114
			Date reservedBegin = reservedPeriod.getKey();
+
115
			Date reservedEnd = reservedPeriod.getValue();
+
116
			/* Forbidden possibilities:
116
117
			 * (A,B = reserved; X,Y = requested)
117
118
			 * X-A-Y-B / A-X-B-Y  -  Begins or ends in a reserved period
118
119
			 * X-A-B-Y  -  Complete overlapping of reserved period
119
120
			 * Allowed possibilities:
120
121
			 * A-B-X-Y / X-Y-A-B  -  No overlapping
121
122
			 */
122
123
			if((begin.after(reservedBegin) && begin.before(reservedEnd)) ||
123
124
			   (end.after(reservedBegin) && end.before(reservedEnd))) {
124
125
				// Triggered if any forbidden structure is detected
125
126
				return false;
126
127
			}
127
128
		}
128
129
		return true; // No overlapping found
129
130
	}
130
131
131
132
	/**
132
133
	 * Checks whether this Bed has open reservation periods.
133
134
	 * @return True if this Bed has reserved periods, false otherwise.
134
135
	 */
135
136
	public boolean hasReservations() {
136
137
		return !this.reservedPeriods.isEmpty();
137
138
	}
138
139
}
139
140

Challenge 6/Reservation.java

63 additions and 41 deletions.

View changes Hide changes
1
1
import java.util.HashSet;
2
2
import java.util.Date;
3
3
4
4
/**
5
5
 * Represents a reservation in a hostel.
6
6
 * Reservation is a simple class that allows one to store reservation
7
7
 * information, request it when necessary, and so on.
8
8
 * Certain methods are provided for interaction, which use contracts to assert
9
9
 * proper functioning.
10
10
 *
11
11
 * Note: There is a form of redundancy in this class.
12
12
 * Reservation holds a Set of Beds, and an integer, indicating the amount of
13
13
 * people that are in this Reservation.
14
14
 * Normally, the amount of people in the Reservation is determined by the amount
15
15
 * of reserved Beds, because the amount of Beds implied the amount of people to
16
16
 * facilitate.
17
17
 * Because of the design of this program, I've weighed the advantages and
18
18
 * disadvantages of holding this implication, of using a seperate member
19
19
 * explicitely indicating the amount of people.
20
20
 *
21
21
 * I've chosen the latter option. It scales better in terms of weird
22
22
 * afterthoughts ("I also want to make people sleep on the floor"), but being
23
23
 * explicit is also a tad easier to manage. It also allows the other classes to
24
24
 * be far more decoupled from Reservation (otherwise, they'd have to check for
25
25
 * errors every time themselves, hindering cohesiveness).
26
26
 *
27
27
 * To overcome the possible difference, I've made it so that, every time, the
28
28
 * relation between the values is changed (the people in the Reservation
29
29
 * changes, for example), the class automatically checks if the values are
30
30
 * corresponding. If they're not, a warning is printed to system.out.err,
31
31
 * informing about the fact that there's a discrepancy.
32
32
 *
33
33
 * I think that's the best way to overcome this problem.
34
34
 * @author Maarten Vangeneugden - 1438256
35
35
 */
36
36
public class Reservation {
37
37
38
38
	// Members
39
39
	private String groupName;
40
40
	private Date begin;
41
-
	private Date end;
42
-
	private Set<Bed> reservedBeds;
+
41
	private Date endDate;
+
42
	private Set<Bed> reservedBeds;
43
43
	private int people; // The amount of people in this Reservation
44
44
	private int reservationID;
45
45
	private String roomType; // Room type requested by group
46
46
	private Set<String> roomFacilities; // Requested room facilities
47
47
	private Set<Date> breakfastDays; // Set of days the group wants breakfast
48
-
	// Controllers
+
48
	// Controllers
49
49
	private RoomController roomController;
50
50
	private ReservationController reservationController;
51
51
52
52
	/**
53
53
	 * Create a new Reservation.
54
54
	 * Be aware about the limitations of a Reservation: it's not the
55
55
	 * Reservation's duty to check whether the provided Beds are properly
56
56
	 * reserved; take care of this accordingly.
57
57
	 * 
58
58
	 * Some information is implicit. For example: The size of the set of
59
59
	 * reserved Beds implies the amount of people in the group; no breakfast
60
60
	 * days implies no breakfast, ...
61
61
	 * @param groupName The name of the group.
62
62
	 * @param people The amount of people that are reserving.
63
63
	 * @param begin The date that the Reservation begins.
64
-
	 * @param end The date that the Reservation ends.
65
-
	 * @param reservedBeds The set of Beds reserved and assigned to this
+
64
	 * @param endDate The date that the Reservation ends.
+
65
	 * @param reservedBeds The set of Beds reserved and assigned to this
66
66
	 * Reservation.
67
67
	 * @param roomType The requested room type.
68
68
	 * @param roomFacilities The set of all requested room facilities.
69
69
	 * @param breakfastDays A set of all days that the group wants breakfast.
70
70
	 * @param reservationID An ID for this reservation, to differentiate from
71
71
	 * other Reservations.
72
72
	 * @pre No parameter must be a null pointer.
73
73
	 * @pre people must be a natural number, different from 0.
74
74
	 * @pre begin must come before end.
75
-
	 * @pre All dates in breakfastDays must fall between begin and end.
76
-
	 * @pre No string parameter may be empty.
+
75
	 * @pre All dates in breakfastDays must fall between beginDate and endDate.
+
76
	 * @pre No string parameter may be empty.
77
77
	 * @post The amount of people in the Reservation is determined by the amount
78
78
	 * of reserved Beds.
79
79
	 * @throws NullPointerException if any parameter is a null pointer.
80
80
	 * @throws IllegalArgumentException if any of the other preconditions is not
81
81
	 * met.
82
82
	 */
83
83
	public Reservation(String groupName, int people, Date begin, Date end, Set<Bed> reservedBeds, String roomType, Set<String> roomFacilities, Set<Date> breakfastDays) {
84
-
		// Contract validation:
+
84
		// Contract validation:
85
85
		if(people < 1) {
86
86
			throw IllegalArgumentException("The amount of people should be at least 1.");
87
-
		}
+
87
		}
88
88
		if(!begin.before(end)) {
89
-
			throw IllegalArgumentException("The begin date occurs after the end date.");
90
-
		}
+
89
			throw new IllegalArgumentException("The begin date occurs after the end date.");
+
90
		}
91
91
		if(groupName.isEmpty() || roomType.isEmpty()) {
92
92
			throw IllegalArgumentException("groupName and/or roomType were empty strings.");
93
-
		}
+
93
		}
94
94
		for(String roomFacility: roomFacilities) {
95
95
			if(roomFacility.isEmpty()) {
96
96
				throw IllegalArgumentException("One of the room facilities was an empty string.");
97
-
			}
+
97
			}
98
98
		}
99
99
		for(Date breakfastDay : breakfastDays) {
100
100
			if(breakfastDay.before(begin) || breakfastDay.after(end)) {
101
-
				throw IllegalArgumentException("One of the breakfast days occurs before/after the reservation period.");
102
-
			}
+
101
				throw new IllegalArgumentException("One of the breakfast days occurs before/after the reservation period.");
+
102
			}
103
103
		}
104
104
		// Contract validated, execute constructor
105
105
		this.groupName = groupName;
106
106
		this.people = people;
107
107
		this.beginDate = begin;
108
-
		this.endDate = end;
109
-
		this.reservedBeds = reservedBeds;
+
108
		this.endDate = endDate;
+
109
		this.reservedBeds = reservedBeds;
110
110
		this.reservationID = reservationID;
111
111
		this.roomType = roomType;
112
112
		this.roomFacilities = roomFacilities;
113
113
		this.breakfastDays = breakfastDays;
114
114
	}
115
115
	
116
116
	/**
117
117
	 * Construct template Reservation.
118
118
	 * Use this constructor if you need to create an empty/new Reservation. 
119
119
	 * It omits the standard restrictions (for example, the group name will be
120
120
	 * empty) to accomodate for the expected usage of this Reservation.
121
121
	 */
122
122
	public Reservation() {
123
123
		this.groupName = "";
124
124
		this.people = 1;
125
125
		this.beginDate = new Date();
126
126
		this.endDate = new Date();
127
127
		this.reservedBeds = new HashSet<>();
128
128
		this.reservationID = 0;
129
129
		this.roomType = "";
130
130
		this.roomFacilities = new HashSet<>();
131
131
		this.breakfastDays = new HashSet<>();
132
132
	}
133
133
134
134
	/**
135
135
	 * Checks whether the amount of people corresponds with the reserved Beds.
136
136
	 * Call this method whenever a change in the amount of Beds, or the amount
137
137
	 * of people is made, or, whenever you need to assure consistency.
138
138
	 *
139
139
	 * It also prints a warning to system.out.err to inform about a discrepancy,
140
140
	 * should one be found.
141
141
	 *
142
142
	 * @return True if the counts are consistent, false otherwise.
143
143
	 */
144
144
	private boolean checkPeopleCountConsistency() {
145
145
		int people = this.getPeople();
146
146
		int beds = this.getReservedBeds().size();
147
147
		if(people != beds) {
148
148
			system.out.err("There's a discrepancy in the amount of people in Reservation"+
149
-
					this.reservationID +":");
+
149
					this.reservationID +":");
150
150
			system.out.err("People: "+String.valueOf(people));
151
-
			system.out.err("Reserved Beds: "+String.valueOf(beds));
152
-
			return false;
+
151
			System.err.println("Reserved Beds: "+String.valueOf(beds));
+
152
			return false;
153
153
		}
154
154
		return true;
155
155
	}
156
156
157
157
	/**
158
158
	 * Set the group name for this Reservation.
159
159
	 * @param groupName The new group name.
160
160
	 * @pre groupName mustn't be empty, or a null pointer.
161
161
	 * @post The group name is changed to the given name.
162
162
	 * @throws NullPointerException if groupName is a null pointer.
163
163
	 * @throws IllegalArgumentException if groupName is an empty String.
164
164
	 */
165
165
	public void setGroupName(String groupName) {
166
166
		if(groupName.isEmpty())
167
167
			throw IllegalArgumentException("groupName is an empty String.");
168
-
		this.groupName = groupName;
+
168
		this.groupName = groupName;
169
169
	}
170
170
171
171
	/**
172
172
	 * Retrieve the current group name.
173
173
	 * @return The group name of this Reservation.
174
174
	 */
175
175
	public String getGroupName() {
176
176
		return this.groupName;
177
177
	}
178
178
179
179
	/**
180
180
	 * Get amount of people in this Reservation.
181
181
	 * @post A warning will be printed to system.out.err if the amount of people
182
182
	 * is inconsistent with the amount of reserved Beds.
183
183
	 * @see Reservation.checkPeopleCountConsistency
184
184
	 * @return The amount of people in this Reservation.
185
185
	 */
186
186
	public int getPeople() {
187
187
		this.checkPeopleCountConsistency();
188
188
		return this.people;
189
189
	}
190
190
191
191
	/**
192
192
	 * Set the amount of people for this Reservation.
193
193
	 * Note that this method will not notify you if the new amount of people is
194
194
	 * equal to the previous amount.
195
195
	 * 
196
196
	 * This method will print
197
197
	 * @param people The new amount of people in this Reservation.
198
198
	 * @pre people must be at least 1.
199
199
	 * @post A warning will be printed to system.out.err if the amount of people
200
200
	 * is inconsistent with the amount of reserved Beds.
201
201
	 * @post The amount of people is changed to the given value.
202
202
	 * @throws IllegalArgumentException if people is less than 1.
203
203
	 */
204
204
	public void setPeople(int people) {
205
205
		if(people < 1) {
206
206
			throw IllegalArgumentException("people must be at least 1.");
207
-
		}
+
207
		}
208
208
		this.people = people;
209
209
		this.checkPeopleCountConsistency();
210
210
	}
211
211
	/**
+
212
	/**
+
213
	 * Returns a copy of the begin Date of this Reservation.
+
214
	 * @return a copy of the begin Date of this Reservation.
+
215
	 */
+
216
	public Date getBeginDate() {
+
217
		return (Date)this.beginDate.clone();
+
218
	}
+
219
+
220
	/**
212
221
	 * Set the begin date for this Reservation.
213
222
	 * @param begin The new begin date.
214
-
	 * @pre begin mustn't be a null pointer.
215
-
	 * @pre begin must come strictly before the end date.
216
-
	 * @post The begin date is updated accordingly.
+
223
	 * @pre beginDate mustn't be a null pointer.
+
224
	 * @pre beginDate must come strictly before the end date.
+
225
	 * @post The begin date is updated accordingly.
217
226
	 * @throws NullPointerException if begin is a null pointer.
218
-
	 * @throws IllegalArgumentException if begin comes after the end date.
219
-
	 */
+
227
	 * @throws IllegalArgumentException if beginDate comes after the end date.
+
228
	 */
220
229
	public void setBegin(Date begin) {
221
-
		if(!begin.before(this.getEnd()))
222
-
			throw IllegalArgumentException("begin comes after the end date.");
223
-
		this.begin = begin;
224
-
	}
+
230
		if(!beginDate.before(this.beginDate))
+
231
			throw new IllegalArgumentException("beginDate comes after the end date.");
+
232
		this.beginDate = beginDate;
+
233
	}
225
234
226
235
	
227
-
+
236
	 * Returns a copy of the end Date of this Reservation.
+
237
	 * @return a copy of the end Date of this Reservation.
+
238
	 */
+
239
	public Date getEndDate() {
+
240
		return (Date)this.endDate.clone();
+
241
	}
+
242
228
243
	/**
229
244
	 * Set the end date for this Reservation.
230
245
	 * @param end The new end date.
231
-
	 * @pre end mustn't be a null pointer.
232
-
	 * @pre end must come strictly after the begin date.
233
-
	 * @post The end date is updated accordingly.
+
246
	 * @pre endDate mustn't be a null pointer.
+
247
	 * @pre endDate must come strictly after the begin date.
+
248
	 * @post The end date is updated accordingly.
234
249
	 * @throws NullPointerException if end is a null pointer.
235
-
	 * @throws IllegalArgumentException if end comes after the end date.
236
-
	 */
+
250
	 * @throws IllegalArgumentException if endDate comes after the begin date.
+
251
	 */
237
252
	public Date getDate() {
238
-
		return date;
239
-
	}
+
253
		if(!endDate.before(this.endDate))
+
254
			throw new IllegalArgumentException("endDate comes before the begin date.");
+
255
		this.endDate = endDate;
+
256
	}
240
257
241
258
	public void setReservedBeds(Set<Bed> reservedBeds) {
242
259
		this.reservedBeds = reservedBeds;
243
260
		this.checkPeopleCountConsistency();
244
261
	}
245
262
246
263
	public Set<Bed> getReservedBeds() {
247
264
		return reservedBeds;
248
265
		this.checkPeopleCountConsistency();
249
266
	}
250
267
251
268
	// TODO: Write documentation for all of these, even though it's all mostly
252
269
	// copy/pasting. pfffff
253
270
	public void setReservationID(int reservationID) {
254
271
		this.reservationID = reservationID;
255
272
	}
256
273
257
274
	public int getReservationID() {
258
275
		return reservationID;
259
276
	}
260
277
261
278
	public void setRoomType(String roomType) {
262
279
		this.roomType = roomType;
263
280
	}
264
281
265
282
	public String getRoomType() {
266
283
		return roomType;
267
284
	}
268
285
269
286
	public void setRoomFacilities(Set<String> roomFacilities) {
270
287
		this.roomFacilities = roomFacilities;
271
288
	}
272
289
273
290
	public Set<String> getRoomFacilities() {
274
291
		return roomFacilities;
275
292
	}
276
293
277
294
	/**
278
295
	 * Calculates the price of the Reservation, based on its current state.
279
296
	 * Price table:
280
297
	 * - 20/person/day
281
298
	 *   - 5 less in low season, 5 more in high season
282
299
	 * - 4/breakfast ordered
283
300
	 * - If room is fully booked by the group --> 10% discount
284
301
	 * @return The price in euros.
285
302
	 */
286
303
	public int getPrice() {
287
304
		int totalPrice = 0;
288
305
		// Jan - Apr: Mid
289
306
		// May - Aug: High
290
307
		// Sep - Dec: Low
291
308
		
292
309
		// Calculate bed prices
293
310
		int month = this.getDate().getMonth();
294
-
		int bedPrice = 20;
+
311
		int bedPrice = 20;
295
312
		if(month >=8) { // From September:
296
313
			bedPrice -= 5;
297
314
		} else if(month >=4) { // From May:
298
315
			bedPrice += 5;
299
316
		}
300
317
		totalPrice += (this.getReservedBeds().size() * this.getNights() * bedPrice);
301
-
		// Calculate price for breakfasts
+
318
		// allows to retrieve the days between them, because the difference is
+
319
		// expressed in milliseconds (nights = ΔDate ÷ 1000 ÷ 60 ÷ 60 ÷ 24)
+
320
		long deltaDate = this.getEndDate().getTime() - this.getBeginDate().getTime();
+
321
		long nights = deltaDate/1000/60/60/24;
+
322
		totalPrice += (this.getReservedBeds().size() * nights * bedPrice);
+
323
		// Calculate price for breakfasts
302
324
		int breakfasts = this.getBreakfastDays().length;
303
325
		totalPrice += breakfasts * this.getReservedBeds().size();
304
326
		// Check if eligible for discount
305
327
		for(Room room: roomController.getRooms()) {
306
328
			Set<Bed> roomBeds = room.getBeds();
307
329
			if(roomBeds.containsAll(this.reservedBeds)) {
308
330
				double discount = (double)totalPrice * 0.1;
309
331
				totalPrice -= (int)discount;
310
332
			}
311
333
		}
312
334
		return totalPrice;
313
335
	}
314
336
315
337
316
338
317
339
318
340
319
341
320
342
321
343
	public void setBreakfastDays(int[] breakfastDays) {
322
344
		this.breakfastDays = breakfastDays;
323
345
	}
324
346
	public int[] getBreakfastDays() {
325
347
		return this.breakfastDays;
326
348
	}
327
349
328
350
}
329
351

Challenge 6/ReservationController.java

10 additions and 8 deletions.

View changes Hide changes
1
1
import java.util.HashSet;
2
2
import java.util.Date;
3
3
4
4
/**
5
5
 * Controller class for the Reservations of this program.
6
6
 * Since this program handles a youth hostel, it's imperative that it holds a
7
7
 * number of Reservations.
8
8
 * 
9
9
 * ReservationController takes the responsibility of handling the addition,
10
10
 * cancelling, editing, ... of Reservations, and the related tasks inherent to
11
11
 * these responsibilities, such as reserving beds, generating IDs, ...
12
12
 * @author Maarten Vangeneugden - 1438256
13
13
 */
14
14
public class ReservationController {
15
15
16
16
	/* Rationale:
17
17
	 * Normally I'd put this as an interface (Set), but an interface does not
18
18
	 * inherit from Object, and thus, does not provide clone(). This is
19
19
	 * necessary, because otherwise, there's no point in having private
20
20
	 * members.
21
21
	 */
22
22
	private HashSet<Reservation> reservations; // Holds all Reservations
23
23
	private RoomController roomController;
24
24
25
25
	/**
26
26
	 * Creates the ReservationController.
27
27
	 * 
28
28
	 * WARNING: This program avoids the use of null pointers, but because of
29
29
	 * circular dependency issues, this class requires that the developer (which
30
30
	 * means YOU) set the RoomController reference member manually.
31
31
	 * Be advised that the setRoomController() method will not accept a null
32
32
	 * pointer.
33
33
	 *
34
34
	 * Once again: Set the RoomController reference manually ASAP. Failure to do
35
35
	 * so will cause this program to blow up. You've been warned.
36
36
	 * @see ReservationController.setRoomController
37
37
	 */
38
38
	public ReservationController() {
39
39
		this.reservations = new HashSet<>();
40
40
	}
41
41
42
42
	/**
43
43
	 * Returns a copy of all Reservations.
44
44
	 * Emphasis on "copy"; There is no setReservations() for a reason, using
45
45
	 * this to edit the pointer variable would omit the use of 'private'.
46
46
	 * @return A verbatim copy of all Reservations.
47
47
	 */
48
48
	@SuppressWarnings("unchecked")
49
49
	public Set<Reservation> getReservations() {
50
50
		return (HashSet<Reservation>)this.reservations.clone();
51
51
	}
52
52
53
53
	/**
54
54
	 * Generates a unique ID for a new Reservation.
55
55
	 * This method is to be called when a new Reservation is to be stored.
56
56
	 * As every Reservation carries an ID, this method searches for a unique
57
57
	 * one, and returns it.
58
58
	 * @return An integer, different from all other stored Reservation IDs.
59
59
	 */
60
60
	public int generateReservationID() {
61
61
		/* Small optimization idea:
62
62
		 * Instead of starting from 0, and incrementing until it's unique, it
63
63
		 * will take the amount of stored Reservations, and test that as an ID.
64
64
		 * This may still overlap (by removing an older Reservation), but may be
65
65
		 * much faster. Consider implemting and testing for effectiveness if
66
66
		 * generating an ID takes too long.
67
67
		 */
68
68
		int ID = 0;
69
69
		boolean isUnique = false;
70
70
		do {
71
71
			ID++;
72
72
			isUnique = true;
73
73
			for(Reservation reservation: this.reservations) {
74
74
				if(ID == reservation.getReservationID()) {
75
75
					isUnique = false;
76
76
				}
77
77
			}
78
78
		} while(!isUnique);
79
79
		// Test:
80
80
		for(Reservation reservation: this.reservations) {
81
81
			assert reservation.getReservationID() != ID : "Duplicate ID generated!";
82
82
		}
83
83
		return ID;
84
84
	}
85
85
	
86
86
	/** 
87
87
	 * Check if Reservation can be made.
88
88
	 * Call this method whenever you're planning on adding a new Reservation. It
89
89
	 * will check the provided requirements (Room facilities, for example), and
90
90
	 * return whether this Reservation can continue.
91
91
	 * @param reservation The Reservation to check for possible addition.
92
92
	 * @pre reservation mustn't be null.
93
93
	 * @throws NullPointerException if reservation is null.
94
94
	 * @return True if reservation can be added, False otherwise.
95
95
	 */
96
96
	public boolean checkReservation(Reservation reservation) {
97
97
		if(reservation == null) {
98
98
			throw NullPointerException("reservation was a null pointer.");
99
-
		}
+
99
		}
100
100
		if(this.getRoomController().getQualifiedRooms(reservation).size() == 0) {
101
-
			return null;
102
-
		}
+
101
			return false;
+
102
		}
103
103
	}
+
104
		return true;
+
105
	}
104
106
105
107
	/**
106
108
	 * Adds and confirms the reservation.
107
109
	 * By calling this method, the given reservation will be stored in the
108
110
	 * system, and the given room will be filled.
109
111
	 * You are to collect the necessary data, and assure yourself that all
110
112
	 * preconditions have been met.
111
113
	 * 
112
114
	 * The best way to accomplish this, is to only send 'sterile' Reservations
113
115
	 * as the first parameter. That is: Reservations without reserved Beds,
114
116
	 * without prior addition to the active Reservations, etc.
115
117
	 * NOTE: You must check for yourself if the Reservation can take place in
116
118
	 * this Room! If there are not enough Beds available, an exception will be
117
119
	 * thrown.
118
120
	 * @param reservation The Reservation to add.
119
121
	 * @param room The Room in which the people will reside.
120
122
	 * @pre room must have enough empty beds.
121
123
	 * @pre room must accomodate the Reservation's requirements.
122
124
	 * @pre No parameter must be null.
123
125
	 * @pre reservation mustn't already be stored in the active Reservations.
124
126
	 * @post reservation is stored in the active Reservations.
125
127
	 * @post The Beds in the provided Room are reserved for the Reservation's
126
128
	 * period.
127
129
	 * @throws IllegalArgumentException if the given Room can't fulfill the
128
130
	 * requirements of the Reservation, or occupants is less than 1.
129
131
	 * @throws NullPointerException if any parameter is a null pointer.
130
132
	 */
131
133
	public void addReservation(Reservation reservation, Room room) {
132
134
		// Contract validation. Null pointers are implicitely checked by calling
133
135
		// methods.
134
136
		if(reservation.getPeople() > room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).size())
135
137
			throw IllegalArgumentException("The given Room has not enough empty Beds for the Reservation period.");
136
-
		if(!this.getRoomController().getQualifiedRooms(reservation).contains(room))
+
138
		if(!this.getRoomController().getQualifiedRooms(reservation).contains(room))
137
139
			throw IllegalArgumentException("The given Room cannot meet all requirements of the Reservation.");
138
-
		if(this.getReservations().contains(reservation))
+
140
		if(this.getReservations().contains(reservation))
139
141
			throw IllegalArgumentException("The given Reservation was already included in the active Reservations.");
140
-
		// Contract validated
+
142
		// Contract validated
141
143
		this.reservations.add(reservation);
142
144
		Bed[] beds = room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).toArray(new Bed[1]);
143
145
		Set<Bed> reservedBeds = new HashSet<>();
144
146
		for(int i=0; i<reservation.getPeople(); i++) {
145
147
			beds[i].reservePeriod(reservation.getBegin(), reservation.getEnd());
146
148
			reservedBeds.add(beds[i]);
147
149
		}
148
150
		reservation.setReservedBeds(reservedBeds);
149
151
	}
150
152
151
153
	/**
152
154
	 * Cancels and removes the given Reservation.
153
155
	 * If you want to remove a Reservation, use this method, and provide the
154
156
	 * Reservation up for removal.
155
157
	 * This method will take care of related actions, such as releasing Beds.
156
158
	 * @param reservation The Reservation to be removed.
157
159
	 * @pre reservation mustn't be null.
158
160
	 * @pre reservation must be contained in the active Reservations.
159
161
	 * @post The Reservation is removed from the active Reservations.
160
162
	 * @post The Beds, previously reserved for this Reservation, are now
161
163
	 * released, and can be reserved for another Reservation.
162
164
	 * @throws NullPointerException if reservation is a null pointer.
163
165
	 * @throws IllegalArgumentException if reservation is not contained in the
164
166
	 * active Reservations.
165
167
	 */
166
168
	public void cancelReservation(Reservation reservation) {
167
169
		// Contract validation
168
170
		if(!this.getReservations().contains(reservation)) {
169
171
			throw IllegalArgumentException("The given Reservation was not contained in the active Reservations.");
170
-
		}
+
172
		}
171
173
		if(reservation == null) {
172
174
			throw NullPointerException();
173
-
		}
+
175
		}
174
176
		// Contract validated, execute method
175
177
		this.reservations.remove(reservation); // Remove from active Reservations
176
178
		for(Bed bed: reservation.getReservedBeds()) { // Release reserved Beds
177
179
			bed.removeReservationPeriod(reservation.getBegin(), reservation.getEnd());
178
180
		}
179
181
		// Asserting post conditions are met
180
182
		assert !this.getReservation().contains(reservation) : "The reservation is still part of the active Reservations.";
181
183
		for(Bed bed: reservation.getReservedBeds()) {
182
184
			assert bed.isFree(reservation.getBegin(), reservation.getEnd()) : "One or more of the Beds are still reserved.";
183
185
		}
184
186
	}
185
187
186
188
}
187
189

Challenge 6/ReservationView.java

7 additions and 5 deletions.

View changes Hide changes
1
1
import java.util.Set;
2
2
import java.util.HashSet;
3
3
import java.util.Date;
4
4
/**
+
5
/**
5
6
 * Creates a view screen that allows interaction with the reservation.
6
7
 * This window will place the reservation details in editable fields, so its
7
8
 * state can be adapted.
8
9
 * It then also adds a couple of buttons; add/update/cancel, with their
9
10
 * respective actions. These will then be passed to the ReservationController.
10
11
 * It will not allow to add/update a reservation if dates overlap, i.e. beds
11
12
 * can't be reserved for that period.
12
13
 * @author Maarten Vangeneugden - 1438256
13
14
 */
14
15
public class ReservationView {
15
16
16
17
	private Reservation reservation;
17
18
	private Window window;
18
19
19
20
	// Controllers for communication with the rest
20
21
	private ReservationController rc;
21
22
	private RoomController roc;
22
23
23
24
	// GUI widgets
24
25
	private JTextField nameField;
25
26
	private JSpinner amountPeopleField;
26
27
	private JTextField dateField;
27
-
	private JSpinner durationField;
+
28
	private JTextField endDateField;
+
29
	private JSpinner durationField;
28
30
	private JRadioButton[] typeField;
29
31
	private JCheckBox showerField;
30
32
	private JCheckBox bathField;
31
33
	private JCheckBox minibarField;
32
34
	private JCheckBox aircoField;
33
35
34
36
35
37
	/**
36
38
	 * Construct a window to edit a Reservation.
37
39
	 * This window presents its caller with the necessary tools to edit the
38
40
	 * given Reservation, and save it in the system.
39
41
	 *
40
42
	 * By sending it an empty Reservation, you can add a new Reservation to the
41
43
	 * system.
42
44
	 * This class is built in a way that allows it to be used for both creating
43
45
	 * new Reservations, and updating/changing existing Reservations.
44
46
	 * Existing Reservations can also be removed by clicking the appropriate
45
47
	 * button.
46
48
	 * @param reservation The Reservation that will be updated.
47
49
	 * @param rc The ReservationController class.
48
50
	 * @param roc The RoomController class.
49
51
	 * @pre No parameter may be a null pointer.
50
52
	 * @post A window is displayed with GUI widgets, which are filled in
51
53
	 * according to the state of reservation.
52
54
	 * @throws NullPointerException if any parameter is a null pointer.
53
55
	 */
54
56
	public ReservationView(Reservation reservation, ReservationController rc, RoomController roc) {
55
57
		// Contract validation
56
58
		if(rc == null || roc == null || reservation == null)
57
59
			throw NullPointerException("One or more of the given parameters is a null pointer.");
58
-
		// Contract validated
+
60
		// Contract validated
59
61
		this.rc = rc;
60
62
		this.roc = roc;
61
63
62
64
		this.reservation = reservation;
63
65
		this.window = new Window("Reservation screen");
64
66
		this.addFields();
65
67
	}
66
68
67
69
	public void setReservation(Reservation reservation) {
68
70
		this.reservation = reservation;
69
71
	}
70
72
71
73
	public Reservation getReservation() {
72
74
		return reservation;
73
75
	}
74
76
75
77
	/**
76
78
	 * Add the necessary fields to the window.
77
79
	 * This method inspects the Reservation, and copies the included information
78
80
	 * to the widget's content.
79
81
	 */
80
82
	private void addFields() {
81
83
		this.window.createLabel("Group name");
82
84
		this.nameField = window.createTextField(this.reservation.getGroupName());
83
85
		this.window.createLabel("Amount of people");
84
86
		this.amountPeopleField = window.createSpinner(1, 20, this.reservation.getPeople(), 1);
85
87
		// Formatting date for the date field:
86
88
		this.window.createLabel("Begin date");
87
89
		this.beginDateField = window.createTextField(this.reservation.getBegin().toString());
88
90
		this.window.createLabel("End date");
89
91
		this.endDateField = window.createTextField(this.reservation.getEnd().toString());
90
92
		String[] types = {"Male", "Female", "Mixed"};
91
93
		this.typeField = this.window.createRadioButtons(types);
92
94
		// TODO: Add a test to see if the Reservation has indicated which type
93
95
		// it is, and then already check the corresponding radio button.
94
96
		this.showerField = this.window.createCheckbox("Shower");
95
97
		this.bathField = this.window.createCheckbox("Bath");
96
98
		this.minibarField = this.window.createCheckbox("Minibar");
97
99
		this.aircoField = this.window.createCheckbox("Airco");
98
100
		// TODO: Idem for the facilities, test if already in Reservation, and
99
101
		// check accordingly.
100
102
		this.window.createButton("Add/Update reservation", "", "addReservation", this);
101
103
		this.window.createButton("Remove reservation", "", "removeReservation", this);
102
104
	}
103
105
104
106
	/**
105
107
	 * Start the addition of this Reservation to the system.
106
108
	 * This method will check the current fields, and update the Reservation
107
109
	 * with those values. In doing so, the validity of the values is checked
108
110
	 * (formatting, begin date before end date, ...).
109
111
	 * If everything is okay, the method will check if these changes are
110
112
	 * possible in the current system; are there enough empty beds?
111
113
	 *
112
114
	 * If everything is okay, the changes will be propagated to the
113
115
	 * Reservation's state, and (if it's a new Reservation) will be added to the
114
116
	 * system's active Reservations.
115
117
	 */
116
118
	public void addReservation() {
117
119
		// Collect all data from the fields
118
120
		String name = this.nameField.getText();
119
121
		Date beginDate = null, endDate = null;
120
122
		try {
121
123
			beginDate = DataFormat.parse(this.beginDateField.getText());
122
-
			endDate = DataFormat.parse(this.endDateField.getText());
123
-
		}
+
124
		endDate = new Date(this.endDateField.getText());
+
125
		}
124
126
		catch(ParseException e) {
125
-
			this.window.messageDialog("Not all date fields were properly formatted.");
+
127
			this.window.messageDialog("Not all date fields were properly formatted.");
126
128
			return;
127
129
		}
128
130
		int people = (Integer)this.amountPeopleField.getValue();
129
131
		String type = "";
130
132
		for(int i=0; i<this.typeField.length; i++) {
131
133
			if(this.typeField[i].isSelected()) {
132
134
				type = this.typeField[i].getText();
133
135
				break;
134
136
			}
135
137
		}
136
138
		Set<String> facilities = new HashSet<>();
137
139
		if(this.showerField.isSelected()) {
138
140
			facilities.add("Shower");
139
141
		}
140
142
		if(this.bathField.isSelected()) {
141
143
			facilities.add("Bath");
142
144
		}
143
145
		if(this.minibarField.isSelected()) {
144
146
			facilities.add("Minibar");
145
147
		}
146
148
		if(this.aircoField.isSelected()) {
147
149
			facilities.add("Airco");
148
150
		}
149
151
150
152
		// TODO breakfast days handling
151
153
152
154
		//Set<Room> possibleRooms = this.roc.getQualifiedRooms(actualDate, duration, type, facilities);
153
155
		// TODO: Refactor upper method to work with the Reservation.
154
156
		// TODO: Implement all checks of valid data above this line!
155
157
		
156
158
		// Data validated; Try finding appropriate beds
157
159
		if(possibleRooms.size() == 0) {
158
160
			boolean tryAgain = this.window.confirmDialog("No rooms met the requirements! Would you like to continue and change the parameters?");
159
161
			if(!tryAgain) {
160
162
				// TODO close window
161
163
			}
162
164
		}
163
165
		else {
164
166
			Room pickedRoom = null;
165
167
			for(Room room: possibleRooms) {
166
168
				if(room.getEmptyBeds(actualDate, actualEndDate).size() < room.getBeds().size()) {
167
169
					// First, fill the rooms that are partially filled.
168
170
					pickedRoom = room;
169
171
					break;
170
172
				}
171
173
			}
172
174
			if(pickedRoom == null) { // If still no room, pick an empty room
173
175
				for(Room room: possibleRooms) {
174
176
					if(room.getEmptyBeds(actualDate, actualEndDate).size() >= amount) {
175
177
						pickedRoom = room;
176
178
						break;
177
179
					}
178
180
				}
179
181
			}
180
182
			assert pickedRoom != null;
181
183
			// TODO: Set reservation fields here!
182
184
			this.rc.addReservation(reservation, pickedRoom);
183
185
184
186
			// Confirm and show price:
185
187
			int price = this.reservation.getPrice();
186
188
			this.window.confirmDialog("Reservation confirmed! Price: " +String.valueOf(price));
187
189
		}
188
190
		
189
191
	}
190
192
191
193
	/**
192
194
	 * Remove Reservation and the GUI.
193
195
	 * Calling this method will remove the Reservation, followed by closing this
194
196
	 * window.
195
197
	 * If the Reservation is active (i.e. saved in the ReservationController),
196
198
	 * it will be removed.
197
199
	 * Any changes will be cancelled.
198
200
	 */
199
201
	public void removeReservation() {
200
202
		if(this.rc.getReservations().contains(this.reservation)) {
201
203
			this.rc.cancelReservation(this.reservation);
202
204
		}
203
205
		this.window.close();
204
206
	}
205
207
}
206
208

Challenge 6/Room.java

7 additions and 7 deletions.

View changes Hide changes
1
1
import java.util.HashSet;
2
2
import java.util.Date;
3
3
4
4
/**
5
5
 * A room in a hostel.
6
6
 * Room represents just that: A room.
7
7
 * A room contains a set of Beds, facilities that can be used, etc.
8
8
 * It's highly decoupled: Apart from holding a set of Beds, the only members
9
9
 * types consist of those that are available in every OpenJDK implementation.
10
10
 * @author Maarten Vangeneugden - 1438256
11
11
 */
12
12
public class Room {
13
13
14
14
	private HashSet<Bed> beds;
15
15
	private String type;
16
16
	private HashSet<String> facilities;
17
17
18
18
	/**
19
19
	 * Create a new Room.
20
20
	 * @param beds The amount of Beds that will be placed in this Room.
21
21
	 * @param type The type of this Room. (for example: Male, Female, Mixed,
22
22
	 * ...)
23
23
	 * @param facilities A Set of facilities this Room provides ("Shower",
24
24
	 * "Radio", "Overpriced WiFi", ...)
25
25
	 * @pre No parameter must be a null pointer.
26
26
	 * @pre beds must be at least 1.
27
27
	 * @pre Type mustn't be an empty String.
28
28
	 * @pre No facility may be an empty String.
29
29
	 * @post The Room will receive the provided amount of Beds, which are all
30
30
	 * completely released.
31
31
	 * @throws IllegalArgumentException if the amount of Beds is less than 1, or
32
32
	 * one of the facilities is an empty String.
33
33
	 * @throws NullPointerException if one of the parameters is a null pointer.
34
34
	 */
35
35
	public Room(int beds, String type, Set<String> facilities) {
36
36
		// Contract validation happens in the setter methods
37
37
		this.setFacilities(facilities);
38
38
		this.setType(type);
39
39
		if(beds < 1) {
40
40
			throw IllegalArgumentException("Beds was less than 1.");
41
-
		}
+
41
		}
42
42
		// Contract validated, execute constructor
43
43
		this.beds = new HashSet<>();
44
44
		for(int i=0; i<beds; i++) {
45
45
			this.beds.add(new Bed());
46
46
		}
47
47
	}
48
48
49
49
	/**
50
50
	 * Remove a Bed from this Room.
51
51
	 * This method will remove the given Bed from this Room.
52
52
	 * However, make sure that this Bed is free of any reservations, as you
53
53
	 * can't delete a Bed with a reserved period.
54
54
	 * @see Bed
55
55
	 * @param bed The Bed to be removed.
56
56
	 * @pre bed mustn't be null.
57
57
	 * @pre bed mustn't be the last Bed in this Room.
58
58
	 * @pre bed must be in this Room.
59
59
	 * @pre bed mustn't have any reserved periods.
60
60
	 * @post The amount of Beds in this Room is decremented by 1.
61
61
	 * @throws IllegalArgumentException if bed is not in this Room, or has
62
62
	 * reserved periods, or this is the last Bed in the Room.
63
63
	 * @throws NullPointerException if bed is a null pointer.
64
64
	 */
65
65
	public void removeBed(Bed bed) {
66
66
		// Contract validation
67
67
		if(!this.getBeds().contains(bed)) {
68
68
			throw IllegalArgumentException("The given Bed is not in this Room.");
69
-
		}
+
69
		}
70
70
		if(bed.hasReservations()) {
71
71
			throw IllegalArgumentException("The given Bed still has reserved periods.");
72
-
		}
+
72
		}
73
73
		if(this.getBeds().size() == 1) {
74
74
			throw IllegalArgumentException("Deleting this Bed would empty the Room.");
75
-
		}
+
75
		}
76
76
		// Contract validated, execute method
77
77
		this.beds.remove(bed);
78
78
		// Assert post conditions
79
79
		assert !this.getBeds().contains(bed) : "The given Bed was not removed from this Room.";
80
80
	}
81
81
82
82
	/**
83
83
	 * Add a Bed to this Room.
84
84
	 * @post The amount of Beds in this Room increments with 1.
85
85
	 */
86
86
	public void addBed() {
87
87
		this.beds.add(new Bed());
88
88
	}
89
89
90
90
	/**
91
91
	 * Returns a copy of all Beds.
92
92
	 * @return A verbatim copy of all Beds.
93
93
	 */
94
94
	@SuppressWarnings("unchecked")
95
95
	public Set<Bed> getBeds() {
96
96
		return (HashSet<Bed>) this.beds.clone();
97
97
	}
98
98
99
99
	/**
100
100
	 * Set the type of this Room.
101
101
	 * @param type The new type of the Room.
102
102
	 * @pre type mustn't be an empty String, or a null pointer.
103
103
	 * @post The Room's type is changed to the given type.
104
104
	 * @throws IllegalArgumentException if type is an empty String.
105
105
	 * @throws NullPointerException if type is a null pointer.
106
106
	 */
107
107
	public void setType(String type) {
108
108
		if(type.isEmpty()) {
109
109
			throw IllegalArgumentException("type is an empty String.");
110
-
		}
+
110
		}
111
111
		this.type = type;
112
112
	}
113
113
114
114
	/**
115
115
	 * Returns the type of this Room.
116
116
	 * @return The secret launch codes of the USS Nimitz, granting access to
117
117
	 * nuclear warheads so big, you'll lose faith in humanity.
118
118
	 */
119
119
	public String getType() {
120
120
		return type;
121
121
	}
122
122
123
123
	/**
124
124
	 * Set the facilities available in this Room.
125
125
	 * @param facilities The set of facilities in this Room.
126
126
	 * @pre No facility must be an empty String or a null pointer.
127
127
	 * @post The Room will have the newly provided facilities.
128
128
	 * @throws IllegalArgumentException if one of the facilities is an empty String.
129
129
	 * @throws NullPointerException if any given variable is a null pointer.
130
130
	 */
131
131
	public void setFacilities(Set<String> facilities) {
132
132
		for(String facility : facilities) {
133
133
			if(facility.isEmpty()) {
134
134
				throw IllegalArgumentException("A facility was an empty String.");
135
-
			}
+
135
			}
136
136
		}
137
137
		this.facilities = facilities;
138
138
	}
139
139
140
140
	/**
141
141
	 * Returns a copy of all facilities.
142
142
	 * @return A verbatim copy of all facilities.
143
143
	 */
144
144
	@SuppressWarnings("unchecked")
145
145
	public Set<String> getFacilities() {
146
146
		return (HashSet<String>)this.facilities.clone();
147
147
	}
148
148
149
149
	/**
150
150
	 * Search for Beds that can be reserved.
151
151
	 * This method will look through all Beds, and check whether they can be
152
152
	 * reserved in the given period. You will receive all Beds that can be
153
153
	 * reserved.
154
154
	 * @param begin The begin date.
155
155
	 * @param end The end date.
156
156
	 * @pre No parameter must be null.
157
157
	 * @pre begin must strictly come before end.
158
158
	 * @throws IllegalArgumentException if begin comes after end, or both are
159
159
	 * equal.
160
160
	 * @throws NullPointerException if any given parameter is a null pointer.
161
161
	 * @return A Set of all Beds that can be reserved, or an empty Set if no Bed
162
162
	 * can be reserved in the given period.
163
163
	 */
164
164
	public Set<Bed> getEmptyBeds(Date begin, Date end) {
165
165
		// Contract validation
166
166
		if(!begin.before(end)) {
167
167
			throw IllegalArgumentException("begin does not come strictly before end");
168
-
		}
+
168
		}
169
169
		// Validated
170
170
		Set<Bed> emptyBeds = new HashSet<>();
171
171
		for(Bed bed: this.getBeds()) {
172
172
			if(bed.isFree(begin, end)) {
173
173
				emptyBeds.add(bed);
174
174
			}
175
175
		}
176
176
		return emptyBeds;
177
177
	}
178
178
179
179
}
180
180

Challenge 6/RoomController.java

2 additions and 2 deletions.

View changes Hide changes
1
1
import java.util.Date;
2
2
import java.util.HashSet;
3
3
4
4
/**
5
5
 * Holds all Rooms in the hostel.
6
6
 * The hostel contains a set of Rooms, with facilities and stuff.
7
7
 * This class takes care of storing all those Rooms, and provides methods to
8
8
 * manage this, like removing Rooms, adding Rooms, ...
9
9
 * @author Maarten Vangeneugden - 1438256
10
10
 */
11
11
public class RoomController {
12
12
13
13
	private Set<Room> rooms;
14
14
15
15
	public RoomController() {
16
16
		this.rooms = new HashSet<>();
17
17
	}
18
18
19
19
20
20
	public Set<Room> getRooms() {
21
21
		return rooms;
22
22
	}
23
23
24
24
	/**
25
25
	 * Returns all rooms that meet the given requirements.
26
26
	 * This method will search through all rooms, and check which rooms qualify.
27
27
	 * Currently, this method checks for the following things:
28
28
	 * - Is the requested type available?
29
29
	 * - Does the Set of requested facilities form a subset of the Room's
30
30
	 *   facilities?
31
31
	 * - Are there any Beds in the Room that can be reserved in the given
32
32
	 *   period?
33
33
	 * @param reservation The Reservation for which to find eligible rooms.
34
34
	 * @pre reservation mustn't be null.
35
35
	 * @throws NullPointerException if reservation is a null pointer.
36
36
	 * @return A set of all rooms that meet the requirements, or an empty set if
37
37
	 * none were found.
38
38
	 */
39
39
	public Set<Room> getQualifiedRooms(Reservation reservation) {
40
40
		Set<Room> qualifiedRooms = new HashSet<>();
41
41
		for(Room room : this.getRooms()) {
42
42
			if(room.getType().equals(reservation.getType()))
43
-
				continue;
+
43
				continue;
44
44
			if(!room.getFacilities().containsAll(reservation.getFacilities()))
45
-
				continue;
+
45
				continue;
46
46
			if(room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).isEmpty())
47
47
				continue;
48
48
			// The Room fulfills all requirements at this point, so add it to
49
49
			// the set
50
50
			qualifiedRooms.add(room);
51
51
		}
52
52
		return qualifiedRooms;
53
53
	}
54
54
}
55
55

Challenge 6/Window.java

1 addition and 1 deletion.

View changes Hide changes
1
1
 * Window.java - Module to create a new window with JSugar.
2
2
 * Copyright © 2016 Maarten "Vngngdn" Vangeneugden
3
3
 * 
4
4
 * This program is free software: you can redistribute it and/or modify
5
5
 * it under the terms of the GNU General Public License as published by
6
6
 * the Free Software Foundation, either version 3 of the License, or
7
7
 * (at your option) any later version.
8
8
 * 
9
9
 * This program is distributed in the hope that it will be useful,
10
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
12
 * GNU General Public License for more details.
13
13
 * 
14
14
 * You should have received a copy of the GNU General Public License
15
15
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
16
 */
17
17
18
18
/*
19
19
 * TODO list:
20
20
 * - JSlider (It's the same as the JSpinner, only longer. So an extra.)
21
21
 * - JTable (And a JScrollBar to accompany it) (extra, because of JList)
22
22
 * - JFileChooser (?)
23
23
 * DONE list:
24
24
 * - JLabel
25
25
 * - JText
26
26
 * - JButton
27
27
 * - JDialogBoxes (you know, everything dialog related)
28
28
 * - JCheckbox
29
29
 * - JRadioButton (properly grouping them has been taken care of as well)
30
30
 * - JSpinner
31
31
 * - JComboBox
32
32
 * - JList
33
33
 */
34
34
35
35
import javax.swing.*; // FIXME: Maybe namespacing it to "javax.swing;" is a better idea.
36
36
import java.util.NoSuchElementException;
37
37
import java.lang.reflect.Method;
38
38
import java.io.File;
39
39
import java.awt.event.WindowEvent; // For programatically closing the window
40
40
41
41
/**
42
42
 * Window class for the program.
43
43
 *
44
44
 * Window contains the necessary data and methods to present the user with what
45
45
 * he's familiar with as being a "window". To make it functional, the developer
46
46
 * can make use of a series of methods to add components to said window, remove
47
47
 * components, and so on.
48
48
 * Currently, Window also contains methods to show dialogs. This will be cleaned
49
49
 * in the near future.
50
50
 * @author Maarten Vangeneugden
51
51
 */
52
52
public class Window { // Must be public, in order to generate Javadoc.
53
53
	private JPanel panel; // The panel that contains all the components.
54
54
	private JFrame frame; // The "window" being presented to the user.
55
55
56
56
	/**
57
57
	 * Constructor of Window.
58
58
	 * By creating a new Window instance, this constructor will automatically
59
59
	 * start the initialization of the GUI. After doing so, the caller can
60
60
	 * start adding components to the window as pleased.
61
61
	 * @param title The title to be shown in the window's title bar.
62
62
	 */
63
63
	public Window(String title) {
64
64
		// Setting the UI style to the platform's UI style. Fuck Swing's,
65
65
		// really.
66
66
		try {
67
67
		UIManager.setLookAndFeel(
68
68
				UIManager.getSystemLookAndFeelClassName());
69
69
		} catch(Exception e) {
70
70
			e.printStackTrace();
71
71
		}
72
72
73
73
		if(title == null || title.equals("")) { // If the title was omitted:
74
74
			title = "JSugar";
75
75
		}
76
76
		this.panel = new JPanel();
77
77
		// TODO: The current title is "Hello world!" but that will become caller
78
78
		// defined soon.
79
79
		JFrame frame = new JFrame(title);
80
80
		// Makes it so that if the user clicks the X in the titlebar, the window
81
81
		// closes:
82
82
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // XXX: Closes
83
83
		//ALL open windows!
84
84
		//frame.getContentPane().add(lblHelloWorld); // So you use a get() in order to set() data? #JavaWTF
85
85
		frame.setContentPane(this.panel); // Connecting the component panel to the window.
86
86
		// Makes the window fit to the necessary width and height, so it can show all "subcomponents".
87
87
		frame.pack(); 	
88
88
		frame.setVisible(true); // Makes the window visible to the user.
89
89
		this.frame = frame;
90
90
	}
91
91
92
92
	/**
93
93
	 * Resizes the window to fit all components.
94
94
	 * By calling this method, the window will evaluate the currently visible
95
95
	 * components, and resize itself so that all components become properly
96
96
	 * visible.
97
97
	 */
98
98
	private void updateWindow() {
99
99
		this.frame.pack();
100
100
	}
101
101
102
102
	/**
103
103
	 * A series of tests for method and class handling.
104
104
	 * When a caller presents certain methods with data concerning reflection,
105
105
	 * the Java classes you need to use for that are quite opaque, and don't
106
106
	 * offer much safety in any way.
107
107
	 * The solution therefore, is run some validation checks, but these take up
108
108
	 * a decent amount of space in terms of LoC.
109
109
	 * This method takes care of all that. Call this function whenever data
110
110
	 * needs to be validated.
111
111
	 * @param methodName The name of the method, as it is declared in object.
112
112
	 * @param object The class instance in where this method will be called.
113
113
	 * @return The method that could be derived from the supplied data, or null
114
114
	 * if that wasn't possible.
115
115
	 * @throws NullPointerException if either methodName or object are null
116
116
	 * pointers.
117
117
	 * @throws IllegalArgumentException if methodName is empty, or the method
118
118
	 * does not appear to be declared in the given object, or object is not a
119
119
	 * class.
120
120
	 */
121
121
	// All unchecked typecasts are safe, and the use of raw types is taken care
122
122
	// of.
123
123
	@SuppressWarnings({"unchecked","rawtypes"})
124
124
	private Method handleReflectionData(String methodName, Object object) {
125
125
		// Null pointer checking:
126
126
		if (methodName == null || object == null) {
127
127
			throw new NullPointerException("One or more of the given parameters are null pointers.");
128
128
		}
129
129
130
130
		// XXX: Some might say the next line should be in an else{} block. But
131
131
		// Scoping rules require that I'd then have to wrap the rest of the
132
132
		// method in the same else to use it.
133
133
		Class methodClass = object.getClass(); 
134
134
		if (methodName.equals("")) {
135
135
			throw new IllegalArgumentException("The given methodName was empty.");
136
136
		}
137
137
		Method method;
138
138
		try { // First: Look if there's a method without parameters.
139
139
			method = methodClass.getMethod(methodName, null);
140
140
		}
141
141
		catch (NoSuchMethodException exception) {
142
142
			try {
143
143
				// It's possible that the method requires an event parameter, so
144
144
				// check for that as well:
145
145
				Class<?>[] parameters = {java.awt.event.ActionEvent.class};
146
146
				method = methodClass.getMethod(methodName, parameters);
147
147
			}
148
148
			catch (NoSuchMethodException e) {
149
149
				throw new IllegalArgumentException("The given method does not appear in the given class. Be aware that the given method mustn't have any parameters, or only 1 parameter, which has to be of type java.awt.event.ActionEvent.");
150
150
			}
151
151
		}
152
152
		// At this stage, the given data has been validated, and we've been able
153
153
		// to retrieve the method itself.
154
154
		return method;
155
155
	}
156
156
157
157
	/**
158
158
	 * Creates a button in the GUI for interaction.
159
159
	 * This function offers a convenient way to create a button, that can be
160
160
	 * directly interacted with by the user. After creation, the button itself
161
161
	 * is returned to the caller, if he wishes to do something else with it.
162
162
	 * @param text The text that will be displayed in the button.
163
163
	 * @param action The action that will be returned to the action listener.
164
164
	 * @param methodName The name of the method that will be called when an
165
165
	 * action is triggered.
166
166
	 * @param triggerObject The object instance that contains the given method.
167
167
	 * This may only be a null pointer if triggerMethod is not an instance
168
168
	 * method.
169
169
	 * performed. This method may accept an ActionEvent parameter as its only
170
170
	 * parameter, or no parameters at all.
171
171
	 * @throws NullPointerException if triggerMethod is a null pointer, or
172
172
	 * the empty String was given.
173
173
	 * @throws IllegalArgumentException if triggerMethod has more than 1
174
174
	 * parameter, or the 1 required parameter is not of type ActionEvent.
175
175
	 * @return The button that was created.
176
176
	 * @see java.awt.event.ActionEvent
177
177
	 * @see java.lang.reflect.Method#invoke
178
178
	 */
179
179
	public JButton createButton(String text, String action, String methodName, Object triggerObject) {
180
180
		Method triggerMethod = this.handleReflectionData(methodName, triggerObject);
181
181
182
182
		// For starters, we first assert that the given parameters are valid:
183
183
		if (text == null) {
184
184
			text = "";
185
185
		}
186
186
		if (action == null) {
187
187
			action = "";
188
188
		}
189
189
		
190
190
		// When the method gets here, everything's been validated correctly.
191
191
		JButton button = new JButton(text);
192
192
		button.setActionCommand(action);
193
193
		button.addActionListener(
194
194
				new java.awt.event.ActionListener() {
195
195
					public void actionPerformed(java.awt.event.ActionEvent event) {
196
196
						try {
197
197
							triggerMethod.setAccessible(true);
198
198
							if (triggerMethod.getParameterTypes().length == 0) {
199
199
								// FIXME: Next line throws a warning?
200
200
								triggerMethod.invoke(triggerObject, null);
201
201
							}
202
202
							else {
203
203
								triggerMethod.invoke(triggerObject, new Object[]{event});
204
204
							}
205
205
						}
206
206
						catch (Exception useless) {
207
207
							/*
208
208
							 * XXX: Some info on why I don't just throw said
209
209
							 * Exception to the caller:
210
210
							 * Java has this awful language constraint, which
211
211
							 * forces every damn exception that isn't a subclass
212
212
							 * of RuntimeException, to be declared in the method
213
213
							 * declaration. This tends to infect all underlying
214
214
							 * methods as well, and all that for reasons I can't
215
215
							 * comprehend. In order to keep JSugar a simple and
216
216
							 * clean library, I'll rather just handle it here,
217
217
							 * and throw a RuntimeException with appropriate
218
218
							 * details.
219
219
							 */
220
220
							throw new IllegalArgumentException("triggerMethod is not accessible from this context.");
221
221
						}
222
222
					}
223
223
				});
224
224
		this.addComponent(button);
225
225
		return button;
226
226
	}
227
227
228
228
	/**
229
229
	 * Ask the user for input through a dialog box.
230
230
	 * This method presents the user with an input field, that can accept
231
231
	 * textual input. The method will return the given input after the user's
232
232
	 * clicked a button to send.
233
233
	 * @param text The text/question to be asked to the user.
234
234
	 * @return A String, equal to what the user entered.
235
235
	 * @throws NullPointerException if text is a null pointer.
236
236
	 */
237
237
	public String inputDialog(String text) {
238
238
		if (text == null) {
239
239
			throw new NullPointerException("The given text/question was a null pointer.");
240
240
		}
241
241
		return JOptionPane.showInputDialog(text);
242
242
	}
243
243
244
244
	/**
245
245
	 * Give the user a dialog box.
246
246
	 * This method can be used to provide a simple dialog to the user.
247
247
	 * This will show the user the given question, after which a boolean value
248
248
	 * is returned, holding the choice.
249
249
	 * @param text The text/question to be asked to the user.
250
250
	 * @return True if the user confirms, False if he denies.
251
251
	 * @throws NullPointerException if text is a null pointer.
252
252
	 */
253
253
	public boolean confirmDialog(String text) {
254
254
		if (text == null) {
255
255
			throw new NullPointerException("The given text/question was a null pointer.");
256
256
		}
257
257
		final int ACCEPTED = 0;
258
258
		//final int DENIED = 1; // Not used in the current context.
259
259
		int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"});
260
260
		if (result == ACCEPTED) {
261
261
			return true;
262
262
		}
263
263
		else {
264
264
			return false;
265
265
		}
266
266
	}
267
267
268
268
	/**
269
269
	 * Give the user a choice dialog box.
270
270
	 * This method gives the user a simple dialog with predefined choices.
271
271
	 * These choices are to be provided by the caller in a simple array.
272
272
	 *
273
273
	 * Tip: This method works extremely well with arbitrary created choices.
274
274
	 * That is: if the outcome of the dialog is trivial (e.g. Only 1 choice),
275
275
	 * then that value is immediately returned.
276
276
	 * @param text The text/question to be asked to the user.
277
277
	 * @param choices An array of Strings, containing the choices the user can
278
278
	 * pick.
279
279
	 * @return The index value of the picked choice, or -1 if no choices were
280
280
	 * given.
281
281
	 * @throws NullPointerException if text is a null pointer.
282
282
	 */
283
283
	public int choiceDialog(String text, String[] choices) {
284
284
		if (text == null) {
285
285
			throw new NullPointerException("The given text/question was a null pointer.");
286
286
		}
287
287
		// First: handling the trivial cases:
288
288
		if (choices.length == 0) {
289
289
			return -1;
290
290
		}
291
291
		else if (choices.length == 1) {
292
292
			return 0;
293
293
		}
294
294
		int answer = JOptionPane.CLOSED_OPTION;
295
295
		// The dialog needs to be shown again until the user has made a possible
296
296
		// choice, i.e. Chickening out using the close button is not possible
297
297
		// (Because that returns CLOSED_OPTION).
298
298
		while (answer == JOptionPane.CLOSED_OPTION) {
299
299
				JOptionPane.showOptionDialog(
300
300
					null, // The parent component. May become the panel?
301
301
					text, // The text/question to describe the goal
302
302
					"Dialog", // The text in the title bar
303
303
					JOptionPane.DEFAULT_OPTION, // The kind of available options
304
304
					JOptionPane.QUESTION_MESSAGE, // The type of message
305
305
					null, // The icon to show
306
306
					choices, // The possible choices
307
307
					choices[0] // The standard choice
308
308
					);
309
309
		}
310
310
		return answer;
311
311
	}
312
312
		
313
313
314
314
	/**
315
315
	 * Creates a label in the GUI for interaction.
316
316
	 * This function offers a convenient way to create a label, that can be
317
317
	 * directly interacted with by the user. After creation, the label itself
318
318
	 * is returned to the caller, if he wishes to do something else with it.
319
319
	 * @param text The text that will be displayed in the label.
320
320
	 * @return The label that was created.
321
321
	 */
322
322
	public JLabel createLabel(String text) {
323
323
		JLabel label = new JLabel(text);
324
324
		this.addComponent(label);
325
325
		return label;
326
326
	}
327
327
328
328
	/**
329
329
	 * Adds a checkbox to the window.
330
330
	 * By providing a String, you can use this method to easily
331
331
	 * create a checkbox, and add it to the window. 
332
332
	 * @param text The text to put next to the checkbox.
333
333
	 * @return The checkbox that was created and added to the GUI.
334
334
	 */
335
335
	public JCheckBox createCheckbox(String text) {
336
336
		JCheckBox checkbox = new JCheckBox(text);
337
337
		this.addComponent(checkbox);
338
338
		return checkbox;
339
339
	}
340
340
341
341
	/**
342
342
	 * Adds radio buttons to the window.
343
343
	 * Given a list of Strings, this method will create the same amount of radio
344
344
	 * buttons.
345
345
	 *
346
346
	 * The radio buttons will silently be grouped in a ButtonGroup object,
347
347
	 * making them automatically disable each other, so only 1 radio button can
348
348
	 * be enabled. This ButtonGroup is immutable.
349
349
	 *
350
350
	 * If you need a mutable ButtonGroup, create your own, and use the 
351
351
	 * {@link #addComponent} method to add the radio buttons manually.
352
352
	 * @param text An array of Strings. The length of the array will determine
353
353
	 * the amount of radio buttons that will be created.
354
354
	 * @return An array of radio buttons, in the same order as text.
355
355
	 */
356
356
	public JRadioButton[] createRadioButtons(String text[]) {
357
357
		JRadioButton[] radioButtons = new JRadioButton[text.length];
358
358
		ButtonGroup buttonGroup = new ButtonGroup();
359
359
		for (int i=0; i<radioButtons.length; i++) {
360
360
			radioButtons[i] = new JRadioButton(text[i]);
361
361
			//radioButtons[i].setText(text[i]);
362
362
			buttonGroup.add(radioButtons[i]);
363
363
			this.addComponent(radioButtons[i]);
364
364
		}
365
365
366
366
		assert radioButtons.length == buttonGroup.getButtonCount() : "The amount of radio buttons ("+ radioButtons.length +") differs from the amount of buttons in buttonGroup ("+ buttonGroup.getButtonCount() +").";
367
367
		return radioButtons;
368
368
	}
369
369
370
370
	public JTextField createTextField(String text) {
371
371
		JTextField textField = new JTextField(text);
372
372
		this.addComponent(textField);
373
373
		return textField;
374
374
	}
375
375
376
376
	/**
377
377
	 * Adds a number spinner component to the GUI.
378
378
	 * This method allows the caller to create a so-called "spinner component"
379
379
	 * to the window. This spinner is an input box, in which only integers can
380
380
	 * be put.
381
381
	 *
382
382
	 * The caller can set a range, a start value, and a step size.
383
383
	 *
384
384
	 * The spinner created with this method may modify itself based on the
385
385
	 * parameters.
386
386
	 * For example: If the minimum and maximum value are equal, the spinner will
387
387
	 * be disabled.
388
388
	 *
389
389
	 * @param minimum The minimum value that can be selected.
390
390
	 * @param maximum The maximum value that can be selected.
391
391
	 * @param start The value that will initially be shown in the component.
392
392
	 * @param stepSize The step size when the user increases/decreases the
393
393
	 * value.
394
394
	 * @throws IllegalArgumentException if minimum is larger than maximum, 
395
395
	 * start is not in the range of the selectable values, or stepsize is not a
396
396
	 * natural number.
397
397
	 * @return The JSpinner that was added to the window.
398
398
	 */
399
399
	public JSpinner createSpinner(int minimum, int maximum, int start, int stepSize) {
400
400
		// As usual, we begin with checking the contract:
401
401
		if(minimum > maximum) {
402
402
			throw new IllegalArgumentException("The minimum value ("+ minimum +") was larger than the maximum value ("+ maximum +")");
403
403
		}
404
404
		// The "start ∉ [minimum, maximum]" is done by the SpinnerNumberModel
405
405
		// constructor, which will be constructed later.
406
406
		if(stepSize <= 0) { // stepSize ∉ ℕ¹ (In Belgium: ℕ₀)
407
407
			throw new IllegalArgumentException("The stepSize ("+ stepSize +") is not a natural number (excluding 0).");
408
408
		}
409
409
		// If the contract is valid, we can begin:
410
410
		/*
411
411
		 * I'd like to interject here, because this is a nice example of why
412
412
		 * JSugar was a good idea:
413
413
		 * If you want a spinner, you'll typically want an integer spinner. But
414
414
		 * in Swing, when you create a JSpinner, it creates a JSpinner, with a
415
415
		 * predefined 'SpinnerNumberModel' attached to it.
416
416
		 * It's this model you then have to extract from the created spinner, on
417
417
		 * which you need to apply the configuration.
418
418
		 * What you actually have to do, is create a SpinnerNumberModel
419
419
		 * yourself, put your settings on that, and then, create a JSpinner to
420
420
		 * which you give that SpinnerNumberModel.
421
421
		 * In essence: The entire Java framework is shit.
422
422
		 */
423
423
		SpinnerNumberModel spinnerSettings = new SpinnerNumberModel(
424
424
				start,
425
425
				minimum,
426
426
				maximum,
427
427
				stepSize
428
428
				);
429
429
		JSpinner spinner = new JSpinner(spinnerSettings);
430
430
		if(minimum == maximum) { // Trivial value is set already, --> disable.
431
431
			spinner.setEnabled(false);
432
432
		}
433
433
		this.addComponent(spinner);
434
434
		return spinner;
435
435
	}
436
436
437
437
	/**
438
438
	 * Adds a number spinner component to the GUI.
439
439
	 * This method allows the caller to create a so-called "spinner component"
440
440
	 * to the window. This spinner is an input box, in which only integers can
441
441
	 * be put.
442
442
	 * 
443
443
	 * Tip: This method is a convenience method, and works extremely well with
444
444
	 * arbitrary data.
445
445
	 * For example: The start value is automatically set to the minimal possible
446
446
	 * value, and the step size defaults to 1.
447
447
	 * If the minimum and maximum are equal, the component will be disabled, and
448
448
	 * thus, be locked on the only (trivially) possible value.
449
449
	 * If minimum is larger than maximum, the method will notify you of this,
450
450
	 * but also swap the values. So you can rest assured that the spinner will
451
451
	 * handle errors, but also, inform you about it.
452
452
	 * @param minimum The minimum value that can be selected.
453
453
	 * @param maximum The maximum value that can be selected.
454
454
	 * @return The JSpinner component that was added to the window.
455
455
	 */
456
456
	public JSpinner createSpinner(int minimum, int maximum) {
457
457
		// The disabling of equal values is done in the full createSpinner(), so
458
458
		// this is merely switching values if they need to be swapped.
459
459
		if(minimum > maximum) {
460
460
			System.err.println("minimum ("+ minimum +") was larger than maximum ("+ maximum +").");
461
461
			// FIXME: Consider whether it's appropriate to print a stacktrace
462
462
			// here, which may be convenient for debugging.
463
463
			
464
464
			// XXX: I know you don't need the help variable when swapping
465
465
			// integers, because you can also do basic arithmetics. Change it if
466
466
			// it causes too much eye burn for you.
467
467
			int swapValue = minimum;
468
468
			minimum = maximum;
469
469
			maximum = swapValue;
470
470
		}
471
471
472
472
		// Yeah, these 2 variables make you cringe huh, performance addicts?
473
473
		// Drown me in the tears of your useless performance-related opinions.
474
474
		int startValue = minimum;
475
475
		int stepSize = 1;
476
476
		return this.createSpinner(minimum, maximum, startValue, stepSize);
477
477
	}
478
478
479
479
	/**
480
480
	 * Adds a combobox to the GUI.
481
481
	 * Allows the caller to create a combobox by providing the values that
482
482
	 * should be put in it.
483
483
	 *
484
484
	 * This method can only be used for String values. If that is not what you
485
485
	 * need, consider creating your own combobox and adding it manually. Or, if
486
486
	 * you need a combobox for integers, consider {@link #createSpinner}.
487
487
	 *
488
488
	 * WARNING: {@link JComboBox#getSelectedItem} returns an object, not a
489
489
	 * String. You need to manually typecast this. This is a constraint of the
490
490
	 * Swing framework.
491
491
	 * @param items An array of Strings that will be put in the combobox.
492
492
	 * @throws NullPointerException if one of the values in items is a null
493
493
	 * pointer.
494
494
	 * @throws IllegalArgumentException if items is empty.
495
495
	 * @return The JCombobox that was added to the window.
496
496
	 */
497
497
	public JComboBox<String> addComboBox(String[] items) {
498
498
		// Contract validation:
499
499
		if(items.length == 0) {
500
500
			throw new IllegalArgumentException("The given array of items was empty.");
501
501
		}
502
502
		for(String item : items) {
503
503
			if(item == null) {
504
504
				throw new NullPointerException("One of the given Strings is a null pointer.");
505
505
			}
506
506
		}
507
507
		// Contract validated, create the component:
508
508
		JComboBox<String> comboBox = new JComboBox<String>(items);
509
509
		comboBox.setSelectedIndex(0);
510
510
		if(comboBox.getItemCount() == 1) { // Trivial selection
511
511
			comboBox.setEnabled(false);
512
512
		}
513
513
		this.addComponent(comboBox);
514
514
		return comboBox;
515
515
	}
516
516
517
517
	/**
518
518
	 * Creates a list of the given data, and adds it to the GUI.
519
519
	 * This will create a JList component, containing the given data. 
520
520
	 * To jar up your memory: A list in this context, is a component in which
521
521
	 * data of the same type is printed out. The user of said list, can then
522
522
	 * select a subset of these items.
523
523
	 *
524
524
	 * @see JList for a collection of possible operations.
525
525
	 * @param items The String items that will be put in the list.
526
526
	 * @throws NullPointerException if one of the values in items is a null
527
527
	 * pointer.
528
528
	 * @throws IllegalArgumentException if items is empty.
529
529
	 * @return A JList component, that was a added to the GUI.
530
530
	 */
531
531
	public JList createList(String[] items) {
532
532
		// Contract validation:
533
533
		if(items.length == 0) {
534
534
			throw new IllegalArgumentException("The given array of items was empty.");
535
535
		}
536
536
		for(String item : items) {
537
537
			if(item == null) {
538
538
				throw new NullPointerException("One of the given Strings is a null pointer.");
539
539
			}
540
540
		}
541
541
		// Contract validated, create the component:
542
542
		JList list = new JList(items);
543
543
		this.addComponent(list);
544
544
		return list;
545
545
	}
546
546
547
547
	/**
548
548
	 * Creates a table of the given data, and adds it to the GUI.
549
549
	 * This method allows you to create a table with sorting functionality in
550
550
	 * the GUI.
551
551
	 * This method relies on implications, deducted from the given data. That
552
552
	 * is, the length of the rows and columns is calculated by the longest
553
553
	 * length it can find in the nested array.
554
554
	 * To change the data, take a look at the JTable documentation.
555
555
	 *
556
556
	 * @see JTable for a collection of possible operations.
557
557
	 * @param items The String items that will be put in the list.
558
558
	 * @throws NullPointerException if one of the values in items is a null
559
559
	 * pointer.
560
560
	 * @throws IllegalArgumentException if items is empty, or the amount of
561
561
	 * column names does not correspond with the given amount of items.
562
562
	 * @return A JTable component, that was a added to the GUI.
563
563
	 */
564
564
	public JTable createTable(String[] columns, String[][] items) {
565
565
		// Contract validation:
566
566
		if(items.length == 0) {
567
567
			throw new IllegalArgumentException("The given array of items was empty.");
568
568
		}
569
569
		if(columns.length != items[0].length) {
570
570
			throw new IllegalArgumentException("The amount of columns does not correspond to the given amount of items.");
571
571
		}
572
572
		for(int i=0; i<items.length; i++) {
573
573
			if(items[i] == null) {
574
574
				throw new NullPointerException("One of the given Strings is a null pointer.");
575
575
			}
576
576
		}
577
577
		// Contract validated, create the component:
578
578
		// Deducting max length:
579
579
		int columnCount = columns.length;
580
580
		int rowCount = items.length;
581
581
		
582
582
		JTable table = new JTable(items, columns);
583
583
		this.addComponent(table);
584
584
		return table;
585
585
	}
586
586
587
587
	/**
588
588
	 * Adds the given component to the GUI.
589
589
	 * This method allows its caller to give a pre-made component, so that it
590
590
	 * can be added to the GUI. Even though its main use is for the Window class
591
591
	 * itself, the user of JSugar can also use it to create components himself,
592
592
	 * and then add them. As such, this method doesn't provide parameters for
593
593
	 * reflection/action triggering purposes.
594
594
	 * @param component The component to be added to the window.
595
595
	 * @throws NullPointerException if the given component is a null pointer.
596
596
	 */
597
597
	public void addComponent(JComponent component) {
598
598
		int originalSize = this.panel.getComponentCount();
599
599
		this.panel.add(component); // Throws the exception if null.
600
600
		this.updateWindow();
601
601
602
602
		assert originalSize == this.panel.getComponentCount()-1 : "A component was supposed to be added to the window, but the total amount of components was unchanged after the addition.";
603
603
	}
604
604
605
605
	/**
606
606
	 * Removes the given component from the GUI.
607
607
	 * This method allows its caller to remove a component from the GUI.
608
608
	 * @param component The component to be removed.
609
609
	 * @throws NoSuchElementException if the given component does not exist in
610
610
	 * the GUI.
611
611
	 * @throws NullPointerException if the given component is a null pointer.
612
612
	 */
613
613
	public void removeComponent(JComponent component) {
614
614
		int originalSize = this.panel.getComponentCount();
615
615
		this.panel.remove(component);
616
616
		int newSize = this.panel.getComponentCount();
617
617
		if (originalSize != newSize+1) {
618
618
			throw new NoSuchElementException("The given component does not exist in the GUI.");
619
619
		}
620
620
		this.updateWindow();
621
621
	}
622
622
	/**
623
623
	 * Prompts the user with a file chooser dialog.
624
624
	 * By calling this method, the user will be presented with a file chooser
625
625
	 * dialog, out of which a single file can be selected. If the selected file
626
626
	 * exists, a File object is returned, a null pointer if the user cancelled.
627
627
	 * @return A File object representing the file the user selected, or null
628
628
	 * otherwise.
629
629
	 */
630
630
	public File openFileChooserDialog() {
631
631
		JFileChooser fileDialog = new JFileChooser();
632
632
		fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
633
633
634
634
		int userResponse = fileDialog.showOpenDialog(this.panel);
635
635
		if(userResponse == JFileChooser.APPROVE_OPTION) {
636
636
			return fileDialog.getSelectedFile();
637
637
		}
638
638
		else {
639
639
			return null;
640
640
		}
641
641
	}
642
642
643
643
	/** Prompts the user with a message dialog.
644
644
	 * This method creates a dialog that is purely informative. As such, it
645
645
	 * only presents the user with the option to confirm it.
646
646
	 * @param text The text to be presented in the dialog box.
647
647
	 * @pre text mustn't be null or an empty string.
648
648
	 * @throws NullPointerException if text is a null pointer.
649
649
	 * @throws IllegalArgumentException if text is an empty string.
650
650
	 */
651
651
	public void messageDialog(String text) {
652
652
		if(text.isEmpty())
653
653
			throw IllegalArgumentException("text mustn't be an empty string.");
654
-
		JOptionPane.showMessageDialog(this.frame, text);
+
654
		JOptionPane.showMessageDialog(this.frame, text);
655
655
	}
656
656
657
657
	/**
658
658
	 * Closes this window.
659
659
	 * This method tells this Window's JFrame that a closing event has been
660
660
	 * fired.
661
661
	 * Use this method when you need to programatically close this window.
662
662
	 */
663
663
	public void close() {
664
664
		this.frame.dispatchEvent(new WindowEvent(this.frame, WindowEvent.WINDOW_CLOSING));
665
665
	}
666
666
}
667
667

Challenge 6/ontwerpkeuzes2.md

9 additions and 0 deletions.

View changes Hide changes
1
1
2
2
## GRASP
3
3
Om zoveel mogelijk tegemoet te komen aan de GRASP-richtlijnen, heb ik volgende
4
4
ontwerpkeuzes toegepast voor de verschillende richtlijnen.
5
5
6
6
### High cohesion & Low coupling
7
7
- Veel classes zijn totaal van elkaar losgekoppeld. Een goed voorbeeld hiervan
8
8
  is Bed; Bed weet niet over dat er Reservaties bestaan, zelfs niet in welke
9
9
  Room het 'staat'.
10
10
- Als er toch gekoppeld moet worden, dan blijft dit tot een minimum beperkt; Een
11
11
  Room weet wel welke Bedden het heeft, maar niet hoe deze gebruikt worden, een
12
12
  Reservation weet wel welke Bedden voor hem gereserveerd zijn, maar heeft geen
13
13
  weet van welke kamer dat deze staat.
14
14
- Uitzondering op deze regel vormen de Controller-classes; omwille van hun taken
15
15
  zijn deze vaak verplicht om van veel verschillende classes op de hoogte te
16
16
  zijn.
17
17
18
18
### Indirectie
19
19
- In plaats van functionaliteit toe te wijzen aan class X om met class Y te
20
20
  interageren, wordt er vaak gebruik gemaakt van een class Z, die deze
21
21
  verantwoordelijkheid op zich neemt.
22
22
- Dit wordt toegepast d.m.v. een MVC-structuur, waarin de \*Controller-classes
23
23
  als 'tussenpersoon' werken.
24
24
- Dit komt ook tegemoet aan de eigenschappen van Information Expertise.
25
25
26
26
### Creator
27
27
- Controllers staan ook in als zgn. Creators van de models die ze beheren. Ze
28
28
  voldoen dan ook aan de eigenschappen zoals
29
29
  [hier](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Creator)
30
30
  opgesomd wordt.
31
31
32
32
33
33
## SOLID
34
34
35
35
### Single Responsibility
36
36
37
37
## Algemene keuzes ter bevordering codekwaliteit
38
38
39
39
## Strings, integers, ... i.p.v. Enums/Eigen classes
40
40
In het programma zijn er sommige members die doen vermoeden dat ze beter als
41
41
Enums aangeduid kunnen worden; Het type van een kamer, faciliteiten, ...
42
42
43
43
In plaats van een speciale class hiervoor te maken, heb ik gekozen om deze
44
44
gewoon te behandelen als simpele Strings in lijsten.
45
45
46
46
Het voordeel aan deze werkwijze, is dat Strings in Java SE quasi universeel
47
47
voorkomen; data kunnen geconverteerd worden naar Strings (en vice versa), veel
48
48
gebruikte GUI-libraries (waaronder Swing, JavaFX, ...) gebruiken Strings voor
49
49
tekst voor te stellen in de GUI-widgets, ...
50
50
Daarnaast weet iedereen die ook maar een beetje geschoold is in Java, direct hoe
51
51
deze members behandeld kunnen worden. Had ik een class gemaakt die bv.
52
52
RoomFacilities heette, dan is die parate kennis over de taal zelf nutteloos.
53
53
54
54
Strings zijn ook voorzien van extreem veel methods, en zijn dus zeer flexibele
55
55
classes om te gebruiken. Het feit dat ze zo makkelijk doorheen het programma te
56
56
gebruiken zijn is dus een groot pluspunt.
57
57
58
58
Als dat nog niet genoeg is: Het gebruiken van de hulpmiddelen uit de _standard
59
59
library_ is niet enkel waarvoor libraries überhaupt bestaan, het beperkt ook
60
60
het benodigde aantal classes in de eigen software, waardoor het geheel
61
61
uiteindelijk veel overzichtelijker blijft, en bijgevolg, makkelijk uit te
62
62
breiden, te testen, te begrijpen, ...
63
63
64
64
Al deze voordelen graan grotendeels verloren als ik beslis om zelf
65
65
gespecialiseerde classes op te stellen.
66
66
Men kan misschien stellen dat "de voordelen die het schrijven van eigen
67
67
classes veel beter tegemoet komt aan de essentie van object-georiënteerd
68
68
programmeren, dan zich _beperken_ tot slechts een handvol generieke classes, die
69
69
misschien niet volledig aan de benodigdheden beantwoorden".
70
70
71
71
Toch laat ik mijn afweging overslaan in het voordeel van minder classes, en meer
72
72
uniformiteit. _Sometimes, less is more._ Meer classes betekent ook:
73
73
- Meer onderhoud
74
74
- Meer kans op bugs
75
75
- Groter programma
76
76
77
77
En al die problemen resulteren op lange termijn in:
78
78
- Slechtere uitbreidbaarheid
79
79
- Slechtere onderhoudbaarheid
80
80
- Slechtere schaalbaarheid
81
81
82
82
Zijn al die problemen de moeite van een (zogezegd) "beter object-georiënteerd
83
83
design" wel waard?
84
84
Veel mensen stellen juist dat OOP net voordelig is als men op zoek is naar
85
85
- Uitbreidbaarheid
86
86
- Onderhoudbaarheid
87
87
- Modulariteit
88
88
- Schaalbaarheid
89
89
90
90
Als de voordelen van het paradigma verdwijnen als ik dat "beter design" volg,
91
91
is dat design dan wel echt beter?
92
92
93
93
94
94
**Conclusie: Uniforme classes zijn volgens mij soms beter dan een stel
95
95
gespecialiseerde classes. Daarom dat ik mij soms behelp met Strings, intergers,
96
96
... i.p.v. zelf een oplossing te schrijven.**
97
97
98
98
99
99
### Null pointers
100
100
Het valt misschien op dat ik doorheen mijn programma in veel contracten eis dat
101
101
geen enkele parameter een zgn. *null pointer* is.
102
102
103
103
Het gebruik van null pointers staat garant voor een overvloed aan moeilijk te
104
104
vinden bugs die (door het design van objectgeörienteerd programmeren) enorm diep
105
105
kunnen doorpropageren.
106
106
107
107
Ik maak er een kerntaak van dat, als er aan mijn programma gewerkt wordt, de
108
108
programmeur zichzelf steeds kan garanderen dat **alle** data die hij
109
109
ontvangt, valide data is.
110
110
Op deze manier valt er een hele last van de schouders van de programmeur; een
111
111
reeks fouten worden voorkomen, simpelweg door een strikt schema aan te houden.
112
112
113
113
Het controleren op null pointers wordt op 2 manieren gedaan:
114
114
- Gebruik van *methods* aanwezig in het gegeven type. Als de gegeven variabele
115
115
  een null pointer is, zal het programma direct crashen, en een
116
116
  NullPointerException geven.
117
117
- Expliciet testen of *var == null*. Wordt toegepast op parameters die direct
118
118
  als members opgeslagen dienen te worden.
119
119
120
120
Deze (contractuele) controle laat toe dat, mocht er een null pointer gebruikt
121
121
worden, het programma de programmeur hiervan direct op de hoogte stelt, en dit
122
122
op het laagst mogelijke niveau (namelijk de eerste method waar deze waarde
123
123
gebruikt wordt).
124
124
125
125
### Cloning
126
126
members 'private' maken (encapsuleren) is volledig nutteloos als men getters en
127
127
setters op deze members toepast; In Java worden references doorgegeven (m.u.v.
128
128
primitives), die de hele notie van encapsulatie voorbijgaan (bij sommige types).
129
129
Een voorbeeld hiervan is het privatiseren van een Set<T>-member: men kan daar
130
130
een 'getSet()'-method op plaatsen, en dan toch de inhoud van deze 'private'
131
131
aanpassen.
132
132
133
133
Ik heb geopteerd om, waar van toepassing, deze variabelen te 'clonen', om zo
134
134
exacte kopieën terug te geven.
135
135
Deze manier van werken brengt enkele voordelen teweeg:
136
136
- Zeer defensief programmeren; De ontwikkelaar kan geen members aanpassen als
137
137
  dat niet de bedoeling was
138
138
- Duidelijkheid code: getSet().clear() zal de member niet meer leegmaken. Om dat
139
139
  te doen, moet men gebruikmaken van de method die daarvoor bedoeld is:
140
140
  setSet(clearedSet)
141
141
142
142
### Inheritance
143
143
Overerving is een goed concept over het algemeen, maar **niet** in OOP.
144
144
De problemen omtrent impliciet gedrag en onnodige *state* zijn al te vaak
145
145
beschreven met OOP-inheritance.
146
146
147
147
Ik heb in mijn programma geen gebruik gemaakt van inheritance, exact omwille van
148
148
de problemen die het voortbrengt, zeker in termen van herbruikbaarheid en
149
149
robuustheid, wat toch zware vereisten waren voor deze opdracht.
150
150
151
151
Ik heb al mijn problemen makkelijk kunnen oplossen d.m.v. compositie.
152
152
153
153
### Benaming variabelen
154
154
Doorheen mijn programma maak ik heel veel gebruik van dezelfde benamingen.
155
155
Bijvoorbeeld: Een variabele van het type Reservation zal haast altijd
156
156
'reservation' heten, een Set van een bepaald type zal de naam van datzelfde type
157
157
dragen, in het meervoud, ...
158
158
159
159
Sommige programmeurs gebruiken liever afkortingen (bv. 'reservation' -->
160
160
'resv'), omdat dit sneller schrijft.
161
161
162
162
Naar mijn mening moet men bij deze werkwijze inleveren aan leesbaarheid, vooral
163
163
wanneer iemand die nog nooit met de code gewerkt heeft, dit programma moet
164
164
overnemen.
165
165
166
166
Daarnaast zorgt de consistentie van woordgebruik ervoor dat een andere
167
167
programmeur, doorheen het hele programma dezelfde context in zijn/haar gedachten
168
168
kan gebruiken.
169
169
170
170
171
171
172
172
173
173
------------------------------------------------------------ TODO
174
174
## Toepassing types/classes
175
175
Doorheen het programma heb ik getracht zoveel mogelijk gebruik te maken van
176
176
types/classes die
177
177
- Veelvoorkomend zijn in de Java STL (Zoals String, Set, List, ...)
178
178
- primitief zijn (ints, ...), omdat deze operatoren hebben en de code
179
179
  leesbaarder maken
180
180
181
181
Een goed voorbeeld hiervan zijn bv. de faciliteiten:
182
182
I.p.v. een aparte "Facility"-class te maken, heb ik de verschillende
183
183
faciliteiten voorgesteld door een simpele String. De voordelen van deze aanpak
184
184
zijn ook direct duidelijk:
185
185
- Betekenis is direct duidelijk; de faciliteit letterlijk in de code vernoemd
186
186
- Makkelijke interactie met GUI, die sowieso Strings vraagt voor bv. JLabel
187
187
- Uitbreidbaarheid wordt bekomen door simpelweg een nieuwe String te
188
188
  introduceren
189
189
190
190
## View en GUI
191
191
Werken met GUI's is vaak tijdrovend en veroorzaakt snel errors, zeker met bv.
192
192
anonieme methods, exceptions, ...
193
193
Alhoewel mijn programma grotendeels in een MVC-stijl is geschreven, maken de
194
194
view-classes (RegistrationView, SearchView, ...) achterliggend gebruik van een
195
195
zelfgemaakt framework om makkelijk vensters te maken.
196
196
Dit kleine framework is een persoonlijk hobbyproject dat ik JSugar noem.
197
197
Het biedt een heleboel voordelen, vergeleken met elk GUI-venster zelf opstellen:
198
198
- Vaak gebruikte GUI-widgets (zoals een label, textfield) worden aangemaakt en
199
199
  toegevoegd door slechts 1 method op te roepen
200
200
- JSugar maakt gebruik van reflectie om op een leesbare en uitbreidbare manier
201
201
  knoppen te activeren:
202
202
  public JButton createButton(String text, String action, String methodName, Object triggerObject)
203
203
  'methodName' is een simpele String, en 'triggerObject' is het object waar deze
204
204
  method moet worden opgeroepen.
205
205
- Automatische uitlijning van widgets
206
206
Voor meer informatie kunt u JSugar-readme.md raadplegen.
207
207
208
208
## java.util.Date
209
209
210
210
Doorheen het programma maak ik vaak gebruik van de Date-class uit de Java STL,
211
211
om de volgende redenen:
212
212
- Uitbreidbaarheid: Door overal eenzelfde type te gebruiken, is het gemakkelijk
213
213
  om nieuwe modules toe te voegen, zonder dat daarvoor abstractielagen etc.
214
214
  nodig zijn.
215
215
- Uniformiteit: Eenzelfde type uit de STL laat de ontwikkelaar toe om door het
216
216
  hele programma heen hetzelfde denkpatroon aan te houden; een
217
217
  Stringvoorstelling hier, een integer daar, ... veroorzaken problemen en bugs,
218
218
  die met deze class voorkomen worden.
219
219
220
220
## Bedden
+
221
+
222
Het moeilijkste om te implementeren waren de data voor ontbijt.
+
223
Er wordt voor de reservaties steeds een set bijgehouden van de data waarop
+
224
ontbijt besteld is.
+
225
Op zich is dit niet zo moeilijk. Het moeilijke hieraan, is de gebruiker van het
+
226
programma op een begrijpelijke manier deze data te laten invoeren.
+
227
Data worden ingevoerd via tekstvelden, omdat Swing geen zgn. "DatePicker" heeft.
+
228
+
229
## Bedden
221
230
222
231
Ik heb voor bedden een aparte class aangemaakt, omdat deze een bepaalde state
223
232
bijhouden, nml. wanneer ze gereserveerd zijn.
224
233
225
234
226
235
## Reservation
227
236
Voor de reservaties heb ik besloten om enkel die data bij te houden die inherent
228
237
gerelateerd is aan die reservatie:
229
238
- Enkel gereserveerde bedden i.p.v. gereserveerde kamers. Qua uitbreidbaarheid
230
239
  heeft dit tot gevolg dat men gemakkelijk kan uitbreiden naar reservaties,
231
240
  gespreid over verschillende kamers.
232
241
233
242
234
243
## ReservationView
235
244
Merk op hoe in ReservationView de data van de Reservation direct in de GUI wordt
236
245
geplaatst.
237
246
Dit staat toe om zeer gemakkelijk deze class te hergebruiken voor zowel het
238
247
**aanmaken** als het **updaten** van de reservatie.
239
248