OOP2

Further refactor of Reservation system in Challenge 6

Author
Vngngdn
Date
Dec. 12, 2016, 12:30 p.m.
Hash
212ab469ae6defcc415d9558e2bf77a3bb06039f
Parent
607ccd02260ec7cfe256edf8c459db78dd6d2feb
Modified files
Challenge 6/Reservation.java
Challenge 6/ReservationController.java

Challenge 6/Reservation.java

1 addition and 0 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
41
	private Date end;
42
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
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
64
	 * @param end The date that the Reservation ends.
65
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
75
	 * @pre All dates in breakfastDays must fall between begin and end.
76
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) {
84
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
89
			throw IllegalArgumentException("The begin date occurs after the end date.");
90
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
101
				throw IllegalArgumentException("One of the breakfast days occurs before/after the reservation period.");
102
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
108
		this.endDate = end;
109
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
	 * Checks whether the amount of people corresponds with the reserved Beds.
118
118
	 * Call this method whenever a change in the amount of Beds, or the amount
119
119
	 * of people is made, or, whenever you need to assure consistency.
120
120
	 *
121
121
	 * It also prints a warning to system.out.err to inform about a discrepancy,
122
122
	 * should one be found.
123
123
	 *
124
124
	 * @return True if the counts are consistent, false otherwise.
125
125
	 */
126
126
	private boolean checkPeopleCountConsistency() {
127
127
		int people = this.getPeople();
128
128
		int beds = this.getReservedBeds().size();
129
129
		if(people != beds) {
130
130
			system.out.err("There's a discrepancy in the amount of people in Reservation"+
131
131
					this.reservationID +":");
132
132
			system.out.err("People: "+String.valueOf(people));
133
133
			system.out.err("Reserved Beds: "+String.valueOf(beds));
134
134
			return false;
135
135
		}
136
136
		return true;
137
137
	}
138
138
139
139
	/**
140
140
	 * Set the group name for this Reservation.
141
141
	 * @param groupName The new group name.
142
142
	 * @pre groupName mustn't be empty, or a null pointer.
143
143
	 * @post The group name is changed to the given name.
144
144
	 * @throws NullPointerException if groupName is a null pointer.
145
145
	 * @throws IllegalArgumentException if groupName is an empty String.
146
146
	 */
147
147
	public void setGroupName(String groupName) {
148
148
		if(groupName.isEmpty())
149
149
			throw IllegalArgumentException("groupName is an empty String.");
150
150
		this.groupName = groupName;
151
151
	}
152
152
153
153
	/**
154
154
	 * Retrieve the current group name.
155
155
	 * @return The group name of this Reservation.
156
156
	 */
157
157
	public String getGroupName() {
158
158
		return this.groupName;
159
159
	}
160
160
161
161
	/**
162
162
	 * Get amount of people in this Reservation.
163
163
	 * @post A warning will be printed to system.out.err if the amount of people
164
164
	 * is inconsistent with the amount of reserved Beds.
165
165
	 * @see Reservation.checkPeopleCountConsistency
166
166
	 * @return The amount of people in this Reservation.
167
167
	 */
168
168
	public int getPeople() {
169
169
		return this.people;
+
170
		return this.people;
170
171
	}
171
172
172
173
	/**
173
174
	 * Set the amount of people for this Reservation.
174
175
	 * Note that this method will not notify you if the new amount of people is
175
176
	 * equal to the previous amount.
176
177
	 * 
177
178
	 * This method will print
178
179
	 * @param people The new amount of people in this Reservation.
179
180
	 * @pre people must be at least 1.
180
181
	 * @post A warning will be printed to system.out.err if the amount of people
181
182
	 * is inconsistent with the amount of reserved Beds.
182
183
	 * @post The amount of people is changed to the given value.
183
184
	 * @throws IllegalArgumentException if people is less than 1.
184
185
	 */
185
186
	public void setPeople(int people) {
186
187
		if(people < 1) {
187
188
			throw IllegalArgumentException("people must be at least 1.");
188
189
		}
189
190
		this.people = people;
190
191
		this.checkPeopleCountConsistency();
191
192
	}
192
193
	/**
193
194
	 * Set the begin date for this Reservation.
194
195
	 * @param begin The new begin date.
195
196
	 * @pre begin mustn't be a null pointer.
196
197
	 * @pre begin must come strictly before the end date.
197
198
	 * @post The begin date is updated accordingly.
198
199
	 * @throws NullPointerException if begin is a null pointer.
199
200
	 * @throws IllegalArgumentException if begin comes after the end date.
200
201
	 */
201
202
	public void setBegin(Date begin) {
202
203
		if(!begin.before(this.getEnd()))
203
204
			throw IllegalArgumentException("begin comes after the end date.")
204
205
		this.begin = begin;
205
206
	}
206
207
207
208
	
208
209
209
210
	/**
210
211
	 * Set the end date for this Reservation.
211
212
	 * @param end The new end date.
212
213
	 * @pre end mustn't be a null pointer.
213
214
	 * @pre end must come strictly after the begin date.
214
215
	 * @post The end date is updated accordingly.
215
216
	 * @throws NullPointerException if end is a null pointer.
216
217
	 * @throws IllegalArgumentException if end comes after the end date.
217
218
	 */
218
219
	public Date getDate() {
219
220
		return date;
220
221
	}
221
222
222
223
	public void setReservedBeds(Set<Bed> reservedBeds) {
223
224
		this.reservedBeds = reservedBeds;
224
225
		this.checkPeopleCountConsistency();
225
226
	}
226
227
227
228
	public Set<Bed> getReservedBeds() {
228
229
		return reservedBeds;
229
230
		this.checkPeopleCountConsistency();
230
231
	}
231
232
232
233
	// TODO: Write documentation for all of these, even though it's all mostly
233
234
	// copy/pasting. pfffff
234
235
	public void setReservationID(int reservationID) {
235
236
		this.reservationID = reservationID;
236
237
	}
237
238
238
239
	public int getReservationID() {
239
240
		return reservationID;
240
241
	}
241
242
242
243
	public void setRoomType(String roomType) {
243
244
		this.roomType = roomType;
244
245
	}
245
246
246
247
	public String getRoomType() {
247
248
		return roomType;
248
249
	}
249
250
250
251
	public void setRoomFacilities(Set<String> roomFacilities) {
251
252
		this.roomFacilities = roomFacilities;
252
253
	}
253
254
254
255
	public Set<String> getRoomFacilities() {
255
256
		return roomFacilities;
256
257
	}
257
258
258
259
	/**
259
260
	 * Calculates the price of the Reservation, based on its current state.
260
261
	 * Price table:
261
262
	 * - 20/person/day
262
263
	 *   - 5 less in low season, 5 more in high season
263
264
	 * - 4/breakfast ordered
264
265
	 * - If room is fully booked by the group --> 10% discount
265
266
	 * @return The price in euros.
266
267
	 */
267
268
	public int getPrice() {
268
269
		int totalPrice = 0;
269
270
		// Jan - Apr: Mid
270
271
		// May - Aug: High
271
272
		// Sep - Dec: Low
272
273
		
273
274
		// Calculate bed prices
274
275
		int month = this.getDate().getMonth();
275
276
		int bedPrice = 20;
276
277
		if(month >=8) { // From September:
277
278
			bedPrice -= 5;
278
279
		} else if(month >=4) { // From May:
279
280
			bedPrice += 5;
280
281
		}
281
282
		totalPrice += (this.getReservedBeds().size() * this.getNights() * bedPrice);
282
283
		// Calculate price for breakfasts
283
284
		int breakfasts = this.getBreakfastDays().length;
284
285
		totalPrice += breakfasts * this.getReservedBeds().size();
285
286
		// Check if eligible for discount
286
287
		for(Room room: roomController.getRooms()) {
287
288
			Set<Bed> roomBeds = room.getBeds();
288
289
			if(roomBeds.containsAll(this.reservedBeds)) {
289
290
				double discount = (double)totalPrice * 0.1;
290
291
				totalPrice -= (int)discount;
291
292
			}
292
293
		}
293
294
		return totalPrice;
294
295
	}
295
296
296
297
297
298
298
299
299
300
300
301
301
302
302
303
	public void setBreakfastDays(int[] breakfastDays) {
303
304
		this.breakfastDays = breakfastDays;
304
305
	}
305
306
	public int[] getBreakfastDays() {
306
307
		return this.breakfastDays;
307
308
	}
308
309
309
310
}
310
311

Challenge 6/ReservationController.java

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