OOP2

I did a lot of stuff. Wow. Run diffs if you're curious.

Author
Vngngdn
Date
Dec. 16, 2016, 8:36 a.m.
Hash
f5432883ce7c309c501db3356376950db2e8be5f
Parent
3c3f7b0921297dda9295fdf1db792c697d92e189
Modified files
Challenge 6/Bed.java
Challenge 6/Main.java
Challenge 6/Reservation.java
Challenge 6/ReservationController.java
Challenge 6/ReservationView.java
Challenge 6/Room.java
Challenge 6/RoomController.java
Challenge 6/SearchView.java
Challenge 6/Window.java
Challenge 6/ontwerpkeuzes2.md

Challenge 6/Bed.java

0 additions and 3 deletions.

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

Challenge 6/Main.java

50 additions and 0 deletions.

View changes Hide changes
+
1
import java.util.HashSet;
+
2
import java.util.Random;
+
3
+
4
/**
1
5
 * The program starts here.
2
6
 * @author Maarten Vangeneugden - 1438256
3
7
 */
4
8
public class Main {
5
9
	public static void main(String[] args) {
6
10
		ReservationController rc = new ReservationController();
+
11
		ReservationController rc = new ReservationController();
7
12
		RoomController roc = new RoomController();
8
13
		MainWindow mainWindow = new MainWindow(rc, roc);
+
14
		addTestRooms(roc);
+
15
		MainWindow mainWindow = new MainWindow(rc, roc);
9
16
	}
10
17
}
+
18
	/**
+
19
	 * Generates a series of Rooms to test the program.
+
20
	 * This method is mostly for debugging purposes.
+
21
	 * It features a set of constants in the front of the method, which you can
+
22
	 * edit to your heart's content. =)
+
23
	 * @param roc The Room Controller to which the Rooms will be added.
+
24
	 * @pre roc mustn't be null.
+
25
	 * @throws NullPointerException if roc is a null pointer.
+
26
	 */
+
27
	public static void addTestRooms(RoomController roc) {
+
28
		// Constants; edit to suit your needs
+
29
		final int ROOM_COUNT = 10; // Amount of Rooms that will be generated
+
30
		final int MAX_BEDS = 20; // The maximum amount of Beds in 1 Room
+
31
		final String[] POSSIBLE_FACILITIES = {"Shower", "Curtains", "Airco", "Bath", "Minibar"};
+
32
		final String[] POSSIBLE_TYPES = {"Male", "Female", "Mixed"};
+
33
+
34
		Set<Room> testRooms = new HashSet<>();
+
35
		for(int i=0; i<ROOM_COUNT; i++) {
+
36
			Random random = new Random();
+
37
			int beds = random.nextInt(MAX_BEDS) + 1; // +1, because it's in range [0, MAX_BEDS[
+
38
			String type = POSSIBLE_TYPES[random.nextInt(POSSIBLE_TYPES.length)];
+
39
			Set<String> facilities = new HashSet<>();
+
40
			for(String possibleFacility: POSSIBLE_FACILITIES) {
+
41
				if(random.nextBoolean()) {
+
42
					facilities.add(possibleFacility);
+
43
				}
+
44
			}
+
45
			testRooms.add(new Room(beds, type, facilities));
+
46
			// For debugging purposes, a human readable layout of the Rooms is
+
47
			// printed:
+
48
			System.out.println("ROOM");
+
49
			System.out.println("Beds: "+ beds);
+
50
			System.out.println("Type: "+ type);
+
51
			System.out.print("Facilities: ");
+
52
			for(String facility : facilities) {
+
53
				System.out.print(facility +", ");
+
54
			}
+
55
			System.out.println();
+
56
			System.out.println("-------------------------------");
+
57
		}
+
58
		roc.setRooms(testRooms);
+
59
	}
+
60
}
11
61

Challenge 6/Reservation.java

29 additions and 17 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 beginDate;
41
41
	private Date endDate;
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 numbers
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 beginDate The date that the Reservation begins.
64
64
	 * @param endDate 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 beginDate must come before endDate.
75
75
	 * @pre All dates in breakfastDays must fall between beginDate and endDate.
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 beginDate, Date endDate, Set<Bed> reservedBeds, String roomType, Set<String> roomFacilities, Set<Date> breakfastDays) {
84
84
		// Contract validation:
85
85
		if(people < 1) {
86
86
			throw new IllegalArgumentException("The amount of people should be at least 1.");
87
87
		}
88
88
		if(!beginDate.before(endDate)) {
89
89
			throw new IllegalArgumentException("The begin date occurs after the end date.");
90
90
		}
91
91
		if(groupName.isEmpty() || roomType.isEmpty()) {
92
92
			throw new IllegalArgumentException("groupName and/or roomType were empty strings.");
93
93
		}
94
94
		for(String roomFacility: roomFacilities) {
95
95
			if(roomFacility.isEmpty()) {
96
96
				throw new 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(beginDate) || breakfastDay.after(endDate)) {
101
101
				throw new 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 = beginDate;
108
108
		this.endDate = endDate;
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
	 * 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
-
		this.people = 1;
+
124
		this.people = 1;
125
125
		this.beginDate = new Date();
126
126
		this.endDate = new Date();
127
127
		this.reservedBeds = new HashSet<>();
+
128
		this.endDate.setTime(this.beginDate.getTime() + (1000*60*60*24));
+
129
		this.reservedBeds = new HashSet<>();
128
130
		this.reservationID = 0;
129
131
		this.roomType = "";
130
132
		this.roomFacilities = new HashSet<>();
131
133
		this.breakfastDays = new HashSet<>();
132
134
	}
133
135
134
136
	/**
135
137
	 * Checks whether the amount of people corresponds with the reserved Beds.
136
138
	 * Call this method whenever a change in the amount of Beds, or the amount
137
139
	 * of people is made, or, whenever you need to assure consistency.
138
140
	 *
139
141
	 * It also prints a warning to system.out.err to inform about a discrepancy,
140
142
	 * should one be found.
141
143
	 *
142
144
	 * @return True if the counts are consistent, false otherwise.
143
145
	 */
144
146
	private boolean checkPeopleCountConsistency() {
145
147
		int people = this.getPeople();
146
-
		int beds = this.getReservedBeds().size();
147
-
		if(people != beds) {
+
148
		// infinite recursion.
+
149
		int people = this.people;
+
150
		int beds = this.reservedBeds.size();
+
151
		if(people != beds) {
148
152
			System.err.println("There's a discrepancy in the amount of people in Reservation "+
149
153
					this.reservationID +":");
150
154
			System.err.println("People: "+String.valueOf(people));
151
155
			System.err.println("Reserved Beds: "+String.valueOf(beds));
152
156
			return false;
153
157
		}
154
158
		return true;
155
159
	}
156
160
157
161
	/**
158
162
	 * Set the group name for this Reservation.
159
163
	 * @param groupName The new group name.
160
164
	 * @pre groupName mustn't be empty, or a null pointer.
161
165
	 * @post The group name is changed to the given name.
162
166
	 * @throws NullPointerException if groupName is a null pointer.
163
167
	 * @throws IllegalArgumentException if groupName is an empty String.
164
168
	 */
165
169
	public void setGroupName(String groupName) {
166
170
		if(groupName.isEmpty())
167
171
			throw new IllegalArgumentException("groupName is an empty String.");
168
172
		this.groupName = groupName;
169
173
	}
170
174
171
175
	/**
172
176
	 * Retrieve the current group name.
173
177
	 * @return The group name of this Reservation.
174
178
	 */
175
179
	public String getGroupName() {
176
180
		return this.groupName;
177
181
	}
178
182
179
183
	/**
180
184
	 * Get amount of people in this Reservation.
181
185
	 * @post A warning will be printed to system.out.err if the amount of people
182
186
	 * is inconsistent with the amount of reserved Beds.
183
187
	 * @see Reservation.checkPeopleCountConsistency
184
188
	 * @return The amount of people in this Reservation.
185
189
	 */
186
190
	public int getPeople() {
187
191
		this.checkPeopleCountConsistency();
188
192
		return this.people;
189
193
	}
190
194
191
195
	/**
192
196
	 * Set the amount of people for this Reservation.
193
197
	 * Note that this method will not notify you if the new amount of people is
194
198
	 * equal to the previous amount.
195
199
	 * 
196
200
	 * This method will print
197
201
	 * @param people The new amount of people in this Reservation.
198
202
	 * @pre people must be at least 1.
199
203
	 * @post A warning will be printed to system.out.err if the amount of people
200
204
	 * is inconsistent with the amount of reserved Beds.
201
205
	 * @post The amount of people is changed to the given value.
202
206
	 * @throws IllegalArgumentException if people is less than 1.
203
207
	 */
204
208
	public void setPeople(int people) {
205
209
		if(people < 1) {
206
210
			throw new IllegalArgumentException("people must be at least 1.");
207
211
		}
208
212
		this.people = people;
209
213
		this.checkPeopleCountConsistency();
210
214
	}
211
215
212
216
	/**
213
217
	 * Returns a copy of the begin Date of this Reservation.
214
218
	 * @return a copy of the begin Date of this Reservation.
215
219
	 */
216
220
	public Date getBeginDate() {
217
221
		return (Date)this.beginDate.clone();
218
222
	}
219
223
220
224
	/**
221
225
	 * Set the begin date for this Reservation.
222
226
	 * @param beginDate The new begin date.
223
227
	 * @pre beginDate mustn't be a null pointer.
224
228
	 * @pre beginDate must come strictly before the end date.
225
229
	 * @post The begin date is updated accordingly.
226
230
	 * @throws NullPointerException if beginDate is a null pointer.
227
231
	 * @throws IllegalArgumentException if beginDate comes after the end date.
228
232
	 */
229
233
	public void setBeginDate(Date beginDate) {
230
234
		if(!beginDate.before(this.beginDate))
231
-
			throw new IllegalArgumentException("beginDate comes after the end date.");
+
235
			throw new IllegalArgumentException("beginDate comes after the end date.");
232
236
		this.beginDate = beginDate;
233
237
	}
234
238
235
239
	/**
236
240
	 * Returns a copy of the end Date of this Reservation.
237
241
	 * @return a copy of the end Date of this Reservation.
238
242
	 */
239
243
	public Date getEndDate() {
240
244
		return (Date)this.endDate.clone();
241
245
	}
242
246
243
247
	/**
244
248
	 * Set the end date for this Reservation.
245
249
	 * @param endDate The new end date.
246
250
	 * @pre endDate mustn't be a null pointer.
247
251
	 * @pre endDate must come strictly after the begin date.
248
252
	 * @post The end date is updated accordingly.
249
253
	 * @throws NullPointerException if endDate is a null pointer.
250
254
	 * @throws IllegalArgumentException if endDate comes after the begin date.
251
255
	 */
252
256
	public void setEndDate(Date endDate) {
253
257
		if(!endDate.before(this.endDate))
254
-
			throw new IllegalArgumentException("endDate comes before the begin date.");
+
258
			throw new IllegalArgumentException("endDate comes before the begin date.");
255
259
		this.endDate = endDate;
256
260
	}
257
261
258
262
	public void setReservedBeds(Set<Bed> reservedBeds) {
259
263
		this.reservedBeds = reservedBeds;
260
264
		this.checkPeopleCountConsistency();
261
265
	}
262
266
263
267
	public Set<Bed> getReservedBeds() {
264
268
		return reservedBeds;
265
-
		this.checkPeopleCountConsistency();
266
269
	}
+
270
	}
267
271
268
272
	// TODO: Write documentation for all of these, even though it's all mostly
269
273
	// copy/pasting. pfffff
270
274
	public void setReservationID(int reservationID) {
271
275
		this.reservationID = reservationID;
272
276
	}
273
277
274
278
	public int getReservationID() {
275
279
		return reservationID;
276
280
	}
277
281
278
282
	public void setRoomType(String roomType) {
279
283
		this.roomType = roomType;
280
284
	}
281
285
282
286
	public String getRoomType() {
283
287
		return roomType;
284
288
	}
285
289
286
290
	public void setRoomFacilities(Set<String> roomFacilities) {
287
291
		this.roomFacilities = roomFacilities;
288
292
	}
289
293
290
294
	public Set<String> getRoomFacilities() {
291
295
		return roomFacilities;
292
296
	}
293
297
294
298
	/**
295
299
	 * Calculates the price of the Reservation, based on its current state.
296
300
	 * Price table:
297
301
	 * - 20/person/day
298
302
	 *   - 5 less in low season, 5 more in high season
299
303
	 * - 4/breakfast ordered
300
304
	 * - If room is fully booked by the group --> 10% discount
301
305
	 * @return The price in euros.
+
306
	 * @pre The Room must contain all Beds of this Reservation.
+
307
	 * @pre No parameter may be null.
+
308
	 * @throws IllegalArgumentException if room doesn't contain all Beds.
+
309
	 * @throws NullPointerException if any parameter is a null pointer.
+
310
	 * @return The price in euros.
302
311
	 */
303
312
	public int getPrice() {
304
-
		int totalPrice = 0;
+
313
		// Contract validation
+
314
		if(!room.getBeds().containsAll(this.reservedBeds)) {
+
315
			throw new IllegalArgumentException("room does not have all the Beds of this Reservation.");
+
316
		}
+
317
		// Contract validated
+
318
		int totalPrice = 0;
305
319
		// Jan - Apr: Mid
306
320
		// May - Aug: High
307
321
		// Sep - Dec: Low
308
322
		
309
323
		// Calculate bed prices
310
324
		int month = this.getBeginDate().getMonth();
311
325
		int bedPrice = 20;
312
326
		if(month >=8) { // From September:
313
327
			bedPrice -= 5;
314
328
		} else if(month >=4) { // From May:
315
329
			bedPrice += 5;
316
330
		}
317
331
		// ΔDate represents the difference between the begin and end date.
318
332
		// allows to retrieve the days between them, because the difference is
319
333
		// expressed in milliseconds (nights = ΔDate ÷ 1000 ÷ 60 ÷ 60 ÷ 24)
320
334
		long deltaDate = this.getEndDate().getTime() - this.getBeginDate().getTime();
321
335
		long nights = deltaDate/1000/60/60/24;
322
336
		totalPrice += (this.getReservedBeds().size() * nights * bedPrice);
323
337
		// Calculate price for breakfasts
324
338
		int breakfasts = this.getBreakfastDays().length;
325
-
		totalPrice += breakfasts * this.getReservedBeds().size();
326
-
		// Check if eligible for discount
+
339
		totalPrice += breakfasts * this.getPeople();
+
340
		// Check if eligible for discount
327
341
		for(Room room: roomController.getRooms()) {
328
-
			Set<Bed> roomBeds = room.getBeds();
329
-
			if(roomBeds.containsAll(this.reservedBeds)) {
330
-
				double discount = (double)totalPrice * 0.1;
331
-
				totalPrice -= (int)discount;
332
-
			}
333
-
		}
+
342
		if(roomBeds.containsAll(this.reservedBeds)) {
+
343
			double discount = (double)totalPrice * 0.1;
+
344
			totalPrice -= (int)discount;
+
345
		}
334
346
		return totalPrice;
335
347
	}
336
348
337
349
338
350
339
351
340
352
341
353
342
354
343
355
	public void setBreakfastDays(int[] breakfastDays) {
344
-
		this.breakfastDays = breakfastDays;
+
356
		this.breakfastDays = breakfastDays;
345
357
	}
346
358
	public int[] getBreakfastDays() {
347
-
		return this.breakfastDays;
+
359
		return this.breakfastDays;
348
360
	}
349
361
350
362
}
351
363

Challenge 6/ReservationController.java

24 additions and 11 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
	 * Sets the RoomController.
+
55
	 * @param roomController The RoomController.
+
56
	 * @pre roomController mustn't be a null pointer.
+
57
	 * @throws NullPointerException if roomController is a null pointer.
+
58
	 */
+
59
	public void setRoomController(RoomController roomController) {
+
60
		if(roomController == null) {
+
61
			throw new NullPointerException("roomController mustn't be a null pointer.");
+
62
		}
+
63
		this.roomController = roomController;
+
64
	}
+
65
+
66
	/**
54
67
	 * Generates a unique ID for a new Reservation.
55
68
	 * This method is to be called when a new Reservation is to be stored.
56
69
	 * As every Reservation carries an ID, this method searches for a unique
57
70
	 * one, and returns it.
58
71
	 * @return An integer, different from all other stored Reservation IDs.
59
72
	 */
60
73
	public int generateReservationID() {
61
74
		/* Small optimization idea:
62
75
		 * Instead of starting from 0, and incrementing until it's unique, it
63
76
		 * will take the amount of stored Reservations, and test that as an ID.
64
77
		 * This may still overlap (by removing an older Reservation), but may be
65
78
		 * much faster. Consider implemting and testing for effectiveness if
66
79
		 * generating an ID takes too long.
67
80
		 */
68
81
		int ID = 0;
69
82
		boolean isUnique = false;
70
83
		do {
71
84
			ID++;
72
85
			isUnique = true;
73
86
			for(Reservation reservation: this.reservations) {
74
87
				if(ID == reservation.getReservationID()) {
75
88
					isUnique = false;
76
89
				}
77
90
			}
78
91
		} while(!isUnique);
79
92
		// Test:
80
93
		for(Reservation reservation: this.reservations) {
81
94
			assert reservation.getReservationID() != ID : "Duplicate ID generated!";
82
95
		}
83
96
		return ID;
84
97
	}
85
98
	
86
99
	/** 
87
100
	 * Check if Reservation can be made.
88
101
	 * Call this method whenever you're planning on adding a new Reservation. It
89
102
	 * will check the provided requirements (Room facilities, for example), and
90
103
	 * return whether this Reservation can continue.
91
104
	 * @param reservation The Reservation to check for possible addition.
+
105
	 * This method is deprecated. Use RoomController directly.
+
106
	 * @see RoomController.getQualifiedRoom
+
107
	 * @param reservation The Reservation to check for possible addition.
92
108
	 * @pre reservation mustn't be null.
93
109
	 * @throws NullPointerException if reservation is null.
94
110
	 * @return True if reservation can be added, False otherwise.
95
111
	 */
96
112
	public boolean checkReservation(Reservation reservation) {
+
113
	public boolean checkReservation(Reservation reservation) {
97
114
		if(reservation == null) {
98
115
			throw new NullPointerException("reservation was a null pointer.");
99
116
		}
100
117
		if(this.roomController.getQualifiedRooms(reservation).size() == 0) {
101
-
			return false;
+
118
			return false;
102
119
		}
103
120
		// TODO: Check for other requirements, for example: facilities)	
104
-
		return true;
105
121
	}
106
122
107
123
	/**
108
124
	 * Adds and confirms the reservation.
109
125
	 * By calling this method, the given reservation will be stored in the
110
126
	 * system, and the given room will be filled.
111
127
	 * You are to collect the necessary data, and assure yourself that all
112
128
	 * preconditions have been met.
113
129
	 * 
114
130
	 * The best way to accomplish this, is to only send 'sterile' Reservations
115
131
	 * as the first parameter. That is: Reservations without reserved Beds,
116
132
	 * without prior addition to the active Reservations, etc.
117
133
	 * NOTE: You must check for yourself if the Reservation can take place in
118
134
	 * this Room! If there are not enough Beds available, an exception will be
119
135
	 * thrown.
120
136
	 * @param reservation The Reservation to add.
121
137
	 * @param room The Room in which the people will reside.
122
138
	 * @pre room must have enough empty beds.
123
139
	 * @pre room must accomodate the Reservation's requirements.
124
140
	 * @pre No parameter must be null.
125
141
	 * @pre reservation mustn't already be stored in the active Reservations.
126
142
	 * @post reservation is stored in the active Reservations.
127
143
	 * @post The Beds in the provided Room are reserved for the Reservation's
128
144
	 * period.
129
145
	 * @throws IllegalArgumentException if the given Room can't fulfill the
130
146
	 * requirements of the Reservation, or occupants is less than 1.
131
147
	 * @throws NullPointerException if any parameter is a null pointer.
132
148
	 */
133
149
	public void addReservation(Reservation reservation, Room room) {
134
150
		// Contract validation. Null pointers are implicitely checked by calling
135
151
		// methods.
136
152
		if(reservation.getPeople() > room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).size())
137
-
			throw new IllegalArgumentException("The given Room has not enough empty Beds for the Reservation period.");
138
-
		if(!this.getRoomController().getQualifiedRooms(reservation).contains(room))
139
-
			throw new IllegalArgumentException("The given Room cannot meet all requirements of the Reservation.");
+
153
			throw new IllegalArgumentException("The given Room cannot meet all requirements of the Reservation.");
140
154
		if(this.getReservations().contains(reservation))
141
155
			throw new IllegalArgumentException("The given Reservation was already included in the active Reservations.");
142
156
		// Contract validated
143
157
		this.reservations.add(reservation);
144
158
		Bed[] beds = room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).toArray(new Bed[1]);
145
-
		Set<Bed> reservedBeds = new HashSet<>();
+
159
		Set<Bed> reservedBeds = new HashSet<>();
146
160
		for(int i=0; i<reservation.getPeople(); i++) {
147
161
			beds[i].reservePeriod(reservation.getBegin(), reservation.getEnd());
148
-
			reservedBeds.add(beds[i]);
+
162
			reservedBeds.add(beds[i]);
149
163
		}
150
164
		reservation.setReservedBeds(reservedBeds);
151
165
	}
152
166
153
167
	/**
154
168
	 * Cancels and removes the given Reservation.
155
169
	 * If you want to remove a Reservation, use this method, and provide the
156
170
	 * Reservation up for removal.
157
171
	 * This method will take care of related actions, such as releasing Beds.
158
172
	 * @param reservation The Reservation to be removed.
159
173
	 * @pre reservation mustn't be null.
160
174
	 * @pre reservation must be contained in the active Reservations.
161
175
	 * @post The Reservation is removed from the active Reservations.
162
176
	 * @post The Beds, previously reserved for this Reservation, are now
163
177
	 * released, and can be reserved for another Reservation.
164
178
	 * @throws NullPointerException if reservation is a null pointer.
165
179
	 * @throws IllegalArgumentException if reservation is not contained in the
166
180
	 * active Reservations.
167
181
	 */
168
182
	public void cancelReservation(Reservation reservation) {
169
183
		// Contract validation
170
184
		if(!this.getReservations().contains(reservation)) {
171
185
			throw new IllegalArgumentException("The given Reservation was not contained in the active Reservations.");
172
186
		}
173
187
		if(reservation == null) {
174
188
			throw new NullPointerException();
175
189
		}
176
190
		// Contract validated, execute method
177
191
		this.reservations.remove(reservation); // Remove from active Reservations
178
192
		for(Bed bed: reservation.getReservedBeds()) { // Release reserved Beds
179
193
			bed.removeReservationPeriod(reservation.getBegin(), reservation.getEnd());
180
-
		}
+
194
		}
181
195
		// Asserting post conditions are met
182
196
		assert !this.getReservation().contains(reservation) : "The reservation is still part of the active Reservations.";
183
-
		for(Bed bed: reservation.getReservedBeds()) {
+
197
		for(Bed bed: reservation.getReservedBeds()) {
184
198
			assert bed.isFree(reservation.getBegin(), reservation.getEnd()) : "One or more of the Beds are still reserved.";
185
-
		}
+
199
		}
186
200
	}
187
201
188
-
}
189
202

Challenge 6/ReservationView.java

164 additions and 46 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
import java.text.DateFormat;
+
5
import java.text.DateFormat;
5
6
/**
+
7
/**
6
8
 * Creates a view screen that allows interaction with the reservation.
7
9
 * This window will place the reservation details in editable fields, so its
8
10
 * state can be adapted.
9
11
 * It then also adds a couple of buttons; add/update/cancel, with their
10
12
 * respective actions. These will then be passed to the ReservationController.
11
13
 * It will not allow to add/update a reservation if dates overlap, i.e. beds
12
14
 * can't be reserved for that period.
13
15
 * @author Maarten Vangeneugden - 1438256
14
16
 */
15
17
public class ReservationView {
16
18
17
19
	private Reservation reservation;
18
20
	private Window window;
19
21
20
22
	// Controllers for communication with the rest
21
23
	private ReservationController rc;
22
24
	private RoomController roc;
23
25
24
26
	// GUI widgets
25
27
	private JTextField nameField;
26
28
	private JSpinner amountPeopleField;
27
29
	private JTextField beginDateField;
28
30
	private JTextField endDateField;
29
31
	private JSpinner durationField;
30
32
	private JRadioButton[] typeField;
31
33
	private JCheckBox showerField;
32
34
	private JCheckBox bathField;
33
35
	private JCheckBox minibarField;
34
36
	private JCheckBox aircoField;
35
37
+
38
36
39
37
40
	/**
38
41
	 * Construct a window to edit a Reservation.
39
42
	 * This window presents its caller with the necessary tools to edit the
40
43
	 * given Reservation, and save it in the system.
41
44
	 *
42
45
	 * By sending it an empty Reservation, you can add a new Reservation to the
43
46
	 * system.
44
47
	 * This class is built in a way that allows it to be used for both creating
45
48
	 * new Reservations, and updating/changing existing Reservations.
46
49
	 * Existing Reservations can also be removed by clicking the appropriate
47
50
	 * button.
48
51
	 * @param reservation The Reservation that will be updated.
49
52
	 * @param rc The ReservationController class.
50
53
	 * @param roc The RoomController class.
51
54
	 * @pre No parameter may be a null pointer.
52
55
	 * @post A window is displayed with GUI widgets, which are filled in
53
56
	 * according to the state of reservation.
54
57
	 * @throws NullPointerException if any parameter is a null pointer.
55
58
	 */
56
59
	public ReservationView(Reservation reservation, ReservationController rc, RoomController roc) {
57
60
		// Contract validation
58
61
		if(rc == null || roc == null || reservation == null)
59
62
			throw new NullPointerException("One or more of the given parameters is a null pointer.");
60
63
		// Contract validated
61
64
		this.rc = rc;
62
65
		this.roc = roc;
63
66
64
67
		this.reservation = reservation;
65
68
		this.window = new Window("Reservation screen");
66
69
		this.addFields();
67
70
	}
68
71
69
72
	public void setReservation(Reservation reservation) {
70
73
		this.reservation = reservation;
71
74
	}
72
75
73
76
	public Reservation getReservation() {
74
77
		return reservation;
75
78
	}
76
79
77
80
	/**
78
81
	 * Add the necessary fields to the window.
79
82
	 * This method inspects the Reservation, and copies the included information
80
83
	 * to the widget's content.
81
84
	 */
82
85
	private void addFields() {
83
86
		this.window.createLabel("Group name");
84
87
		this.nameField = window.createTextField(this.reservation.getGroupName());
85
88
		this.window.createLabel("Amount of people");
+
89
		// small.
+
90
		Dimension minimumSize = new Dimension(500, 0);
+
91
		this.nameField.setMinimumSize(minimumSize);
+
92
		this.window.createLabel("Amount of people");
86
93
		this.amountPeopleField = window.createSpinner(1, 20, this.reservation.getPeople(), 1);
87
-
		// Formatting date for the date field:
+
94
		System.out.println("HIER AL");
+
95
		// Formatting date for the date field:
88
96
		this.window.createLabel("Begin date");
89
97
		this.beginDateField = window.createTextField(this.reservation.getBegin().toString());
90
-
		this.window.createLabel("End date");
+
98
		this.window.createLabel("End date");
91
99
		this.endDateField = window.createTextField(this.reservation.getEnd().toString());
92
-
		String[] types = {"Male", "Female", "Mixed"};
+
100
		String[] types = {"Male", "Female", "Mixed"};
93
101
		this.typeField = this.window.createRadioButtons(types);
94
102
		// TODO: Add a test to see if the Reservation has indicated which type
95
103
		// it is, and then already check the corresponding radio button.
96
104
		this.showerField = this.window.createCheckbox("Shower");
97
105
		this.bathField = this.window.createCheckbox("Bath");
98
106
		this.minibarField = this.window.createCheckbox("Minibar");
99
107
		this.aircoField = this.window.createCheckbox("Airco");
100
108
		// TODO: Idem for the facilities, test if already in Reservation, and
101
109
		// check accordingly.
102
110
		this.window.createButton("Add/Update reservation", "", "addReservation", this);
+
111
		this.breakfastDayFields = new ArrayList<>();
+
112
		this.window.createButton("Add/Update reservation", "", "addReservation", this);
103
113
		this.window.createButton("Remove reservation", "", "removeReservation", this);
104
114
	}
+
115
		this.generateBreakfastOptions();
+
116
	}
105
117
106
118
	/**
107
119
	 * Start the addition of this Reservation to the system.
108
-
	 * This method will check the current fields, and update the Reservation
109
-
	 * with those values. In doing so, the validity of the values is checked
110
-
	 * (formatting, begin date before end date, ...).
111
-
	 * If everything is okay, the method will check if these changes are
112
-
	 * possible in the current system; are there enough empty beds?
113
-
	 *
+
120
	 * This method will first inform itself with the BeginDateField and
+
121
	 * EndDateField values.
+
122
	 * If these are filled with correctly formatted dates, the method will
+
123
	 * generate all times that breakfast can be served in the given timespan.
+
124
	 *
114
125
	 * If everything is okay, the changes will be propagated to the
115
-
	 * Reservation's state, and (if it's a new Reservation) will be added to the
116
-
	 * system's active Reservations.
117
-
	 */
+
126
	 * the Reservation itself if there are already breakfasts put in it. If so,
+
127
	 * the checkboxes of those days are checked by default.
+
128
	 * 
+
129
	 * Call this method whenever an update of the days is requested.
+
130
	 */
118
131
	public void addReservation() {
119
-
		// Collect all data from the fields
+
132
		Date beginDate = new Date(Date.parse(this.beginDateField.getText()));
+
133
		Date endDate = new Date(this.endDateField.getText());
+
134
		if(!beginDate.before(endDate)) {
+
135
			System.err.println("Begin date did not occur before end date. No update executed.");
+
136
			return;
+
137
		}
+
138
+
139
		// Time in Date class is represented using milliseconds
+
140
		final long ONE_DAY = 1000*60*60*24;
+
141
		Date breakfastDay = (Date)beginDate.clone();
+
142
		// Breakfast is served at 9 o'clock!
+
143
		breakfastDay.setSeconds(0);
+
144
		breakfastDay.setMinutes(0);
+
145
		breakfastDay.setHours(9);
+
146
		// First check: Can people have breakfast on day of arrival?
+
147
		if(breakfastDay.before(beginDate)) {
+
148
			breakfastDay.setTime(breakfastDay.getTime() + ONE_DAY);
+
149
		}
+
150
		// Removing the previous checkboxes, and creating a new list of them:
+
151
		for(int i=0; i<this.breakfastDayFields.size(); i++) {
+
152
			this.window.removeComponent(this.breakfastDayFields.get(i));
+
153
		}
+
154
		ArrayList<JCheckBox> checkboxes = new ArrayList<>();
+
155
		while(breakfastDay.before(endDate)) {
+
156
			String dateString = 
+
157
				String.valueOf(breakfastDay.getDate()) +"/"+
+
158
				String.valueOf(breakfastDay.getMonth()+1) +"/"+
+
159
				String.valueOf(breakfastDay.getYear()+1900);
+
160
			JCheckBox checkbox = this.window.createCheckbox(dateString);
+
161
			if(this.reservation.getBreakfastDays().contains(breakfastDay)) {// Check if already enabled
+
162
				checkbox.setSelected(true);
+
163
			}
+
164
			checkboxes.add(checkbox);
+
165
			breakfastDay.setTime(breakfastDay.getTime() + ONE_DAY);
+
166
		}
+
167
		// Putting these checkboxes in the appropriate member:
+
168
		this.breakfastDayFields = checkboxes;
+
169
	}
+
170
+
171
	/**
+
172
	 * Get all selected breakfast days.
+
173
	 * Goes over all the current checkboxes concerning the breakfast days, and
+
174
	 * looks which ones are selected.
+
175
	 * The selected ones are put in a Set, which is then returned to the method
+
176
	 * caller.
+
177
	 * @pre generateBreakfastOptions must be run ONCE before calling this
+
178
	 * method.
+
179
	 * @throws NullPointerException if the precondition wasn't met.
+
180
	 * @return A Set containing all dates on which breakfast should be served.
+
181
	 * An empty Set indicates no selected breakfast days. all breakfast days
+
182
	 * occur at 9 o'clock in the morning.
+
183
	 */
+
184
	public Set<Date> getSelectedBreakfastDays() {
+
185
		Set<Date> breakfastDays = new HashSet<>();
+
186
		for(JCheckBox checkbox : this.breakfastDayFields) {
+
187
			if(checkbox.isSelected()) {
+
188
				// Extract information first
+
189
				String[] parts = checkbox.getText().split("\\/");  // Regex for "/"
+
190
				int day = Integer.parseInt(parts[0]);
+
191
				int month = Integer.parseInt(parts[1]);
+
192
				int year = Integer.parseInt(parts[2]);
+
193
				breakfastDays.add(new Date(year+1900, month-1, day, 8, 0, 0));
+
194
			}
+
195
		}
+
196
		return breakfastDays;
+
197
	}
+
198
+
199
	/**
+
200
	 * Update stored Reservation with displayed information.
+
201
	 * This method reads all information displayed in the fields.
+
202
	 * It will then update the Reservation with that data.
+
203
	 * This method will not update the Reservation until all data is correctly
+
204
	 * formatted.
+
205
	 */
+
206
	public void updateReservation() {
+
207
		// Collect all data from the fields
120
208
		String name = this.nameField.getText();
121
209
		Date beginDate = null, endDate = null;
+
210
			this.window.messageDialog("Name mustn't be blank.");
+
211
			return;
+
212
		}
+
213
		Date beginDate = null, endDate = null;
122
214
		try {
123
215
		beginDate = new Date(this.beginDateField.getText());
124
216
		endDate = new Date(this.endDateField.getText());
125
217
		}
126
218
		catch(Exception e) {
127
219
			this.window.messageDialog("Not all date fields were properly formatted.");
128
220
			return;
129
221
		}
130
222
		int people = (Integer)this.amountPeopleField.getValue();
+
223
			this.window.messageDialog("Begin date must come before end date.");
+
224
			return;
+
225
		}
+
226
		int people = (Integer)this.amountPeopleField.getValue();
131
227
		String type = "";
132
228
		for(int i=0; i<this.typeField.length; i++) {
133
229
			if(this.typeField[i].isSelected()) {
134
230
				type = this.typeField[i].getText();
135
231
				break;
136
232
			}
137
233
		}
138
234
		Set<String> facilities = new HashSet<>();
139
235
		if(this.showerField.isSelected()) {
140
236
			facilities.add("Shower");
141
237
		}
142
238
		if(this.bathField.isSelected()) {
143
239
			facilities.add("Bath");
144
240
		}
145
241
		if(this.minibarField.isSelected()) {
146
242
			facilities.add("Minibar");
147
243
		}
148
244
		if(this.aircoField.isSelected()) {
149
245
			facilities.add("Airco");
150
246
		}
151
247
+
248
		// All data collected; save in Reservation.
+
249
		this.reservation.setGroupName(name);
+
250
		this.reservation.setPeople(people);
+
251
		this.reservation.setBeginDate(beginDate);
+
252
		this.reservation.setEndDate(endDate);
+
253
		this.reservation.setRoomType(type);
+
254
		this.reservation.setRoomFacilities(facilities);
+
255
		this.reservation.setBreakfastDays(breakfastDays);
+
256
	}
+
257
152
258
		// TODO breakfast days handling
153
-
154
-
		//Set<Room> possibleRooms = this.roc.getQualifiedRooms(actualDate, duration, type, facilities);
155
-
		// TODO: Refactor upper method to work with the Reservation.
156
-
		// TODO: Implement all checks of valid data above this line!
157
-
		
158
-
		// Data validated; Try finding appropriate beds
159
-
		if(possibleRooms.size() == 0) {
160
-
			boolean tryAgain = this.window.confirmDialog("No rooms met the requirements! Would you like to continue and change the parameters?");
+
259
	 * Start the addition of this Reservation to the system.
+
260
	 * This method will check the current fields, and update the Reservation
+
261
	 * with those values. In doing so, the validity of the values is checked
+
262
	 * (formatting, begin date before end date, ...).
+
263
	 * If everything is okay, the method will check if these changes are
+
264
	 * possible in the current system; are there enough empty beds?
+
265
	 *
+
266
	 * If everything is okay, the changes will be propagated to the
+
267
	 * Reservation's state, and (if it's a new Reservation) will be added to the
+
268
	 * system's active Reservations.
+
269
	 */
+
270
	public void addReservation() {
+
271
		// HACK:
+
272
		// I'm temporarily replacing the real Reservation with a fake one, as to
+
273
		// test whether there is a free Room. If so, we can return the real
+
274
		// Reservation and do the same thing.
+
275
		Reservation tempReservation = this.reservation;
+
276
		this.reservation = new Reservation();
+
277
		this.updateReservation();
+
278
		// XXX: To make sure that, if this is an active Reservation, it's not
+
279
		// counted as being active, we must temporarily remove it from the
+
280
		// active Reservations:
+
281
		if(this.rc.getReservations().contains(tempReservation)) {
+
282
			this.rc.cancelReservation(tempReservation);
+
283
		}
+
284
		// Now checking for a free Room:
+
285
		if(this.roc.getQualifiedRooms(this.reservation).isEmpty()) {
+
286
			boolean tryAgain = this.window.confirmDialog("No rooms met the requirements! Would you like to continue and change the parameters?");
161
287
			if(!tryAgain) {
162
-
				// TODO close window
163
-
			}
+
288
				this.reservation = tempReservation; // Sanitizing what I made dirty =3
+
289
				// We don't need to add the Reservation back, because, if we add
+
290
				// it later, it will be correct nonetheless.
+
291
			}
+
292
			else {
+
293
				this.removeReservation();
+
294
			}
164
295
		}
165
296
		else {
166
297
			Room pickedRoom = null;
167
-
			for(Room room: possibleRooms) {
168
-
				if(room.getEmptyBeds(actualDate, actualEndDate).size() < room.getBeds().size()) {
169
-
					// First, fill the rooms that are partially filled.
170
-
					pickedRoom = room;
171
-
					break;
172
-
				}
173
-
			}
174
-
			if(pickedRoom == null) { // If still no room, pick an empty room
175
-
				for(Room room: possibleRooms) {
176
-
					if(room.getEmptyBeds(actualDate, actualEndDate).size() >= amount) {
177
-
						pickedRoom = room;
178
-
						break;
179
-
					}
180
-
				}
181
-
			}
182
-
			assert pickedRoom != null;
183
-
			// TODO: Set reservation fields here!
184
-
			this.rc.addReservation(reservation, pickedRoom);
185
-
186
-
			// Confirm and show price:
+
298
			// it.
+
299
			this.reservation = tempReservation;
+
300
			this.updateReservation();
+
301
			Room room = this.roc.getQualifiedRoom(this.reservation);
+
302
			this.rc.addReservation(this.reservation, room);
+
303
			// Now closing the window, we're done here
+
304
			// Confirm and show price:
187
305
			int price = this.reservation.getPrice();
188
-
			this.window.confirmDialog("Reservation confirmed! Price: " +String.valueOf(price));
189
-
		}
+
306
			this.window.messageDialog("Reservation confirmed! Price: " +String.valueOf(price));
+
307
			this.window.close();
+
308
		}
190
309
		
191
-
	}
192
310
193
311
	/**
194
312
	 * Remove Reservation and the GUI.
195
313
	 * Calling this method will remove the Reservation, followed by closing this
196
314
	 * window.
197
315
	 * If the Reservation is active (i.e. saved in the ReservationController),
198
316
	 * it will be removed.
199
317
	 * Any changes will be cancelled.
200
318
	 */
201
319
	public void removeReservation() {
202
320
		if(this.rc.getReservations().contains(this.reservation)) {
203
321
			this.rc.cancelReservation(this.reservation);
204
322
		}
205
323
		this.window.close();
206
324
	}
207
325
}
208
326

Challenge 6/Room.java

1 addition and 1 deletion.

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 new 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 new IllegalArgumentException("The given Bed is not in this Room.");
69
69
		}
70
70
		if(bed.hasReservations()) {
71
71
			throw new IllegalArgumentException("The given Bed still has reserved periods.");
72
72
		}
73
73
		if(this.getBeds().size() == 1) {
74
74
			throw new 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 new 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 new 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 new 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

35 additions and 1 deletion.

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
		this.rooms = rooms;
+
26
	}
+
27
+
28
	/**
25
29
	 * Returns all rooms that meet the given requirements.
26
30
	 * This method will search through all rooms, and check which rooms qualify.
27
31
	 * Currently, this method checks for the following things:
28
32
	 * - Is the requested type available?
29
33
	 * - Does the Set of requested facilities form a subset of the Room's
30
34
	 *   facilities?
31
35
	 * - Are there any Beds in the Room that can be reserved in the given
32
36
	 *   period?
33
37
	 * @param reservation The Reservation for which to find eligible rooms.
34
38
	 * @pre reservation mustn't be null.
35
39
	 * @throws NullPointerException if reservation is a null pointer.
36
40
	 * @return A set of all rooms that meet the requirements, or an empty set if
37
41
	 * none were found.
38
42
	 */
39
43
	public Set<Room> getQualifiedRooms(Reservation reservation) {
40
44
		Set<Room> qualifiedRooms = new HashSet<>();
41
45
		for(Room room : this.getRooms()) {
42
46
			if(room.getType().equals(reservation.getRoomType()))
+
47
			if(room.getType().equals(reservation.getRoomType()))
43
48
				continue;
44
49
			if(!room.getFacilities().containsAll(reservation.getRoomFacilities()))
45
50
				continue;
46
51
			if(room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).isEmpty())
47
-
				continue;
+
52
				continue;
48
53
			// The Room fulfills all requirements at this point, so add it to
+
54
			// The Room fulfills all requirements at this point, so add it to
49
55
			// the set
50
56
			qualifiedRooms.add(room);
51
57
		}
52
58
		return qualifiedRooms;
53
59
	}
54
60
}
+
61
	/**
+
62
	 * Returns the Room most suited for the given Reservation.
+
63
	 * What this method does, is passing itself on to the getQualifiedRooms()
+
64
	 * method. Out of the offered options, it picks the most suited Room, based
+
65
	 * on predetermined requests.
+
66
	 * As of writing, return a Room that is already partially filed. If there
+
67
	 * are only unoccupied Rooms, return one of those.
+
68
	 *
+
69
	 * WARNING: This method returns null if no Room is available.
+
70
	 * @return The Room that meets the requirements the best of all available
+
71
	 * Rooms, or null if no suitable Room was found.
+
72
	 */
+
73
	public Room getQualifiedRoom(Reservation reservation) {
+
74
		Set<Room> qualifiedRooms = this.getQualifiedRooms(reservation);
+
75
		if(qualifiedRooms.isEmpty()) return null;
+
76
		for(Room room: qualifiedRooms) {
+
77
			// If Room has less occupants than Beds, then it's partially filled.
+
78
			// getQualifiedRooms() implies there is enough space.
+
79
			if(
+
80
					room.getEmptyBeds(reservation.getBeginDate(), reservation.getEndDate()).size() <
+
81
					room.getBeds().size()) {
+
82
				return room;
+
83
			}
+
84
			// No good Room found, return one of the other Rooms
+
85
		}
+
86
		return (Room)qualifiedRooms.toArray()[0];
+
87
	}
+
88
}
55
89

Challenge 6/SearchView.java

74 additions and 4 deletions.

View changes Hide changes
+
1
import java.util.ArrayList;
+
2
import java.util.Date;
+
3
+
4
/**
1
5
 * Class for creating a screen that allows querying actions.
2
6
 * This program offers the ability to search in the program for Reservations,
3
7
 * Room status, etc.
4
8
 * This class creates a GUI for user interaction.
5
9
 * When matches are found, it will display them. In case of a unique match, that
6
10
 * match is immediately displayed.
7
11
 * @see ReservationView
8
12
 * @author Maarten Vangeneugden - 1438256
9
13
 */
10
14
public class SearchView {
11
15
	private ReservationController rc;
12
16
	private RoomController roc;
13
17
	private Window window;
14
18
15
19
	public SearchView(ReservationController rc, RoomController roc) {
+
20
	private JTextField reservationIDField;
+
21
	private JTextField beginDateField;
+
22
	private JTextField endDateField;
+
23
	private JTextField breakfastDayField;
+
24
+
25
	public SearchView(ReservationController rc, RoomController roc) {
16
26
		this.rc = rc;
17
27
		this.roc = roc;
18
28
19
29
		this.window = new Window("Search screen");
20
30
		this.addFields();
21
31
	}
22
32
	
23
33
	private void addFields() {
24
34
		// TODO: Add fields to private members!
25
35
		this.window.createLabel("Reservation number:");
26
-
		this.window.createTextField("");
27
-
		this.window.createLabel("Group name:");
28
-
		this.window.createTextField("");
29
-
+
36
		this.groupNameField = this.window.createTextField("Search by group name");
+
37
		this.reservationIDField = this.window.createTextField("Search by reservation ID");
+
38
		this.window.createButton("Search reservation", "", "queryReservations", this);
+
39
		this.window.createLabel("Search reserved beds in period (begin-end):");
+
40
		this.beginDateField = this.window.createTextField(new Date().toGMTString());
+
41
		this.endDateField = this.window.createTextField(new Date().toGMTString());
+
42
		this.window.createButton("Get reserved beds", "", "queryBeds", this);
+
43
		this.window.createLabel("Find amount of breakfasts:");
+
44
		this.breakfastDayField = this.window.createTextField(new Date().toGMTString());
+
45
		this.window.createButton("Get breakfasts on given day", "", "queryBreakfasts", this);
+
46
	}
+
47
+
48
	public void queryReservations() {
+
49
		ArrayList<Reservation> foundReservations = new ArrayList<>();
+
50
		String nameQuery = this.groupNameField.getText();
+
51
		String IDQuery = this.reservationIDField.getText();
+
52
		for(Reservation activeReservation : this.rc.getReservations()) {
+
53
			if(activeReservation.getGroupName().contains(nameQuery) &&
+
54
				String.valueOf(activeReservation.getReservationID()).contains(IDQuery)) {
+
55
				foundReservations.add(activeReservation);
+
56
				}
+
57
		}
+
58
		// After collecting all results, offer the user the choice about which
+
59
		// one to take:
+
60
		String[] reservationNames = new String[foundReservations.size()];
+
61
		for(int i=0; i<foundReservations.size(); i++) {
+
62
			String text = foundReservations.get(i).getGroupName() +" ("+
+
63
				String.valueOf(foundReservations.get(i).getReservationID()) +")";
+
64
			reservationNames[i] = text;
+
65
		}
+
66
		int choice = this.window.choiceDialog("Multiple results were found. Specify which one you want to view:", reservationNames);
+
67
		if(choice == -1) { // No result found
+
68
			this.window.messageDialog("No reservations matched the given details.");
+
69
		}
+
70
		else {
+
71
			new ReservationView(foundReservations.get(choice), this.rc, this.roc);
+
72
		}
+
73
	}
+
74
+
75
	public void queryBeds() {
+
76
		// The amount of reserved Beds is determined by the Reservations.
+
77
		// If a Reservation (partially) overlaps with the given time, then this
+
78
		// Bed is reserved in the given period.
+
79
		Date beginDate = new Date(this.beginDateField.getText());
+
80
		Date endDate = new Date(this.endDateField.getText());
+
81
		String result = "";
+
82
		for(Reservation reservation: this.rc.getReservations()) {
+
83
			if(!
+
84
				(reservation.getBeginDate().before(beginDate) && reservation.getEndDate().before(beginDate)) ||
+
85
				(reservation.getBeginDate().after(endDate) && reservation.getEndDate().after(endDate)))
+
86
			{ // This block is only reached if the valid options didn't match.
+
87
				String name = reservation.getGroupName();
+
88
				String beds = String.valueOf(reservation.getReservedBeds().size());
+
89
				String begin = reservation.getBeginDate().toGMTString();
+
90
				String end = reservation.getEndDate().toGMTString();
+
91
				result = result +
+
92
					name +" reserved "+ beds +" between "+ begin +" and "+ end +".\n";
+
93
			}
+
94
+
95
		
+
96
	}
+
97
+
98
	public void queryBreakfasts() {
+
99
30
100
	}
31
101
}
32
102

Challenge 6/Window.java

5 additions 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
209
							 * XXX: Some info on why I don't just throw said
209
210
							 * Exception to the caller:
210
211
							 * Java has this awful language constraint, which
211
212
							 * forces every damn exception that isn't a subclass
212
213
							 * of RuntimeException, to be declared in the method
213
214
							 * declaration. This tends to infect all underlying
214
215
							 * methods as well, and all that for reasons I can't
215
216
							 * comprehend. In order to keep JSugar a simple and
216
217
							 * clean library, I'll rather just handle it here,
217
218
							 * and throw a RuntimeException with appropriate
218
219
							 * details.
219
220
							 */
220
221
							throw new IllegalArgumentException("triggerMethod is not accessible from this context.");
221
222
						}
222
223
					}
223
224
				});
224
225
		this.addComponent(button);
225
226
		return button;
226
227
	}
227
228
228
229
	/**
229
230
	 * Ask the user for input through a dialog box.
230
231
	 * This method presents the user with an input field, that can accept
231
232
	 * textual input. The method will return the given input after the user's
232
233
	 * clicked a button to send.
233
234
	 * @param text The text/question to be asked to the user.
234
235
	 * @return A String, equal to what the user entered.
235
236
	 * @throws NullPointerException if text is a null pointer.
236
237
	 */
237
238
	public String inputDialog(String text) {
238
239
		if (text == null) {
239
240
			throw new NullPointerException("The given text/question was a null pointer.");
240
241
		}
241
242
		return JOptionPane.showInputDialog(text);
242
243
	}
243
244
244
245
	/**
245
246
	 * Give the user a dialog box.
246
247
	 * This method can be used to provide a simple dialog to the user.
247
248
	 * This will show the user the given question, after which a boolean value
248
249
	 * is returned, holding the choice.
249
250
	 * @param text The text/question to be asked to the user.
250
251
	 * @return True if the user confirms, False if he denies.
251
252
	 * @throws NullPointerException if text is a null pointer.
252
253
	 */
253
254
	public boolean confirmDialog(String text) {
254
255
		if (text == null) {
255
256
			throw new NullPointerException("The given text/question was a null pointer.");
256
257
		}
257
258
		final int ACCEPTED = 0;
258
259
		//final int DENIED = 1; // Not used in the current context.
259
260
		int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"});
260
261
		if (result == ACCEPTED) {
261
262
			return true;
262
263
		}
263
264
		else {
264
265
			return false;
265
266
		}
266
267
	}
267
268
268
269
	/**
269
270
	 * Give the user a choice dialog box.
270
271
	 * This method gives the user a simple dialog with predefined choices.
271
272
	 * These choices are to be provided by the caller in a simple array.
272
273
	 *
273
274
	 * Tip: This method works extremely well with arbitrary created choices.
274
275
	 * That is: if the outcome of the dialog is trivial (e.g. Only 1 choice),
275
276
	 * then that value is immediately returned.
276
277
	 * @param text The text/question to be asked to the user.
277
278
	 * @param choices An array of Strings, containing the choices the user can
278
279
	 * pick.
279
280
	 * @return The index value of the picked choice, or -1 if no choices were
280
281
	 * given.
281
282
	 * @throws NullPointerException if text is a null pointer.
282
283
	 */
283
284
	public int choiceDialog(String text, String[] choices) {
284
285
		if (text == null) {
285
286
			throw new NullPointerException("The given text/question was a null pointer.");
286
287
		}
287
288
		// First: handling the trivial cases:
288
289
		if (choices.length == 0) {
289
290
			return -1;
290
291
		}
291
292
		else if (choices.length == 1) {
292
293
			return 0;
293
294
		}
294
295
		int answer = JOptionPane.CLOSED_OPTION;
295
296
		// The dialog needs to be shown again until the user has made a possible
296
297
		// choice, i.e. Chickening out using the close button is not possible
297
298
		// (Because that returns CLOSED_OPTION).
298
299
		while (answer == JOptionPane.CLOSED_OPTION) {
299
300
				JOptionPane.showOptionDialog(
300
-
					null, // The parent component. May become the panel?
+
301
					null, // The parent component. May become the panel?
301
302
					text, // The text/question to describe the goal
302
303
					"Dialog", // The text in the title bar
303
304
					JOptionPane.DEFAULT_OPTION, // The kind of available options
304
305
					JOptionPane.QUESTION_MESSAGE, // The type of message
305
306
					null, // The icon to show
306
307
					choices, // The possible choices
307
308
					choices[0] // The standard choice
308
309
					);
309
310
		}
310
311
		return answer;
311
312
	}
312
313
		
313
314
314
315
	/**
315
316
	 * Creates a label in the GUI for interaction.
316
317
	 * This function offers a convenient way to create a label, that can be
317
318
	 * directly interacted with by the user. After creation, the label itself
318
319
	 * is returned to the caller, if he wishes to do something else with it.
319
320
	 * @param text The text that will be displayed in the label.
320
321
	 * @return The label that was created.
321
322
	 */
322
323
	public JLabel createLabel(String text) {
323
324
		JLabel label = new JLabel(text);
324
325
		this.addComponent(label);
325
326
		return label;
326
327
	}
327
328
328
329
	/**
329
330
	 * Adds a checkbox to the window.
330
331
	 * By providing a String, you can use this method to easily
331
332
	 * create a checkbox, and add it to the window. 
332
333
	 * @param text The text to put next to the checkbox.
333
334
	 * @return The checkbox that was created and added to the GUI.
334
335
	 */
335
336
	public JCheckBox createCheckbox(String text) {
336
337
		JCheckBox checkbox = new JCheckBox(text);
337
338
		this.addComponent(checkbox);
338
339
		return checkbox;
339
340
	}
340
341
341
342
	/**
342
343
	 * Adds radio buttons to the window.
343
344
	 * Given a list of Strings, this method will create the same amount of radio
344
345
	 * buttons.
345
346
	 *
346
347
	 * The radio buttons will silently be grouped in a ButtonGroup object,
347
348
	 * making them automatically disable each other, so only 1 radio button can
348
349
	 * be enabled. This ButtonGroup is immutable.
349
350
	 *
350
351
	 * If you need a mutable ButtonGroup, create your own, and use the 
351
352
	 * {@link #addComponent} method to add the radio buttons manually.
352
353
	 * @param text An array of Strings. The length of the array will determine
353
354
	 * the amount of radio buttons that will be created.
354
355
	 * @return An array of radio buttons, in the same order as text.
355
356
	 */
356
357
	public JRadioButton[] createRadioButtons(String text[]) {
357
358
		JRadioButton[] radioButtons = new JRadioButton[text.length];
358
359
		ButtonGroup buttonGroup = new ButtonGroup();
359
360
		for (int i=0; i<radioButtons.length; i++) {
360
361
			radioButtons[i] = new JRadioButton(text[i]);
361
362
			//radioButtons[i].setText(text[i]);
362
363
			buttonGroup.add(radioButtons[i]);
363
364
			this.addComponent(radioButtons[i]);
364
365
		}
365
366
366
367
		assert radioButtons.length == buttonGroup.getButtonCount() : "The amount of radio buttons ("+ radioButtons.length +") differs from the amount of buttons in buttonGroup ("+ buttonGroup.getButtonCount() +").";
367
368
		return radioButtons;
368
369
	}
369
370
370
371
	public JTextField createTextField(String text) {
371
372
		JTextField textField = new JTextField(text);
372
373
		this.addComponent(textField);
373
374
		return textField;
374
375
	}
375
376
376
377
	/**
377
378
	 * Adds a number spinner component to the GUI.
378
379
	 * This method allows the caller to create a so-called "spinner component"
379
380
	 * to the window. This spinner is an input box, in which only integers can
380
381
	 * be put.
381
382
	 *
382
383
	 * The caller can set a range, a start value, and a step size.
383
384
	 *
384
385
	 * The spinner created with this method may modify itself based on the
385
386
	 * parameters.
386
387
	 * For example: If the minimum and maximum value are equal, the spinner will
387
388
	 * be disabled.
388
389
	 *
389
390
	 * @param minimum The minimum value that can be selected.
390
391
	 * @param maximum The maximum value that can be selected.
391
392
	 * @param start The value that will initially be shown in the component.
392
393
	 * @param stepSize The step size when the user increases/decreases the
393
394
	 * value.
394
395
	 * @throws IllegalArgumentException if minimum is larger than maximum, 
395
396
	 * start is not in the range of the selectable values, or stepsize is not a
396
397
	 * natural number.
397
398
	 * @return The JSpinner that was added to the window.
398
399
	 */
399
400
	public JSpinner createSpinner(int minimum, int maximum, int start, int stepSize) {
400
401
		// As usual, we begin with checking the contract:
401
402
		if(minimum > maximum) {
402
403
			throw new IllegalArgumentException("The minimum value ("+ minimum +") was larger than the maximum value ("+ maximum +")");
403
404
		}
404
405
		// The "start ∉ [minimum, maximum]" is done by the SpinnerNumberModel
+
406
			throw new IllegalArgumentException("The start value("+ start +") was not in the given range ("+ minimum +", "+ maximum +").");
+
407
		}
+
408
		// The "start ∉ [minimum, maximum]" is done by the SpinnerNumberModel
405
409
		// constructor, which will be constructed later.
406
410
		if(stepSize <= 0) { // stepSize ∉ ℕ¹ (In Belgium: ℕ₀)
407
411
			throw new IllegalArgumentException("The stepSize ("+ stepSize +") is not a natural number (excluding 0).");
408
412
		}
409
413
		// If the contract is valid, we can begin:
410
414
		/*
411
415
		 * I'd like to interject here, because this is a nice example of why
412
416
		 * JSugar was a good idea:
413
417
		 * If you want a spinner, you'll typically want an integer spinner. But
414
418
		 * in Swing, when you create a JSpinner, it creates a JSpinner, with a
415
419
		 * predefined 'SpinnerNumberModel' attached to it.
416
420
		 * It's this model you then have to extract from the created spinner, on
417
421
		 * which you need to apply the configuration.
418
422
		 * What you actually have to do, is create a SpinnerNumberModel
419
423
		 * yourself, put your settings on that, and then, create a JSpinner to
420
424
		 * which you give that SpinnerNumberModel.
421
425
		 * In essence: The entire Java framework is shit.
422
426
		 */
423
427
		SpinnerNumberModel spinnerSettings = new SpinnerNumberModel(
424
428
				start,
425
429
				minimum,
426
430
				maximum,
427
431
				stepSize
428
432
				);
429
433
		JSpinner spinner = new JSpinner(spinnerSettings);
430
434
		if(minimum == maximum) { // Trivial value is set already, --> disable.
431
435
			spinner.setEnabled(false);
432
436
		}
433
437
		this.addComponent(spinner);
434
438
		return spinner;
435
439
	}
436
440
437
441
	/**
438
442
	 * Adds a number spinner component to the GUI.
439
443
	 * This method allows the caller to create a so-called "spinner component"
440
444
	 * to the window. This spinner is an input box, in which only integers can
441
445
	 * be put.
442
446
	 * 
443
447
	 * Tip: This method is a convenience method, and works extremely well with
444
448
	 * arbitrary data.
445
449
	 * For example: The start value is automatically set to the minimal possible
446
450
	 * value, and the step size defaults to 1.
447
451
	 * If the minimum and maximum are equal, the component will be disabled, and
448
452
	 * thus, be locked on the only (trivially) possible value.
449
453
	 * If minimum is larger than maximum, the method will notify you of this,
450
454
	 * but also swap the values. So you can rest assured that the spinner will
451
455
	 * handle errors, but also, inform you about it.
452
456
	 * @param minimum The minimum value that can be selected.
453
457
	 * @param maximum The maximum value that can be selected.
454
458
	 * @return The JSpinner component that was added to the window.
455
459
	 */
456
460
	public JSpinner createSpinner(int minimum, int maximum) {
457
461
		// The disabling of equal values is done in the full createSpinner(), so
458
462
		// this is merely switching values if they need to be swapped.
459
463
		if(minimum > maximum) {
460
464
			System.err.println("minimum ("+ minimum +") was larger than maximum ("+ maximum +").");
461
465
			// FIXME: Consider whether it's appropriate to print a stacktrace
462
466
			// here, which may be convenient for debugging.
463
467
			
464
468
			// XXX: I know you don't need the help variable when swapping
465
469
			// integers, because you can also do basic arithmetics. Change it if
466
470
			// it causes too much eye burn for you.
467
471
			int swapValue = minimum;
468
472
			minimum = maximum;
469
473
			maximum = swapValue;
470
474
		}
471
475
472
476
		// Yeah, these 2 variables make you cringe huh, performance addicts?
473
477
		// Drown me in the tears of your useless performance-related opinions.
474
478
		int startValue = minimum;
475
479
		int stepSize = 1;
476
480
		return this.createSpinner(minimum, maximum, startValue, stepSize);
477
481
	}
478
482
479
483
	/**
480
484
	 * Adds a combobox to the GUI.
481
485
	 * Allows the caller to create a combobox by providing the values that
482
486
	 * should be put in it.
483
487
	 *
484
488
	 * This method can only be used for String values. If that is not what you
485
489
	 * need, consider creating your own combobox and adding it manually. Or, if
486
490
	 * you need a combobox for integers, consider {@link #createSpinner}.
487
491
	 *
488
492
	 * WARNING: {@link JComboBox#getSelectedItem} returns an object, not a
489
493
	 * String. You need to manually typecast this. This is a constraint of the
490
494
	 * Swing framework.
491
495
	 * @param items An array of Strings that will be put in the combobox.
492
496
	 * @throws NullPointerException if one of the values in items is a null
493
497
	 * pointer.
494
498
	 * @throws IllegalArgumentException if items is empty.
495
499
	 * @return The JCombobox that was added to the window.
496
500
	 */
497
501
	public JComboBox<String> addComboBox(String[] items) {
498
502
		// Contract validation:
499
503
		if(items.length == 0) {
500
504
			throw new IllegalArgumentException("The given array of items was empty.");
501
505
		}
502
506
		for(String item : items) {
503
507
			if(item == null) {
504
508
				throw new NullPointerException("One of the given Strings is a null pointer.");
505
509
			}
506
510
		}
507
511
		// Contract validated, create the component:
508
512
		JComboBox<String> comboBox = new JComboBox<String>(items);
509
513
		comboBox.setSelectedIndex(0);
510
514
		if(comboBox.getItemCount() == 1) { // Trivial selection
511
515
			comboBox.setEnabled(false);
512
516
		}
513
517
		this.addComponent(comboBox);
514
518
		return comboBox;
515
519
	}
516
520
517
521
	/**
518
522
	 * Creates a list of the given data, and adds it to the GUI.
519
523
	 * This will create a JList component, containing the given data. 
520
524
	 * To jar up your memory: A list in this context, is a component in which
521
525
	 * data of the same type is printed out. The user of said list, can then
522
526
	 * select a subset of these items.
523
527
	 *
524
528
	 * @see JList for a collection of possible operations.
525
529
	 * @param items The String items that will be put in the list.
526
530
	 * @throws NullPointerException if one of the values in items is a null
527
531
	 * pointer.
528
532
	 * @throws IllegalArgumentException if items is empty.
529
533
	 * @return A JList component, that was a added to the GUI.
530
534
	 */
531
535
	public JList createList(String[] items) {
532
536
		// Contract validation:
533
537
		if(items.length == 0) {
534
538
			throw new IllegalArgumentException("The given array of items was empty.");
535
539
		}
536
540
		for(String item : items) {
537
541
			if(item == null) {
538
542
				throw new NullPointerException("One of the given Strings is a null pointer.");
539
543
			}
540
544
		}
541
545
		// Contract validated, create the component:
542
546
		JList list = new JList(items);
543
547
		this.addComponent(list);
544
548
		return list;
545
549
	}
546
550
547
551
	/**
548
552
	 * Creates a table of the given data, and adds it to the GUI.
549
553
	 * This method allows you to create a table with sorting functionality in
550
554
	 * the GUI.
551
555
	 * This method relies on implications, deducted from the given data. That
552
556
	 * is, the length of the rows and columns is calculated by the longest
553
557
	 * length it can find in the nested array.
554
558
	 * To change the data, take a look at the JTable documentation.
555
559
	 *
556
560
	 * @see JTable for a collection of possible operations.
557
561
	 * @param items The String items that will be put in the list.
558
562
	 * @throws NullPointerException if one of the values in items is a null
559
563
	 * pointer.
560
564
	 * @throws IllegalArgumentException if items is empty, or the amount of
561
565
	 * column names does not correspond with the given amount of items.
562
566
	 * @return A JTable component, that was a added to the GUI.
563
567
	 */
564
568
	public JTable createTable(String[] columns, String[][] items) {
565
569
		// Contract validation:
566
570
		if(items.length == 0) {
567
571
			throw new IllegalArgumentException("The given array of items was empty.");
568
572
		}
569
573
		if(columns.length != items[0].length) {
570
574
			throw new IllegalArgumentException("The amount of columns does not correspond to the given amount of items.");
571
575
		}
572
576
		for(int i=0; i<items.length; i++) {
573
577
			if(items[i] == null) {
574
578
				throw new NullPointerException("One of the given Strings is a null pointer.");
575
579
			}
576
580
		}
577
581
		// Contract validated, create the component:
578
582
		// Deducting max length:
579
583
		int columnCount = columns.length;
580
584
		int rowCount = items.length;
581
585
		
582
586
		JTable table = new JTable(items, columns);
583
587
		this.addComponent(table);
584
588
		return table;
585
589
	}
586
590
587
591
	/**
588
592
	 * Adds the given component to the GUI.
589
593
	 * This method allows its caller to give a pre-made component, so that it
590
594
	 * can be added to the GUI. Even though its main use is for the Window class
591
595
	 * itself, the user of JSugar can also use it to create components himself,
592
596
	 * and then add them. As such, this method doesn't provide parameters for
593
597
	 * reflection/action triggering purposes.
594
598
	 * @param component The component to be added to the window.
595
599
	 * @throws NullPointerException if the given component is a null pointer.
596
600
	 */
597
601
	public void addComponent(JComponent component) {
598
602
		int originalSize = this.panel.getComponentCount();
599
603
		this.panel.add(component); // Throws the exception if null.
600
604
		this.updateWindow();
601
605
602
606
		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
607
	}
604
608
605
609
	/**
606
610
	 * Removes the given component from the GUI.
607
611
	 * This method allows its caller to remove a component from the GUI.
608
612
	 * @param component The component to be removed.
609
613
	 * @throws NoSuchElementException if the given component does not exist in
610
614
	 * the GUI.
611
615
	 * @throws NullPointerException if the given component is a null pointer.
612
616
	 */
613
617
	public void removeComponent(JComponent component) {
614
618
		int originalSize = this.panel.getComponentCount();
615
619
		this.panel.remove(component);
616
620
		int newSize = this.panel.getComponentCount();
617
621
		if (originalSize != newSize+1) {
618
622
			throw new NoSuchElementException("The given component does not exist in the GUI.");
619
623
		}
620
624
		this.updateWindow();
621
625
	}
622
626
	/**
623
627
	 * Prompts the user with a file chooser dialog.
624
628
	 * By calling this method, the user will be presented with a file chooser
625
629
	 * dialog, out of which a single file can be selected. If the selected file
626
630
	 * exists, a File object is returned, a null pointer if the user cancelled.
627
631
	 * @return A File object representing the file the user selected, or null
628
632
	 * otherwise.
629
633
	 */
630
634
	public File openFileChooserDialog() {
631
635
		JFileChooser fileDialog = new JFileChooser();
632
636
		fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);
633
637
634
638
		int userResponse = fileDialog.showOpenDialog(this.panel);
635
639
		if(userResponse == JFileChooser.APPROVE_OPTION) {
636
640
			return fileDialog.getSelectedFile();
637
641
		}
638
642
		else {
639
643
			return null;
640
644
		}
641
645
	}
642
646
643
647
	/** Prompts the user with a message dialog.
644
648
	 * This method creates a dialog that is purely informative. As such, it
645
649
	 * only presents the user with the option to confirm it.
646
650
	 * @param text The text to be presented in the dialog box.
647
651
	 * @pre text mustn't be null or an empty string.
648
652
	 * @throws NullPointerException if text is a null pointer.
649
653
	 * @throws IllegalArgumentException if text is an empty string.
650
654
	 */
651
655
	public void messageDialog(String text) {
652
656
		if(text.isEmpty())
653
657
			throw new IllegalArgumentException("text mustn't be an empty string.");
654
658
		JOptionPane.showMessageDialog(this.frame, text);
655
659
	}
656
660
657
661
	/**
658
662
	 * Closes this window.
659
663
	 * This method tells this Window's JFrame that a closing event has been
660
664
	 * fired.
661
665
	 * Use this method when you need to programatically close this window.
662
666
	 */
663
667
	public void close() {
664
668
		this.frame.dispatchEvent(new WindowEvent(this.frame, WindowEvent.WINDOW_CLOSING));
665
669
	}
666
670
}
667
671

Challenge 6/ontwerpkeuzes2.md

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