Major refactor of code + add info about design choices
- Author
- Vngngdn
- Date
- Dec. 16, 2016, 2:38 a.m.
- Hash
- 3c3f7b0921297dda9295fdf1db792c697d92e189
- Parent
- f68e848416b19091f2f20166cbd19ebec9fa4bc3
- Modified files
- Challenge 6/Bed.java
- Challenge 6/Reservation.java
- Challenge 6/ReservationController.java
- Challenge 6/ReservationView.java
- Challenge 6/Room.java
- Challenge 6/RoomController.java
- Challenge 6/Window.java
- Challenge 6/ontwerpkeuzes2.md
Challenge 6/Bed.java ¶
11 additions and 10 deletions.
View changes Hide changes
1 |
1 |
import java.util.Map; |
2 |
2 |
import java.util.HashMap; |
3 |
3 |
|
4 |
4 |
/** |
+ |
5 |
/** |
5 |
6 |
* Class representing a bed in the hostel. |
6 |
7 |
* Even though beds in a hostel could simply be an integer in a Room, these Beds |
7 |
8 |
* need to remember on what dates they are reserved. |
8 |
9 |
* @author Maarten Vangeneugden - 1438256 |
9 |
10 |
*/ |
10 |
11 |
public class Bed { |
11 |
12 |
|
12 |
13 |
/* Rationale: |
13 |
14 |
* I know that a mapping is not necessarily the best solution for storing |
14 |
15 |
* periods, because it doesn't offer built-in protection yadda yadda. |
15 |
16 |
* However, that's Java's fault, as it doesn't provide a Pair<L,R> thing in |
16 |
17 |
* the STL. |
17 |
18 |
* The best solution is actually a collection of tuples: (Begin, End), but |
18 |
19 |
* that doesn't exist in Java, and thus requires a custom implementation. |
19 |
20 |
* All things considered, a Map is the least bad choice. |
20 |
21 |
*/ |
21 |
22 |
private Map<Date, Date> reservedPeriods; |
22 |
23 |
|
23 |
24 |
public Bed() { |
24 |
25 |
this.reservedPeriods = new HashMap<>(); |
25 |
26 |
} |
26 |
27 |
|
27 |
28 |
/** |
28 |
29 |
* Reserves this Bed for the given period. |
29 |
30 |
* This method will mark the given period for this Bed as "reserved", which |
30 |
31 |
* can then be cancelled or queried later. |
31 |
32 |
* @param begin The time where the reservation begins. |
32 |
33 |
* @param end The time where the reservation ends. |
33 |
34 |
* @pre No parameter may be a null pointer. |
34 |
35 |
* @pre The given period mustn't overlap with a period already marked as |
35 |
36 |
* reserved. |
36 |
37 |
* @pre begin must come before end. |
37 |
38 |
* @pre begin and end mustn't be equal. |
38 |
39 |
* @post The given period will be marked as "Reserved", and will have to be |
39 |
40 |
* manually cancelled/removed in order to reserve again. |
40 |
41 |
* @throws NullPointerException if any parameter is a null pointer. |
41 |
42 |
* @throws IllegalArgumentException if begin is equal to, or comes after |
42 |
43 |
* end. |
43 |
44 |
* @throws DateTimeException if the given period overlaps with an already |
44 |
45 |
* reserved period. |
45 |
46 |
*/ |
46 |
47 |
public void reservePeriod(Date begin, Date end) { |
47 |
48 |
if(!begin.before(end)) { |
48 |
49 |
throw IllegalArgumentException("The begin date occurs after the end date."); |
49 |
- | } |
+ |
50 |
} |
50 |
51 |
if(!this.isFree(begin, end)) { |
51 |
52 |
throw DateTimeException("This period overlaps with a reserved period."); |
52 |
- | } |
+ |
53 |
} |
53 |
54 |
// Contract validated, execute method |
54 |
55 |
this.reservedPeriods.put(begin, end); |
55 |
56 |
} |
56 |
57 |
|
57 |
58 |
/** |
58 |
59 |
* Remove a previous registration from this Bed. |
59 |
60 |
* This method will remove/cancel the given reservation period from the |
60 |
61 |
* Bed, opening it up for reservation again. |
61 |
62 |
* @param begin The time where the reservation begins. |
62 |
63 |
* @param end The time where the reservation ends. |
63 |
64 |
* @pre No parameter may be a null pointer. |
64 |
65 |
* @pre The given period must already be marked as "Reserved" in this Bed. |
65 |
66 |
* @pre begin must come before end. |
66 |
67 |
* @pre begin and end mustn't be equal. |
67 |
68 |
* @post The given period will lose its "Reserved" mark, allowing the period |
68 |
69 |
* to be reserved again. |
69 |
70 |
* @throws NullPointerException if any parameter is a null pointer. |
70 |
71 |
* @throws IllegalArgumentException if begin is equal to, or comes after |
71 |
72 |
* end; or, if the given period is not reserved. |
72 |
73 |
*/ |
73 |
74 |
public void removeReservationPeriod(Date begin, Date end) { |
74 |
75 |
if(!begin.before(end)) { |
75 |
76 |
throw IllegalArgumentException("The begin date occurs after the end date."); |
76 |
- | } |
+ |
77 |
} |
77 |
78 |
if(!this.isFree(begin, end)) { |
78 |
79 |
throw DateTimeException("This period overlaps with a reserved period."); |
79 |
- | } |
+ |
80 |
} |
80 |
81 |
// Contract partially validated; further validation occurs while looking |
81 |
82 |
// for the reservation. |
82 |
83 |
// XXX: Check if Java correctly handles equality: 2 different Date |
83 |
84 |
// objects with the same date representation should be equal! |
84 |
85 |
boolean reservationFound = this.reservedPeriods.remove(begin, end); |
85 |
86 |
if(!reservationFound) { |
86 |
87 |
throw IllegalArgumentException("The given period was not marked as reserved."); |
87 |
- | } |
+ |
88 |
} |
88 |
89 |
} |
89 |
90 |
|
90 |
91 |
/** |
91 |
92 |
* Checks whether this Bed can be reserved in the given period. |
92 |
93 |
* Use this method whenever you need to inform yourself about any |
93 |
94 |
* conflicting reservation period. |
94 |
95 |
* @param begin The time where the reservation begins. |
95 |
96 |
* @param end The time where the reservation ends. |
96 |
97 |
* @pre No parameter must be a null pointer. |
97 |
98 |
* @pre begin must come before end. |
98 |
99 |
* @pre begin and end mustn't be equal. |
99 |
100 |
* @throws NullPointerException if any parameter is a null pointer. |
100 |
101 |
* @throws IllegalArgumentException if begin is equal to, or comes after |
101 |
102 |
* end. |
102 |
103 |
* @return True if the given period does not overlap with any reservation, false otherwise. |
103 |
104 |
*/ |
104 |
105 |
public boolean isFree(Date begin, Date end) { |
105 |
106 |
if(!begin.before(end)) { |
106 |
107 |
throw IllegalArgumentException("The begin date occurs after the end date."); |
107 |
- | } |
+ |
108 |
} |
108 |
109 |
if(!this.isFree(begin, end)) { |
109 |
110 |
throw DateTimeException("This period overlaps with a reserved period."); |
110 |
- | } |
+ |
111 |
} |
111 |
112 |
// Contract validated, execute method |
112 |
113 |
for(Map.Entry<Date, Date> reservedPeriod: this.reservedPeriods.EntrySet()) { |
113 |
- | Date reservedBegin = reservedPeriod.key(); |
114 |
- | Date reservedEnd = reservedPeriod.value(); |
115 |
- | /* Forbidden possibilities: |
+ |
114 |
Date reservedBegin = reservedPeriod.getKey(); |
+ |
115 |
Date reservedEnd = reservedPeriod.getValue(); |
+ |
116 |
/* Forbidden possibilities: |
116 |
117 |
* (A,B = reserved; X,Y = requested) |
117 |
118 |
* X-A-Y-B / A-X-B-Y - Begins or ends in a reserved period |
118 |
119 |
* X-A-B-Y - Complete overlapping of reserved period |
119 |
120 |
* Allowed possibilities: |
120 |
121 |
* A-B-X-Y / X-Y-A-B - No overlapping |
121 |
122 |
*/ |
122 |
123 |
if((begin.after(reservedBegin) && begin.before(reservedEnd)) || |
123 |
124 |
(end.after(reservedBegin) && end.before(reservedEnd))) { |
124 |
125 |
// Triggered if any forbidden structure is detected |
125 |
126 |
return false; |
126 |
127 |
} |
127 |
128 |
} |
128 |
129 |
return true; // No overlapping found |
129 |
130 |
} |
130 |
131 |
|
131 |
132 |
/** |
132 |
133 |
* Checks whether this Bed has open reservation periods. |
133 |
134 |
* @return True if this Bed has reserved periods, false otherwise. |
134 |
135 |
*/ |
135 |
136 |
public boolean hasReservations() { |
136 |
137 |
return !this.reservedPeriods.isEmpty(); |
137 |
138 |
} |
138 |
139 |
} |
139 |
140 |
Challenge 6/Reservation.java ¶
63 additions and 41 deletions.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* Represents a reservation in a hostel. |
6 |
6 |
* Reservation is a simple class that allows one to store reservation |
7 |
7 |
* information, request it when necessary, and so on. |
8 |
8 |
* Certain methods are provided for interaction, which use contracts to assert |
9 |
9 |
* proper functioning. |
10 |
10 |
* |
11 |
11 |
* Note: There is a form of redundancy in this class. |
12 |
12 |
* Reservation holds a Set of Beds, and an integer, indicating the amount of |
13 |
13 |
* people that are in this Reservation. |
14 |
14 |
* Normally, the amount of people in the Reservation is determined by the amount |
15 |
15 |
* of reserved Beds, because the amount of Beds implied the amount of people to |
16 |
16 |
* facilitate. |
17 |
17 |
* Because of the design of this program, I've weighed the advantages and |
18 |
18 |
* disadvantages of holding this implication, of using a seperate member |
19 |
19 |
* explicitely indicating the amount of people. |
20 |
20 |
* |
21 |
21 |
* I've chosen the latter option. It scales better in terms of weird |
22 |
22 |
* afterthoughts ("I also want to make people sleep on the floor"), but being |
23 |
23 |
* explicit is also a tad easier to manage. It also allows the other classes to |
24 |
24 |
* be far more decoupled from Reservation (otherwise, they'd have to check for |
25 |
25 |
* errors every time themselves, hindering cohesiveness). |
26 |
26 |
* |
27 |
27 |
* To overcome the possible difference, I've made it so that, every time, the |
28 |
28 |
* relation between the values is changed (the people in the Reservation |
29 |
29 |
* changes, for example), the class automatically checks if the values are |
30 |
30 |
* corresponding. If they're not, a warning is printed to system.out.err, |
31 |
31 |
* informing about the fact that there's a discrepancy. |
32 |
32 |
* |
33 |
33 |
* I think that's the best way to overcome this problem. |
34 |
34 |
* @author Maarten Vangeneugden - 1438256 |
35 |
35 |
*/ |
36 |
36 |
public class Reservation { |
37 |
37 |
|
38 |
38 |
// Members |
39 |
39 |
private String groupName; |
40 |
40 |
private Date begin; |
41 |
- | private Date end; |
42 |
- | private Set<Bed> reservedBeds; |
+ |
41 |
private Date endDate; |
+ |
42 |
private Set<Bed> reservedBeds; |
43 |
43 |
private int people; // The amount of people in this Reservation |
44 |
44 |
private int reservationID; |
45 |
45 |
private String roomType; // Room type requested by group |
46 |
46 |
private Set<String> roomFacilities; // Requested room facilities |
47 |
47 |
private Set<Date> breakfastDays; // Set of days the group wants breakfast |
48 |
- | // Controllers |
+ |
48 |
// Controllers |
49 |
49 |
private RoomController roomController; |
50 |
50 |
private ReservationController reservationController; |
51 |
51 |
|
52 |
52 |
/** |
53 |
53 |
* Create a new Reservation. |
54 |
54 |
* Be aware about the limitations of a Reservation: it's not the |
55 |
55 |
* Reservation's duty to check whether the provided Beds are properly |
56 |
56 |
* reserved; take care of this accordingly. |
57 |
57 |
* |
58 |
58 |
* Some information is implicit. For example: The size of the set of |
59 |
59 |
* reserved Beds implies the amount of people in the group; no breakfast |
60 |
60 |
* days implies no breakfast, ... |
61 |
61 |
* @param groupName The name of the group. |
62 |
62 |
* @param people The amount of people that are reserving. |
63 |
63 |
* @param begin The date that the Reservation begins. |
64 |
- | * @param end The date that the Reservation ends. |
65 |
- | * @param reservedBeds The set of Beds reserved and assigned to this |
+ |
64 |
* @param endDate The date that the Reservation ends. |
+ |
65 |
* @param reservedBeds The set of Beds reserved and assigned to this |
66 |
66 |
* Reservation. |
67 |
67 |
* @param roomType The requested room type. |
68 |
68 |
* @param roomFacilities The set of all requested room facilities. |
69 |
69 |
* @param breakfastDays A set of all days that the group wants breakfast. |
70 |
70 |
* @param reservationID An ID for this reservation, to differentiate from |
71 |
71 |
* other Reservations. |
72 |
72 |
* @pre No parameter must be a null pointer. |
73 |
73 |
* @pre people must be a natural number, different from 0. |
74 |
74 |
* @pre begin must come before end. |
75 |
- | * @pre All dates in breakfastDays must fall between begin and end. |
76 |
- | * @pre No string parameter may be empty. |
+ |
75 |
* @pre All dates in breakfastDays must fall between beginDate and endDate. |
+ |
76 |
* @pre No string parameter may be empty. |
77 |
77 |
* @post The amount of people in the Reservation is determined by the amount |
78 |
78 |
* of reserved Beds. |
79 |
79 |
* @throws NullPointerException if any parameter is a null pointer. |
80 |
80 |
* @throws IllegalArgumentException if any of the other preconditions is not |
81 |
81 |
* met. |
82 |
82 |
*/ |
83 |
83 |
public Reservation(String groupName, int people, Date begin, Date end, Set<Bed> reservedBeds, String roomType, Set<String> roomFacilities, Set<Date> breakfastDays) { |
84 |
- | // Contract validation: |
+ |
84 |
// Contract validation: |
85 |
85 |
if(people < 1) { |
86 |
86 |
throw IllegalArgumentException("The amount of people should be at least 1."); |
87 |
- | } |
+ |
87 |
} |
88 |
88 |
if(!begin.before(end)) { |
89 |
- | throw IllegalArgumentException("The begin date occurs after the end date."); |
90 |
- | } |
+ |
89 |
throw new IllegalArgumentException("The begin date occurs after the end date."); |
+ |
90 |
} |
91 |
91 |
if(groupName.isEmpty() || roomType.isEmpty()) { |
92 |
92 |
throw IllegalArgumentException("groupName and/or roomType were empty strings."); |
93 |
- | } |
+ |
93 |
} |
94 |
94 |
for(String roomFacility: roomFacilities) { |
95 |
95 |
if(roomFacility.isEmpty()) { |
96 |
96 |
throw IllegalArgumentException("One of the room facilities was an empty string."); |
97 |
- | } |
+ |
97 |
} |
98 |
98 |
} |
99 |
99 |
for(Date breakfastDay : breakfastDays) { |
100 |
100 |
if(breakfastDay.before(begin) || breakfastDay.after(end)) { |
101 |
- | throw IllegalArgumentException("One of the breakfast days occurs before/after the reservation period."); |
102 |
- | } |
+ |
101 |
throw new IllegalArgumentException("One of the breakfast days occurs before/after the reservation period."); |
+ |
102 |
} |
103 |
103 |
} |
104 |
104 |
// Contract validated, execute constructor |
105 |
105 |
this.groupName = groupName; |
106 |
106 |
this.people = people; |
107 |
107 |
this.beginDate = begin; |
108 |
- | this.endDate = end; |
109 |
- | this.reservedBeds = reservedBeds; |
+ |
108 |
this.endDate = endDate; |
+ |
109 |
this.reservedBeds = reservedBeds; |
110 |
110 |
this.reservationID = reservationID; |
111 |
111 |
this.roomType = roomType; |
112 |
112 |
this.roomFacilities = roomFacilities; |
113 |
113 |
this.breakfastDays = breakfastDays; |
114 |
114 |
} |
115 |
115 |
|
116 |
116 |
/** |
117 |
117 |
* Construct template Reservation. |
118 |
118 |
* Use this constructor if you need to create an empty/new Reservation. |
119 |
119 |
* It omits the standard restrictions (for example, the group name will be |
120 |
120 |
* empty) to accomodate for the expected usage of this Reservation. |
121 |
121 |
*/ |
122 |
122 |
public Reservation() { |
123 |
123 |
this.groupName = ""; |
124 |
124 |
this.people = 1; |
125 |
125 |
this.beginDate = new Date(); |
126 |
126 |
this.endDate = new Date(); |
127 |
127 |
this.reservedBeds = new HashSet<>(); |
128 |
128 |
this.reservationID = 0; |
129 |
129 |
this.roomType = ""; |
130 |
130 |
this.roomFacilities = new HashSet<>(); |
131 |
131 |
this.breakfastDays = new HashSet<>(); |
132 |
132 |
} |
133 |
133 |
|
134 |
134 |
/** |
135 |
135 |
* Checks whether the amount of people corresponds with the reserved Beds. |
136 |
136 |
* Call this method whenever a change in the amount of Beds, or the amount |
137 |
137 |
* of people is made, or, whenever you need to assure consistency. |
138 |
138 |
* |
139 |
139 |
* It also prints a warning to system.out.err to inform about a discrepancy, |
140 |
140 |
* should one be found. |
141 |
141 |
* |
142 |
142 |
* @return True if the counts are consistent, false otherwise. |
143 |
143 |
*/ |
144 |
144 |
private boolean checkPeopleCountConsistency() { |
145 |
145 |
int people = this.getPeople(); |
146 |
146 |
int beds = this.getReservedBeds().size(); |
147 |
147 |
if(people != beds) { |
148 |
148 |
system.out.err("There's a discrepancy in the amount of people in Reservation"+ |
149 |
- | this.reservationID +":"); |
+ |
149 |
this.reservationID +":"); |
150 |
150 |
system.out.err("People: "+String.valueOf(people)); |
151 |
- | system.out.err("Reserved Beds: "+String.valueOf(beds)); |
152 |
- | return false; |
+ |
151 |
System.err.println("Reserved Beds: "+String.valueOf(beds)); |
+ |
152 |
return false; |
153 |
153 |
} |
154 |
154 |
return true; |
155 |
155 |
} |
156 |
156 |
|
157 |
157 |
/** |
158 |
158 |
* Set the group name for this Reservation. |
159 |
159 |
* @param groupName The new group name. |
160 |
160 |
* @pre groupName mustn't be empty, or a null pointer. |
161 |
161 |
* @post The group name is changed to the given name. |
162 |
162 |
* @throws NullPointerException if groupName is a null pointer. |
163 |
163 |
* @throws IllegalArgumentException if groupName is an empty String. |
164 |
164 |
*/ |
165 |
165 |
public void setGroupName(String groupName) { |
166 |
166 |
if(groupName.isEmpty()) |
167 |
167 |
throw IllegalArgumentException("groupName is an empty String."); |
168 |
- | this.groupName = groupName; |
+ |
168 |
this.groupName = groupName; |
169 |
169 |
} |
170 |
170 |
|
171 |
171 |
/** |
172 |
172 |
* Retrieve the current group name. |
173 |
173 |
* @return The group name of this Reservation. |
174 |
174 |
*/ |
175 |
175 |
public String getGroupName() { |
176 |
176 |
return this.groupName; |
177 |
177 |
} |
178 |
178 |
|
179 |
179 |
/** |
180 |
180 |
* Get amount of people in this Reservation. |
181 |
181 |
* @post A warning will be printed to system.out.err if the amount of people |
182 |
182 |
* is inconsistent with the amount of reserved Beds. |
183 |
183 |
* @see Reservation.checkPeopleCountConsistency |
184 |
184 |
* @return The amount of people in this Reservation. |
185 |
185 |
*/ |
186 |
186 |
public int getPeople() { |
187 |
187 |
this.checkPeopleCountConsistency(); |
188 |
188 |
return this.people; |
189 |
189 |
} |
190 |
190 |
|
191 |
191 |
/** |
192 |
192 |
* Set the amount of people for this Reservation. |
193 |
193 |
* Note that this method will not notify you if the new amount of people is |
194 |
194 |
* equal to the previous amount. |
195 |
195 |
* |
196 |
196 |
* This method will print |
197 |
197 |
* @param people The new amount of people in this Reservation. |
198 |
198 |
* @pre people must be at least 1. |
199 |
199 |
* @post A warning will be printed to system.out.err if the amount of people |
200 |
200 |
* is inconsistent with the amount of reserved Beds. |
201 |
201 |
* @post The amount of people is changed to the given value. |
202 |
202 |
* @throws IllegalArgumentException if people is less than 1. |
203 |
203 |
*/ |
204 |
204 |
public void setPeople(int people) { |
205 |
205 |
if(people < 1) { |
206 |
206 |
throw IllegalArgumentException("people must be at least 1."); |
207 |
- | } |
+ |
207 |
} |
208 |
208 |
this.people = people; |
209 |
209 |
this.checkPeopleCountConsistency(); |
210 |
210 |
} |
211 |
211 |
/** |
+ |
212 |
/** |
+ |
213 |
* Returns a copy of the begin Date of this Reservation. |
+ |
214 |
* @return a copy of the begin Date of this Reservation. |
+ |
215 |
*/ |
+ |
216 |
public Date getBeginDate() { |
+ |
217 |
return (Date)this.beginDate.clone(); |
+ |
218 |
} |
+ |
219 |
|
+ |
220 |
/** |
212 |
221 |
* Set the begin date for this Reservation. |
213 |
222 |
* @param begin The new begin date. |
214 |
- | * @pre begin mustn't be a null pointer. |
215 |
- | * @pre begin must come strictly before the end date. |
216 |
- | * @post The begin date is updated accordingly. |
+ |
223 |
* @pre beginDate mustn't be a null pointer. |
+ |
224 |
* @pre beginDate must come strictly before the end date. |
+ |
225 |
* @post The begin date is updated accordingly. |
217 |
226 |
* @throws NullPointerException if begin is a null pointer. |
218 |
- | * @throws IllegalArgumentException if begin comes after the end date. |
219 |
- | */ |
+ |
227 |
* @throws IllegalArgumentException if beginDate comes after the end date. |
+ |
228 |
*/ |
220 |
229 |
public void setBegin(Date begin) { |
221 |
- | if(!begin.before(this.getEnd())) |
222 |
- | throw IllegalArgumentException("begin comes after the end date."); |
223 |
- | this.begin = begin; |
224 |
- | } |
+ |
230 |
if(!beginDate.before(this.beginDate)) |
+ |
231 |
throw new IllegalArgumentException("beginDate comes after the end date."); |
+ |
232 |
this.beginDate = beginDate; |
+ |
233 |
} |
225 |
234 |
|
226 |
235 |
|
227 |
- | |
+ |
236 |
* Returns a copy of the end Date of this Reservation. |
+ |
237 |
* @return a copy of the end Date of this Reservation. |
+ |
238 |
*/ |
+ |
239 |
public Date getEndDate() { |
+ |
240 |
return (Date)this.endDate.clone(); |
+ |
241 |
} |
+ |
242 |
|
228 |
243 |
/** |
229 |
244 |
* Set the end date for this Reservation. |
230 |
245 |
* @param end The new end date. |
231 |
- | * @pre end mustn't be a null pointer. |
232 |
- | * @pre end must come strictly after the begin date. |
233 |
- | * @post The end date is updated accordingly. |
+ |
246 |
* @pre endDate mustn't be a null pointer. |
+ |
247 |
* @pre endDate must come strictly after the begin date. |
+ |
248 |
* @post The end date is updated accordingly. |
234 |
249 |
* @throws NullPointerException if end is a null pointer. |
235 |
- | * @throws IllegalArgumentException if end comes after the end date. |
236 |
- | */ |
+ |
250 |
* @throws IllegalArgumentException if endDate comes after the begin date. |
+ |
251 |
*/ |
237 |
252 |
public Date getDate() { |
238 |
- | return date; |
239 |
- | } |
+ |
253 |
if(!endDate.before(this.endDate)) |
+ |
254 |
throw new IllegalArgumentException("endDate comes before the begin date."); |
+ |
255 |
this.endDate = endDate; |
+ |
256 |
} |
240 |
257 |
|
241 |
258 |
public void setReservedBeds(Set<Bed> reservedBeds) { |
242 |
259 |
this.reservedBeds = reservedBeds; |
243 |
260 |
this.checkPeopleCountConsistency(); |
244 |
261 |
} |
245 |
262 |
|
246 |
263 |
public Set<Bed> getReservedBeds() { |
247 |
264 |
return reservedBeds; |
248 |
265 |
this.checkPeopleCountConsistency(); |
249 |
266 |
} |
250 |
267 |
|
251 |
268 |
// TODO: Write documentation for all of these, even though it's all mostly |
252 |
269 |
// copy/pasting. pfffff |
253 |
270 |
public void setReservationID(int reservationID) { |
254 |
271 |
this.reservationID = reservationID; |
255 |
272 |
} |
256 |
273 |
|
257 |
274 |
public int getReservationID() { |
258 |
275 |
return reservationID; |
259 |
276 |
} |
260 |
277 |
|
261 |
278 |
public void setRoomType(String roomType) { |
262 |
279 |
this.roomType = roomType; |
263 |
280 |
} |
264 |
281 |
|
265 |
282 |
public String getRoomType() { |
266 |
283 |
return roomType; |
267 |
284 |
} |
268 |
285 |
|
269 |
286 |
public void setRoomFacilities(Set<String> roomFacilities) { |
270 |
287 |
this.roomFacilities = roomFacilities; |
271 |
288 |
} |
272 |
289 |
|
273 |
290 |
public Set<String> getRoomFacilities() { |
274 |
291 |
return roomFacilities; |
275 |
292 |
} |
276 |
293 |
|
277 |
294 |
/** |
278 |
295 |
* Calculates the price of the Reservation, based on its current state. |
279 |
296 |
* Price table: |
280 |
297 |
* - 20/person/day |
281 |
298 |
* - 5 less in low season, 5 more in high season |
282 |
299 |
* - 4/breakfast ordered |
283 |
300 |
* - If room is fully booked by the group --> 10% discount |
284 |
301 |
* @return The price in euros. |
285 |
302 |
*/ |
286 |
303 |
public int getPrice() { |
287 |
304 |
int totalPrice = 0; |
288 |
305 |
// Jan - Apr: Mid |
289 |
306 |
// May - Aug: High |
290 |
307 |
// Sep - Dec: Low |
291 |
308 |
|
292 |
309 |
// Calculate bed prices |
293 |
310 |
int month = this.getDate().getMonth(); |
294 |
- | int bedPrice = 20; |
+ |
311 |
int bedPrice = 20; |
295 |
312 |
if(month >=8) { // From September: |
296 |
313 |
bedPrice -= 5; |
297 |
314 |
} else if(month >=4) { // From May: |
298 |
315 |
bedPrice += 5; |
299 |
316 |
} |
300 |
317 |
totalPrice += (this.getReservedBeds().size() * this.getNights() * bedPrice); |
301 |
- | // Calculate price for breakfasts |
+ |
318 |
// allows to retrieve the days between them, because the difference is |
+ |
319 |
// expressed in milliseconds (nights = ΔDate ÷ 1000 ÷ 60 ÷ 60 ÷ 24) |
+ |
320 |
long deltaDate = this.getEndDate().getTime() - this.getBeginDate().getTime(); |
+ |
321 |
long nights = deltaDate/1000/60/60/24; |
+ |
322 |
totalPrice += (this.getReservedBeds().size() * nights * bedPrice); |
+ |
323 |
// Calculate price for breakfasts |
302 |
324 |
int breakfasts = this.getBreakfastDays().length; |
303 |
325 |
totalPrice += breakfasts * this.getReservedBeds().size(); |
304 |
326 |
// Check if eligible for discount |
305 |
327 |
for(Room room: roomController.getRooms()) { |
306 |
328 |
Set<Bed> roomBeds = room.getBeds(); |
307 |
329 |
if(roomBeds.containsAll(this.reservedBeds)) { |
308 |
330 |
double discount = (double)totalPrice * 0.1; |
309 |
331 |
totalPrice -= (int)discount; |
310 |
332 |
} |
311 |
333 |
} |
312 |
334 |
return totalPrice; |
313 |
335 |
} |
314 |
336 |
|
315 |
337 |
|
316 |
338 |
|
317 |
339 |
|
318 |
340 |
|
319 |
341 |
|
320 |
342 |
|
321 |
343 |
public void setBreakfastDays(int[] breakfastDays) { |
322 |
344 |
this.breakfastDays = breakfastDays; |
323 |
345 |
} |
324 |
346 |
public int[] getBreakfastDays() { |
325 |
347 |
return this.breakfastDays; |
326 |
348 |
} |
327 |
349 |
|
328 |
350 |
} |
329 |
351 |
Challenge 6/ReservationController.java ¶
10 additions and 8 deletions.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* Controller class for the Reservations of this program. |
6 |
6 |
* Since this program handles a youth hostel, it's imperative that it holds a |
7 |
7 |
* number of Reservations. |
8 |
8 |
* |
9 |
9 |
* ReservationController takes the responsibility of handling the addition, |
10 |
10 |
* cancelling, editing, ... of Reservations, and the related tasks inherent to |
11 |
11 |
* these responsibilities, such as reserving beds, generating IDs, ... |
12 |
12 |
* @author Maarten Vangeneugden - 1438256 |
13 |
13 |
*/ |
14 |
14 |
public class ReservationController { |
15 |
15 |
|
16 |
16 |
/* Rationale: |
17 |
17 |
* Normally I'd put this as an interface (Set), but an interface does not |
18 |
18 |
* inherit from Object, and thus, does not provide clone(). This is |
19 |
19 |
* necessary, because otherwise, there's no point in having private |
20 |
20 |
* members. |
21 |
21 |
*/ |
22 |
22 |
private HashSet<Reservation> reservations; // Holds all Reservations |
23 |
23 |
private RoomController roomController; |
24 |
24 |
|
25 |
25 |
/** |
26 |
26 |
* Creates the ReservationController. |
27 |
27 |
* |
28 |
28 |
* WARNING: This program avoids the use of null pointers, but because of |
29 |
29 |
* circular dependency issues, this class requires that the developer (which |
30 |
30 |
* means YOU) set the RoomController reference member manually. |
31 |
31 |
* Be advised that the setRoomController() method will not accept a null |
32 |
32 |
* pointer. |
33 |
33 |
* |
34 |
34 |
* Once again: Set the RoomController reference manually ASAP. Failure to do |
35 |
35 |
* so will cause this program to blow up. You've been warned. |
36 |
36 |
* @see ReservationController.setRoomController |
37 |
37 |
*/ |
38 |
38 |
public ReservationController() { |
39 |
39 |
this.reservations = new HashSet<>(); |
40 |
40 |
} |
41 |
41 |
|
42 |
42 |
/** |
43 |
43 |
* Returns a copy of all Reservations. |
44 |
44 |
* Emphasis on "copy"; There is no setReservations() for a reason, using |
45 |
45 |
* this to edit the pointer variable would omit the use of 'private'. |
46 |
46 |
* @return A verbatim copy of all Reservations. |
47 |
47 |
*/ |
48 |
48 |
@SuppressWarnings("unchecked") |
49 |
49 |
public Set<Reservation> getReservations() { |
50 |
50 |
return (HashSet<Reservation>)this.reservations.clone(); |
51 |
51 |
} |
52 |
52 |
|
53 |
53 |
/** |
54 |
54 |
* Generates a unique ID for a new Reservation. |
55 |
55 |
* This method is to be called when a new Reservation is to be stored. |
56 |
56 |
* As every Reservation carries an ID, this method searches for a unique |
57 |
57 |
* one, and returns it. |
58 |
58 |
* @return An integer, different from all other stored Reservation IDs. |
59 |
59 |
*/ |
60 |
60 |
public int generateReservationID() { |
61 |
61 |
/* Small optimization idea: |
62 |
62 |
* Instead of starting from 0, and incrementing until it's unique, it |
63 |
63 |
* will take the amount of stored Reservations, and test that as an ID. |
64 |
64 |
* This may still overlap (by removing an older Reservation), but may be |
65 |
65 |
* much faster. Consider implemting and testing for effectiveness if |
66 |
66 |
* generating an ID takes too long. |
67 |
67 |
*/ |
68 |
68 |
int ID = 0; |
69 |
69 |
boolean isUnique = false; |
70 |
70 |
do { |
71 |
71 |
ID++; |
72 |
72 |
isUnique = true; |
73 |
73 |
for(Reservation reservation: this.reservations) { |
74 |
74 |
if(ID == reservation.getReservationID()) { |
75 |
75 |
isUnique = false; |
76 |
76 |
} |
77 |
77 |
} |
78 |
78 |
} while(!isUnique); |
79 |
79 |
// Test: |
80 |
80 |
for(Reservation reservation: this.reservations) { |
81 |
81 |
assert reservation.getReservationID() != ID : "Duplicate ID generated!"; |
82 |
82 |
} |
83 |
83 |
return ID; |
84 |
84 |
} |
85 |
85 |
|
86 |
86 |
/** |
87 |
87 |
* Check if Reservation can be made. |
88 |
88 |
* Call this method whenever you're planning on adding a new Reservation. It |
89 |
89 |
* will check the provided requirements (Room facilities, for example), and |
90 |
90 |
* return whether this Reservation can continue. |
91 |
91 |
* @param reservation The Reservation to check for possible addition. |
92 |
92 |
* @pre reservation mustn't be null. |
93 |
93 |
* @throws NullPointerException if reservation is null. |
94 |
94 |
* @return True if reservation can be added, False otherwise. |
95 |
95 |
*/ |
96 |
96 |
public boolean checkReservation(Reservation reservation) { |
97 |
97 |
if(reservation == null) { |
98 |
98 |
throw NullPointerException("reservation was a null pointer."); |
99 |
- | } |
+ |
99 |
} |
100 |
100 |
if(this.getRoomController().getQualifiedRooms(reservation).size() == 0) { |
101 |
- | return null; |
102 |
- | } |
+ |
101 |
return false; |
+ |
102 |
} |
103 |
103 |
} |
+ |
104 |
return true; |
+ |
105 |
} |
104 |
106 |
|
105 |
107 |
/** |
106 |
108 |
* Adds and confirms the reservation. |
107 |
109 |
* By calling this method, the given reservation will be stored in the |
108 |
110 |
* system, and the given room will be filled. |
109 |
111 |
* You are to collect the necessary data, and assure yourself that all |
110 |
112 |
* preconditions have been met. |
111 |
113 |
* |
112 |
114 |
* The best way to accomplish this, is to only send 'sterile' Reservations |
113 |
115 |
* as the first parameter. That is: Reservations without reserved Beds, |
114 |
116 |
* without prior addition to the active Reservations, etc. |
115 |
117 |
* NOTE: You must check for yourself if the Reservation can take place in |
116 |
118 |
* this Room! If there are not enough Beds available, an exception will be |
117 |
119 |
* thrown. |
118 |
120 |
* @param reservation The Reservation to add. |
119 |
121 |
* @param room The Room in which the people will reside. |
120 |
122 |
* @pre room must have enough empty beds. |
121 |
123 |
* @pre room must accomodate the Reservation's requirements. |
122 |
124 |
* @pre No parameter must be null. |
123 |
125 |
* @pre reservation mustn't already be stored in the active Reservations. |
124 |
126 |
* @post reservation is stored in the active Reservations. |
125 |
127 |
* @post The Beds in the provided Room are reserved for the Reservation's |
126 |
128 |
* period. |
127 |
129 |
* @throws IllegalArgumentException if the given Room can't fulfill the |
128 |
130 |
* requirements of the Reservation, or occupants is less than 1. |
129 |
131 |
* @throws NullPointerException if any parameter is a null pointer. |
130 |
132 |
*/ |
131 |
133 |
public void addReservation(Reservation reservation, Room room) { |
132 |
134 |
// Contract validation. Null pointers are implicitely checked by calling |
133 |
135 |
// methods. |
134 |
136 |
if(reservation.getPeople() > room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).size()) |
135 |
137 |
throw IllegalArgumentException("The given Room has not enough empty Beds for the Reservation period."); |
136 |
- | if(!this.getRoomController().getQualifiedRooms(reservation).contains(room)) |
+ |
138 |
if(!this.getRoomController().getQualifiedRooms(reservation).contains(room)) |
137 |
139 |
throw IllegalArgumentException("The given Room cannot meet all requirements of the Reservation."); |
138 |
- | if(this.getReservations().contains(reservation)) |
+ |
140 |
if(this.getReservations().contains(reservation)) |
139 |
141 |
throw IllegalArgumentException("The given Reservation was already included in the active Reservations."); |
140 |
- | // Contract validated |
+ |
142 |
// Contract validated |
141 |
143 |
this.reservations.add(reservation); |
142 |
144 |
Bed[] beds = room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).toArray(new Bed[1]); |
143 |
145 |
Set<Bed> reservedBeds = new HashSet<>(); |
144 |
146 |
for(int i=0; i<reservation.getPeople(); i++) { |
145 |
147 |
beds[i].reservePeriod(reservation.getBegin(), reservation.getEnd()); |
146 |
148 |
reservedBeds.add(beds[i]); |
147 |
149 |
} |
148 |
150 |
reservation.setReservedBeds(reservedBeds); |
149 |
151 |
} |
150 |
152 |
|
151 |
153 |
/** |
152 |
154 |
* Cancels and removes the given Reservation. |
153 |
155 |
* If you want to remove a Reservation, use this method, and provide the |
154 |
156 |
* Reservation up for removal. |
155 |
157 |
* This method will take care of related actions, such as releasing Beds. |
156 |
158 |
* @param reservation The Reservation to be removed. |
157 |
159 |
* @pre reservation mustn't be null. |
158 |
160 |
* @pre reservation must be contained in the active Reservations. |
159 |
161 |
* @post The Reservation is removed from the active Reservations. |
160 |
162 |
* @post The Beds, previously reserved for this Reservation, are now |
161 |
163 |
* released, and can be reserved for another Reservation. |
162 |
164 |
* @throws NullPointerException if reservation is a null pointer. |
163 |
165 |
* @throws IllegalArgumentException if reservation is not contained in the |
164 |
166 |
* active Reservations. |
165 |
167 |
*/ |
166 |
168 |
public void cancelReservation(Reservation reservation) { |
167 |
169 |
// Contract validation |
168 |
170 |
if(!this.getReservations().contains(reservation)) { |
169 |
171 |
throw IllegalArgumentException("The given Reservation was not contained in the active Reservations."); |
170 |
- | } |
+ |
172 |
} |
171 |
173 |
if(reservation == null) { |
172 |
174 |
throw NullPointerException(); |
173 |
- | } |
+ |
175 |
} |
174 |
176 |
// Contract validated, execute method |
175 |
177 |
this.reservations.remove(reservation); // Remove from active Reservations |
176 |
178 |
for(Bed bed: reservation.getReservedBeds()) { // Release reserved Beds |
177 |
179 |
bed.removeReservationPeriod(reservation.getBegin(), reservation.getEnd()); |
178 |
180 |
} |
179 |
181 |
// Asserting post conditions are met |
180 |
182 |
assert !this.getReservation().contains(reservation) : "The reservation is still part of the active Reservations."; |
181 |
183 |
for(Bed bed: reservation.getReservedBeds()) { |
182 |
184 |
assert bed.isFree(reservation.getBegin(), reservation.getEnd()) : "One or more of the Beds are still reserved."; |
183 |
185 |
} |
184 |
186 |
} |
185 |
187 |
|
186 |
188 |
} |
187 |
189 |
Challenge 6/ReservationView.java ¶
7 additions and 5 deletions.
View changes Hide changes
1 |
1 |
import java.util.Set; |
2 |
2 |
import java.util.HashSet; |
3 |
3 |
import java.util.Date; |
4 |
4 |
/** |
+ |
5 |
/** |
5 |
6 |
* Creates a view screen that allows interaction with the reservation. |
6 |
7 |
* This window will place the reservation details in editable fields, so its |
7 |
8 |
* state can be adapted. |
8 |
9 |
* It then also adds a couple of buttons; add/update/cancel, with their |
9 |
10 |
* respective actions. These will then be passed to the ReservationController. |
10 |
11 |
* It will not allow to add/update a reservation if dates overlap, i.e. beds |
11 |
12 |
* can't be reserved for that period. |
12 |
13 |
* @author Maarten Vangeneugden - 1438256 |
13 |
14 |
*/ |
14 |
15 |
public class ReservationView { |
15 |
16 |
|
16 |
17 |
private Reservation reservation; |
17 |
18 |
private Window window; |
18 |
19 |
|
19 |
20 |
// Controllers for communication with the rest |
20 |
21 |
private ReservationController rc; |
21 |
22 |
private RoomController roc; |
22 |
23 |
|
23 |
24 |
// GUI widgets |
24 |
25 |
private JTextField nameField; |
25 |
26 |
private JSpinner amountPeopleField; |
26 |
27 |
private JTextField dateField; |
27 |
- | private JSpinner durationField; |
+ |
28 |
private JTextField endDateField; |
+ |
29 |
private JSpinner durationField; |
28 |
30 |
private JRadioButton[] typeField; |
29 |
31 |
private JCheckBox showerField; |
30 |
32 |
private JCheckBox bathField; |
31 |
33 |
private JCheckBox minibarField; |
32 |
34 |
private JCheckBox aircoField; |
33 |
35 |
|
34 |
36 |
|
35 |
37 |
/** |
36 |
38 |
* Construct a window to edit a Reservation. |
37 |
39 |
* This window presents its caller with the necessary tools to edit the |
38 |
40 |
* given Reservation, and save it in the system. |
39 |
41 |
* |
40 |
42 |
* By sending it an empty Reservation, you can add a new Reservation to the |
41 |
43 |
* system. |
42 |
44 |
* This class is built in a way that allows it to be used for both creating |
43 |
45 |
* new Reservations, and updating/changing existing Reservations. |
44 |
46 |
* Existing Reservations can also be removed by clicking the appropriate |
45 |
47 |
* button. |
46 |
48 |
* @param reservation The Reservation that will be updated. |
47 |
49 |
* @param rc The ReservationController class. |
48 |
50 |
* @param roc The RoomController class. |
49 |
51 |
* @pre No parameter may be a null pointer. |
50 |
52 |
* @post A window is displayed with GUI widgets, which are filled in |
51 |
53 |
* according to the state of reservation. |
52 |
54 |
* @throws NullPointerException if any parameter is a null pointer. |
53 |
55 |
*/ |
54 |
56 |
public ReservationView(Reservation reservation, ReservationController rc, RoomController roc) { |
55 |
57 |
// Contract validation |
56 |
58 |
if(rc == null || roc == null || reservation == null) |
57 |
59 |
throw NullPointerException("One or more of the given parameters is a null pointer."); |
58 |
- | // Contract validated |
+ |
60 |
// Contract validated |
59 |
61 |
this.rc = rc; |
60 |
62 |
this.roc = roc; |
61 |
63 |
|
62 |
64 |
this.reservation = reservation; |
63 |
65 |
this.window = new Window("Reservation screen"); |
64 |
66 |
this.addFields(); |
65 |
67 |
} |
66 |
68 |
|
67 |
69 |
public void setReservation(Reservation reservation) { |
68 |
70 |
this.reservation = reservation; |
69 |
71 |
} |
70 |
72 |
|
71 |
73 |
public Reservation getReservation() { |
72 |
74 |
return reservation; |
73 |
75 |
} |
74 |
76 |
|
75 |
77 |
/** |
76 |
78 |
* Add the necessary fields to the window. |
77 |
79 |
* This method inspects the Reservation, and copies the included information |
78 |
80 |
* to the widget's content. |
79 |
81 |
*/ |
80 |
82 |
private void addFields() { |
81 |
83 |
this.window.createLabel("Group name"); |
82 |
84 |
this.nameField = window.createTextField(this.reservation.getGroupName()); |
83 |
85 |
this.window.createLabel("Amount of people"); |
84 |
86 |
this.amountPeopleField = window.createSpinner(1, 20, this.reservation.getPeople(), 1); |
85 |
87 |
// Formatting date for the date field: |
86 |
88 |
this.window.createLabel("Begin date"); |
87 |
89 |
this.beginDateField = window.createTextField(this.reservation.getBegin().toString()); |
88 |
90 |
this.window.createLabel("End date"); |
89 |
91 |
this.endDateField = window.createTextField(this.reservation.getEnd().toString()); |
90 |
92 |
String[] types = {"Male", "Female", "Mixed"}; |
91 |
93 |
this.typeField = this.window.createRadioButtons(types); |
92 |
94 |
// TODO: Add a test to see if the Reservation has indicated which type |
93 |
95 |
// it is, and then already check the corresponding radio button. |
94 |
96 |
this.showerField = this.window.createCheckbox("Shower"); |
95 |
97 |
this.bathField = this.window.createCheckbox("Bath"); |
96 |
98 |
this.minibarField = this.window.createCheckbox("Minibar"); |
97 |
99 |
this.aircoField = this.window.createCheckbox("Airco"); |
98 |
100 |
// TODO: Idem for the facilities, test if already in Reservation, and |
99 |
101 |
// check accordingly. |
100 |
102 |
this.window.createButton("Add/Update reservation", "", "addReservation", this); |
101 |
103 |
this.window.createButton("Remove reservation", "", "removeReservation", this); |
102 |
104 |
} |
103 |
105 |
|
104 |
106 |
/** |
105 |
107 |
* Start the addition of this Reservation to the system. |
106 |
108 |
* This method will check the current fields, and update the Reservation |
107 |
109 |
* with those values. In doing so, the validity of the values is checked |
108 |
110 |
* (formatting, begin date before end date, ...). |
109 |
111 |
* If everything is okay, the method will check if these changes are |
110 |
112 |
* possible in the current system; are there enough empty beds? |
111 |
113 |
* |
112 |
114 |
* If everything is okay, the changes will be propagated to the |
113 |
115 |
* Reservation's state, and (if it's a new Reservation) will be added to the |
114 |
116 |
* system's active Reservations. |
115 |
117 |
*/ |
116 |
118 |
public void addReservation() { |
117 |
119 |
// Collect all data from the fields |
118 |
120 |
String name = this.nameField.getText(); |
119 |
121 |
Date beginDate = null, endDate = null; |
120 |
122 |
try { |
121 |
123 |
beginDate = DataFormat.parse(this.beginDateField.getText()); |
122 |
- | endDate = DataFormat.parse(this.endDateField.getText()); |
123 |
- | } |
+ |
124 |
endDate = new Date(this.endDateField.getText()); |
+ |
125 |
} |
124 |
126 |
catch(ParseException e) { |
125 |
- | this.window.messageDialog("Not all date fields were properly formatted."); |
+ |
127 |
this.window.messageDialog("Not all date fields were properly formatted."); |
126 |
128 |
return; |
127 |
129 |
} |
128 |
130 |
int people = (Integer)this.amountPeopleField.getValue(); |
129 |
131 |
String type = ""; |
130 |
132 |
for(int i=0; i<this.typeField.length; i++) { |
131 |
133 |
if(this.typeField[i].isSelected()) { |
132 |
134 |
type = this.typeField[i].getText(); |
133 |
135 |
break; |
134 |
136 |
} |
135 |
137 |
} |
136 |
138 |
Set<String> facilities = new HashSet<>(); |
137 |
139 |
if(this.showerField.isSelected()) { |
138 |
140 |
facilities.add("Shower"); |
139 |
141 |
} |
140 |
142 |
if(this.bathField.isSelected()) { |
141 |
143 |
facilities.add("Bath"); |
142 |
144 |
} |
143 |
145 |
if(this.minibarField.isSelected()) { |
144 |
146 |
facilities.add("Minibar"); |
145 |
147 |
} |
146 |
148 |
if(this.aircoField.isSelected()) { |
147 |
149 |
facilities.add("Airco"); |
148 |
150 |
} |
149 |
151 |
|
150 |
152 |
// TODO breakfast days handling |
151 |
153 |
|
152 |
154 |
//Set<Room> possibleRooms = this.roc.getQualifiedRooms(actualDate, duration, type, facilities); |
153 |
155 |
// TODO: Refactor upper method to work with the Reservation. |
154 |
156 |
// TODO: Implement all checks of valid data above this line! |
155 |
157 |
|
156 |
158 |
// Data validated; Try finding appropriate beds |
157 |
159 |
if(possibleRooms.size() == 0) { |
158 |
160 |
boolean tryAgain = this.window.confirmDialog("No rooms met the requirements! Would you like to continue and change the parameters?"); |
159 |
161 |
if(!tryAgain) { |
160 |
162 |
// TODO close window |
161 |
163 |
} |
162 |
164 |
} |
163 |
165 |
else { |
164 |
166 |
Room pickedRoom = null; |
165 |
167 |
for(Room room: possibleRooms) { |
166 |
168 |
if(room.getEmptyBeds(actualDate, actualEndDate).size() < room.getBeds().size()) { |
167 |
169 |
// First, fill the rooms that are partially filled. |
168 |
170 |
pickedRoom = room; |
169 |
171 |
break; |
170 |
172 |
} |
171 |
173 |
} |
172 |
174 |
if(pickedRoom == null) { // If still no room, pick an empty room |
173 |
175 |
for(Room room: possibleRooms) { |
174 |
176 |
if(room.getEmptyBeds(actualDate, actualEndDate).size() >= amount) { |
175 |
177 |
pickedRoom = room; |
176 |
178 |
break; |
177 |
179 |
} |
178 |
180 |
} |
179 |
181 |
} |
180 |
182 |
assert pickedRoom != null; |
181 |
183 |
// TODO: Set reservation fields here! |
182 |
184 |
this.rc.addReservation(reservation, pickedRoom); |
183 |
185 |
|
184 |
186 |
// Confirm and show price: |
185 |
187 |
int price = this.reservation.getPrice(); |
186 |
188 |
this.window.confirmDialog("Reservation confirmed! Price: " +String.valueOf(price)); |
187 |
189 |
} |
188 |
190 |
|
189 |
191 |
} |
190 |
192 |
|
191 |
193 |
/** |
192 |
194 |
* Remove Reservation and the GUI. |
193 |
195 |
* Calling this method will remove the Reservation, followed by closing this |
194 |
196 |
* window. |
195 |
197 |
* If the Reservation is active (i.e. saved in the ReservationController), |
196 |
198 |
* it will be removed. |
197 |
199 |
* Any changes will be cancelled. |
198 |
200 |
*/ |
199 |
201 |
public void removeReservation() { |
200 |
202 |
if(this.rc.getReservations().contains(this.reservation)) { |
201 |
203 |
this.rc.cancelReservation(this.reservation); |
202 |
204 |
} |
203 |
205 |
this.window.close(); |
204 |
206 |
} |
205 |
207 |
} |
206 |
208 |
Challenge 6/Room.java ¶
7 additions and 7 deletions.
View changes Hide changes
1 |
1 |
import java.util.HashSet; |
2 |
2 |
import java.util.Date; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* A room in a hostel. |
6 |
6 |
* Room represents just that: A room. |
7 |
7 |
* A room contains a set of Beds, facilities that can be used, etc. |
8 |
8 |
* It's highly decoupled: Apart from holding a set of Beds, the only members |
9 |
9 |
* types consist of those that are available in every OpenJDK implementation. |
10 |
10 |
* @author Maarten Vangeneugden - 1438256 |
11 |
11 |
*/ |
12 |
12 |
public class Room { |
13 |
13 |
|
14 |
14 |
private HashSet<Bed> beds; |
15 |
15 |
private String type; |
16 |
16 |
private HashSet<String> facilities; |
17 |
17 |
|
18 |
18 |
/** |
19 |
19 |
* Create a new Room. |
20 |
20 |
* @param beds The amount of Beds that will be placed in this Room. |
21 |
21 |
* @param type The type of this Room. (for example: Male, Female, Mixed, |
22 |
22 |
* ...) |
23 |
23 |
* @param facilities A Set of facilities this Room provides ("Shower", |
24 |
24 |
* "Radio", "Overpriced WiFi", ...) |
25 |
25 |
* @pre No parameter must be a null pointer. |
26 |
26 |
* @pre beds must be at least 1. |
27 |
27 |
* @pre Type mustn't be an empty String. |
28 |
28 |
* @pre No facility may be an empty String. |
29 |
29 |
* @post The Room will receive the provided amount of Beds, which are all |
30 |
30 |
* completely released. |
31 |
31 |
* @throws IllegalArgumentException if the amount of Beds is less than 1, or |
32 |
32 |
* one of the facilities is an empty String. |
33 |
33 |
* @throws NullPointerException if one of the parameters is a null pointer. |
34 |
34 |
*/ |
35 |
35 |
public Room(int beds, String type, Set<String> facilities) { |
36 |
36 |
// Contract validation happens in the setter methods |
37 |
37 |
this.setFacilities(facilities); |
38 |
38 |
this.setType(type); |
39 |
39 |
if(beds < 1) { |
40 |
40 |
throw IllegalArgumentException("Beds was less than 1."); |
41 |
- | } |
+ |
41 |
} |
42 |
42 |
// Contract validated, execute constructor |
43 |
43 |
this.beds = new HashSet<>(); |
44 |
44 |
for(int i=0; i<beds; i++) { |
45 |
45 |
this.beds.add(new Bed()); |
46 |
46 |
} |
47 |
47 |
} |
48 |
48 |
|
49 |
49 |
/** |
50 |
50 |
* Remove a Bed from this Room. |
51 |
51 |
* This method will remove the given Bed from this Room. |
52 |
52 |
* However, make sure that this Bed is free of any reservations, as you |
53 |
53 |
* can't delete a Bed with a reserved period. |
54 |
54 |
* @see Bed |
55 |
55 |
* @param bed The Bed to be removed. |
56 |
56 |
* @pre bed mustn't be null. |
57 |
57 |
* @pre bed mustn't be the last Bed in this Room. |
58 |
58 |
* @pre bed must be in this Room. |
59 |
59 |
* @pre bed mustn't have any reserved periods. |
60 |
60 |
* @post The amount of Beds in this Room is decremented by 1. |
61 |
61 |
* @throws IllegalArgumentException if bed is not in this Room, or has |
62 |
62 |
* reserved periods, or this is the last Bed in the Room. |
63 |
63 |
* @throws NullPointerException if bed is a null pointer. |
64 |
64 |
*/ |
65 |
65 |
public void removeBed(Bed bed) { |
66 |
66 |
// Contract validation |
67 |
67 |
if(!this.getBeds().contains(bed)) { |
68 |
68 |
throw IllegalArgumentException("The given Bed is not in this Room."); |
69 |
- | } |
+ |
69 |
} |
70 |
70 |
if(bed.hasReservations()) { |
71 |
71 |
throw IllegalArgumentException("The given Bed still has reserved periods."); |
72 |
- | } |
+ |
72 |
} |
73 |
73 |
if(this.getBeds().size() == 1) { |
74 |
74 |
throw IllegalArgumentException("Deleting this Bed would empty the Room."); |
75 |
- | } |
+ |
75 |
} |
76 |
76 |
// Contract validated, execute method |
77 |
77 |
this.beds.remove(bed); |
78 |
78 |
// Assert post conditions |
79 |
79 |
assert !this.getBeds().contains(bed) : "The given Bed was not removed from this Room."; |
80 |
80 |
} |
81 |
81 |
|
82 |
82 |
/** |
83 |
83 |
* Add a Bed to this Room. |
84 |
84 |
* @post The amount of Beds in this Room increments with 1. |
85 |
85 |
*/ |
86 |
86 |
public void addBed() { |
87 |
87 |
this.beds.add(new Bed()); |
88 |
88 |
} |
89 |
89 |
|
90 |
90 |
/** |
91 |
91 |
* Returns a copy of all Beds. |
92 |
92 |
* @return A verbatim copy of all Beds. |
93 |
93 |
*/ |
94 |
94 |
@SuppressWarnings("unchecked") |
95 |
95 |
public Set<Bed> getBeds() { |
96 |
96 |
return (HashSet<Bed>) this.beds.clone(); |
97 |
97 |
} |
98 |
98 |
|
99 |
99 |
/** |
100 |
100 |
* Set the type of this Room. |
101 |
101 |
* @param type The new type of the Room. |
102 |
102 |
* @pre type mustn't be an empty String, or a null pointer. |
103 |
103 |
* @post The Room's type is changed to the given type. |
104 |
104 |
* @throws IllegalArgumentException if type is an empty String. |
105 |
105 |
* @throws NullPointerException if type is a null pointer. |
106 |
106 |
*/ |
107 |
107 |
public void setType(String type) { |
108 |
108 |
if(type.isEmpty()) { |
109 |
109 |
throw IllegalArgumentException("type is an empty String."); |
110 |
- | } |
+ |
110 |
} |
111 |
111 |
this.type = type; |
112 |
112 |
} |
113 |
113 |
|
114 |
114 |
/** |
115 |
115 |
* Returns the type of this Room. |
116 |
116 |
* @return The secret launch codes of the USS Nimitz, granting access to |
117 |
117 |
* nuclear warheads so big, you'll lose faith in humanity. |
118 |
118 |
*/ |
119 |
119 |
public String getType() { |
120 |
120 |
return type; |
121 |
121 |
} |
122 |
122 |
|
123 |
123 |
/** |
124 |
124 |
* Set the facilities available in this Room. |
125 |
125 |
* @param facilities The set of facilities in this Room. |
126 |
126 |
* @pre No facility must be an empty String or a null pointer. |
127 |
127 |
* @post The Room will have the newly provided facilities. |
128 |
128 |
* @throws IllegalArgumentException if one of the facilities is an empty String. |
129 |
129 |
* @throws NullPointerException if any given variable is a null pointer. |
130 |
130 |
*/ |
131 |
131 |
public void setFacilities(Set<String> facilities) { |
132 |
132 |
for(String facility : facilities) { |
133 |
133 |
if(facility.isEmpty()) { |
134 |
134 |
throw IllegalArgumentException("A facility was an empty String."); |
135 |
- | } |
+ |
135 |
} |
136 |
136 |
} |
137 |
137 |
this.facilities = facilities; |
138 |
138 |
} |
139 |
139 |
|
140 |
140 |
/** |
141 |
141 |
* Returns a copy of all facilities. |
142 |
142 |
* @return A verbatim copy of all facilities. |
143 |
143 |
*/ |
144 |
144 |
@SuppressWarnings("unchecked") |
145 |
145 |
public Set<String> getFacilities() { |
146 |
146 |
return (HashSet<String>)this.facilities.clone(); |
147 |
147 |
} |
148 |
148 |
|
149 |
149 |
/** |
150 |
150 |
* Search for Beds that can be reserved. |
151 |
151 |
* This method will look through all Beds, and check whether they can be |
152 |
152 |
* reserved in the given period. You will receive all Beds that can be |
153 |
153 |
* reserved. |
154 |
154 |
* @param begin The begin date. |
155 |
155 |
* @param end The end date. |
156 |
156 |
* @pre No parameter must be null. |
157 |
157 |
* @pre begin must strictly come before end. |
158 |
158 |
* @throws IllegalArgumentException if begin comes after end, or both are |
159 |
159 |
* equal. |
160 |
160 |
* @throws NullPointerException if any given parameter is a null pointer. |
161 |
161 |
* @return A Set of all Beds that can be reserved, or an empty Set if no Bed |
162 |
162 |
* can be reserved in the given period. |
163 |
163 |
*/ |
164 |
164 |
public Set<Bed> getEmptyBeds(Date begin, Date end) { |
165 |
165 |
// Contract validation |
166 |
166 |
if(!begin.before(end)) { |
167 |
167 |
throw IllegalArgumentException("begin does not come strictly before end"); |
168 |
- | } |
+ |
168 |
} |
169 |
169 |
// Validated |
170 |
170 |
Set<Bed> emptyBeds = new HashSet<>(); |
171 |
171 |
for(Bed bed: this.getBeds()) { |
172 |
172 |
if(bed.isFree(begin, end)) { |
173 |
173 |
emptyBeds.add(bed); |
174 |
174 |
} |
175 |
175 |
} |
176 |
176 |
return emptyBeds; |
177 |
177 |
} |
178 |
178 |
|
179 |
179 |
} |
180 |
180 |
Challenge 6/RoomController.java ¶
2 additions and 2 deletions.
View changes Hide changes
1 |
1 |
import java.util.Date; |
2 |
2 |
import java.util.HashSet; |
3 |
3 |
|
4 |
4 |
/** |
5 |
5 |
* Holds all Rooms in the hostel. |
6 |
6 |
* The hostel contains a set of Rooms, with facilities and stuff. |
7 |
7 |
* This class takes care of storing all those Rooms, and provides methods to |
8 |
8 |
* manage this, like removing Rooms, adding Rooms, ... |
9 |
9 |
* @author Maarten Vangeneugden - 1438256 |
10 |
10 |
*/ |
11 |
11 |
public class RoomController { |
12 |
12 |
|
13 |
13 |
private Set<Room> rooms; |
14 |
14 |
|
15 |
15 |
public RoomController() { |
16 |
16 |
this.rooms = new HashSet<>(); |
17 |
17 |
} |
18 |
18 |
|
19 |
19 |
|
20 |
20 |
public Set<Room> getRooms() { |
21 |
21 |
return rooms; |
22 |
22 |
} |
23 |
23 |
|
24 |
24 |
/** |
25 |
25 |
* Returns all rooms that meet the given requirements. |
26 |
26 |
* This method will search through all rooms, and check which rooms qualify. |
27 |
27 |
* Currently, this method checks for the following things: |
28 |
28 |
* - Is the requested type available? |
29 |
29 |
* - Does the Set of requested facilities form a subset of the Room's |
30 |
30 |
* facilities? |
31 |
31 |
* - Are there any Beds in the Room that can be reserved in the given |
32 |
32 |
* period? |
33 |
33 |
* @param reservation The Reservation for which to find eligible rooms. |
34 |
34 |
* @pre reservation mustn't be null. |
35 |
35 |
* @throws NullPointerException if reservation is a null pointer. |
36 |
36 |
* @return A set of all rooms that meet the requirements, or an empty set if |
37 |
37 |
* none were found. |
38 |
38 |
*/ |
39 |
39 |
public Set<Room> getQualifiedRooms(Reservation reservation) { |
40 |
40 |
Set<Room> qualifiedRooms = new HashSet<>(); |
41 |
41 |
for(Room room : this.getRooms()) { |
42 |
42 |
if(room.getType().equals(reservation.getType())) |
43 |
- | continue; |
+ |
43 |
continue; |
44 |
44 |
if(!room.getFacilities().containsAll(reservation.getFacilities())) |
45 |
- | continue; |
+ |
45 |
continue; |
46 |
46 |
if(room.getEmptyBeds(reservation.getBegin(), reservation.getEnd()).isEmpty()) |
47 |
47 |
continue; |
48 |
48 |
// The Room fulfills all requirements at this point, so add it to |
49 |
49 |
// the set |
50 |
50 |
qualifiedRooms.add(room); |
51 |
51 |
} |
52 |
52 |
return qualifiedRooms; |
53 |
53 |
} |
54 |
54 |
} |
55 |
55 |
Challenge 6/Window.java ¶
1 addition and 1 deletion.
View changes Hide changes
1 |
1 |
* Window.java - Module to create a new window with JSugar. |
2 |
2 |
* Copyright © 2016 Maarten "Vngngdn" Vangeneugden |
3 |
3 |
* |
4 |
4 |
* This program is free software: you can redistribute it and/or modify |
5 |
5 |
* it under the terms of the GNU General Public License as published by |
6 |
6 |
* the Free Software Foundation, either version 3 of the License, or |
7 |
7 |
* (at your option) any later version. |
8 |
8 |
* |
9 |
9 |
* This program is distributed in the hope that it will be useful, |
10 |
10 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 |
11 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 |
12 |
* GNU General Public License for more details. |
13 |
13 |
* |
14 |
14 |
* You should have received a copy of the GNU General Public License |
15 |
15 |
* along with this program. If not, see <https://www.gnu.org/licenses/>. |
16 |
16 |
*/ |
17 |
17 |
|
18 |
18 |
/* |
19 |
19 |
* TODO list: |
20 |
20 |
* - JSlider (It's the same as the JSpinner, only longer. So an extra.) |
21 |
21 |
* - JTable (And a JScrollBar to accompany it) (extra, because of JList) |
22 |
22 |
* - JFileChooser (?) |
23 |
23 |
* DONE list: |
24 |
24 |
* - JLabel |
25 |
25 |
* - JText |
26 |
26 |
* - JButton |
27 |
27 |
* - JDialogBoxes (you know, everything dialog related) |
28 |
28 |
* - JCheckbox |
29 |
29 |
* - JRadioButton (properly grouping them has been taken care of as well) |
30 |
30 |
* - JSpinner |
31 |
31 |
* - JComboBox |
32 |
32 |
* - JList |
33 |
33 |
*/ |
34 |
34 |
|
35 |
35 |
import javax.swing.*; // FIXME: Maybe namespacing it to "javax.swing;" is a better idea. |
36 |
36 |
import java.util.NoSuchElementException; |
37 |
37 |
import java.lang.reflect.Method; |
38 |
38 |
import java.io.File; |
39 |
39 |
import java.awt.event.WindowEvent; // For programatically closing the window |
40 |
40 |
|
41 |
41 |
/** |
42 |
42 |
* Window class for the program. |
43 |
43 |
* |
44 |
44 |
* Window contains the necessary data and methods to present the user with what |
45 |
45 |
* he's familiar with as being a "window". To make it functional, the developer |
46 |
46 |
* can make use of a series of methods to add components to said window, remove |
47 |
47 |
* components, and so on. |
48 |
48 |
* Currently, Window also contains methods to show dialogs. This will be cleaned |
49 |
49 |
* in the near future. |
50 |
50 |
* @author Maarten Vangeneugden |
51 |
51 |
*/ |
52 |
52 |
public class Window { // Must be public, in order to generate Javadoc. |
53 |
53 |
private JPanel panel; // The panel that contains all the components. |
54 |
54 |
private JFrame frame; // The "window" being presented to the user. |
55 |
55 |
|
56 |
56 |
/** |
57 |
57 |
* Constructor of Window. |
58 |
58 |
* By creating a new Window instance, this constructor will automatically |
59 |
59 |
* start the initialization of the GUI. After doing so, the caller can |
60 |
60 |
* start adding components to the window as pleased. |
61 |
61 |
* @param title The title to be shown in the window's title bar. |
62 |
62 |
*/ |
63 |
63 |
public Window(String title) { |
64 |
64 |
// Setting the UI style to the platform's UI style. Fuck Swing's, |
65 |
65 |
// really. |
66 |
66 |
try { |
67 |
67 |
UIManager.setLookAndFeel( |
68 |
68 |
UIManager.getSystemLookAndFeelClassName()); |
69 |
69 |
} catch(Exception e) { |
70 |
70 |
e.printStackTrace(); |
71 |
71 |
} |
72 |
72 |
|
73 |
73 |
if(title == null || title.equals("")) { // If the title was omitted: |
74 |
74 |
title = "JSugar"; |
75 |
75 |
} |
76 |
76 |
this.panel = new JPanel(); |
77 |
77 |
// TODO: The current title is "Hello world!" but that will become caller |
78 |
78 |
// defined soon. |
79 |
79 |
JFrame frame = new JFrame(title); |
80 |
80 |
// Makes it so that if the user clicks the X in the titlebar, the window |
81 |
81 |
// closes: |
82 |
82 |
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // XXX: Closes |
83 |
83 |
//ALL open windows! |
84 |
84 |
//frame.getContentPane().add(lblHelloWorld); // So you use a get() in order to set() data? #JavaWTF |
85 |
85 |
frame.setContentPane(this.panel); // Connecting the component panel to the window. |
86 |
86 |
// Makes the window fit to the necessary width and height, so it can show all "subcomponents". |
87 |
87 |
frame.pack(); |
88 |
88 |
frame.setVisible(true); // Makes the window visible to the user. |
89 |
89 |
this.frame = frame; |
90 |
90 |
} |
91 |
91 |
|
92 |
92 |
/** |
93 |
93 |
* Resizes the window to fit all components. |
94 |
94 |
* By calling this method, the window will evaluate the currently visible |
95 |
95 |
* components, and resize itself so that all components become properly |
96 |
96 |
* visible. |
97 |
97 |
*/ |
98 |
98 |
private void updateWindow() { |
99 |
99 |
this.frame.pack(); |
100 |
100 |
} |
101 |
101 |
|
102 |
102 |
/** |
103 |
103 |
* A series of tests for method and class handling. |
104 |
104 |
* When a caller presents certain methods with data concerning reflection, |
105 |
105 |
* the Java classes you need to use for that are quite opaque, and don't |
106 |
106 |
* offer much safety in any way. |
107 |
107 |
* The solution therefore, is run some validation checks, but these take up |
108 |
108 |
* a decent amount of space in terms of LoC. |
109 |
109 |
* This method takes care of all that. Call this function whenever data |
110 |
110 |
* needs to be validated. |
111 |
111 |
* @param methodName The name of the method, as it is declared in object. |
112 |
112 |
* @param object The class instance in where this method will be called. |
113 |
113 |
* @return The method that could be derived from the supplied data, or null |
114 |
114 |
* if that wasn't possible. |
115 |
115 |
* @throws NullPointerException if either methodName or object are null |
116 |
116 |
* pointers. |
117 |
117 |
* @throws IllegalArgumentException if methodName is empty, or the method |
118 |
118 |
* does not appear to be declared in the given object, or object is not a |
119 |
119 |
* class. |
120 |
120 |
*/ |
121 |
121 |
// All unchecked typecasts are safe, and the use of raw types is taken care |
122 |
122 |
// of. |
123 |
123 |
@SuppressWarnings({"unchecked","rawtypes"}) |
124 |
124 |
private Method handleReflectionData(String methodName, Object object) { |
125 |
125 |
// Null pointer checking: |
126 |
126 |
if (methodName == null || object == null) { |
127 |
127 |
throw new NullPointerException("One or more of the given parameters are null pointers."); |
128 |
128 |
} |
129 |
129 |
|
130 |
130 |
// XXX: Some might say the next line should be in an else{} block. But |
131 |
131 |
// Scoping rules require that I'd then have to wrap the rest of the |
132 |
132 |
// method in the same else to use it. |
133 |
133 |
Class methodClass = object.getClass(); |
134 |
134 |
if (methodName.equals("")) { |
135 |
135 |
throw new IllegalArgumentException("The given methodName was empty."); |
136 |
136 |
} |
137 |
137 |
Method method; |
138 |
138 |
try { // First: Look if there's a method without parameters. |
139 |
139 |
method = methodClass.getMethod(methodName, null); |
140 |
140 |
} |
141 |
141 |
catch (NoSuchMethodException exception) { |
142 |
142 |
try { |
143 |
143 |
// It's possible that the method requires an event parameter, so |
144 |
144 |
// check for that as well: |
145 |
145 |
Class<?>[] parameters = {java.awt.event.ActionEvent.class}; |
146 |
146 |
method = methodClass.getMethod(methodName, parameters); |
147 |
147 |
} |
148 |
148 |
catch (NoSuchMethodException e) { |
149 |
149 |
throw new IllegalArgumentException("The given method does not appear in the given class. Be aware that the given method mustn't have any parameters, or only 1 parameter, which has to be of type java.awt.event.ActionEvent."); |
150 |
150 |
} |
151 |
151 |
} |
152 |
152 |
// At this stage, the given data has been validated, and we've been able |
153 |
153 |
// to retrieve the method itself. |
154 |
154 |
return method; |
155 |
155 |
} |
156 |
156 |
|
157 |
157 |
/** |
158 |
158 |
* Creates a button in the GUI for interaction. |
159 |
159 |
* This function offers a convenient way to create a button, that can be |
160 |
160 |
* directly interacted with by the user. After creation, the button itself |
161 |
161 |
* is returned to the caller, if he wishes to do something else with it. |
162 |
162 |
* @param text The text that will be displayed in the button. |
163 |
163 |
* @param action The action that will be returned to the action listener. |
164 |
164 |
* @param methodName The name of the method that will be called when an |
165 |
165 |
* action is triggered. |
166 |
166 |
* @param triggerObject The object instance that contains the given method. |
167 |
167 |
* This may only be a null pointer if triggerMethod is not an instance |
168 |
168 |
* method. |
169 |
169 |
* performed. This method may accept an ActionEvent parameter as its only |
170 |
170 |
* parameter, or no parameters at all. |
171 |
171 |
* @throws NullPointerException if triggerMethod is a null pointer, or |
172 |
172 |
* the empty String was given. |
173 |
173 |
* @throws IllegalArgumentException if triggerMethod has more than 1 |
174 |
174 |
* parameter, or the 1 required parameter is not of type ActionEvent. |
175 |
175 |
* @return The button that was created. |
176 |
176 |
* @see java.awt.event.ActionEvent |
177 |
177 |
* @see java.lang.reflect.Method#invoke |
178 |
178 |
*/ |
179 |
179 |
public JButton createButton(String text, String action, String methodName, Object triggerObject) { |
180 |
180 |
Method triggerMethod = this.handleReflectionData(methodName, triggerObject); |
181 |
181 |
|
182 |
182 |
// For starters, we first assert that the given parameters are valid: |
183 |
183 |
if (text == null) { |
184 |
184 |
text = ""; |
185 |
185 |
} |
186 |
186 |
if (action == null) { |
187 |
187 |
action = ""; |
188 |
188 |
} |
189 |
189 |
|
190 |
190 |
// When the method gets here, everything's been validated correctly. |
191 |
191 |
JButton button = new JButton(text); |
192 |
192 |
button.setActionCommand(action); |
193 |
193 |
button.addActionListener( |
194 |
194 |
new java.awt.event.ActionListener() { |
195 |
195 |
public void actionPerformed(java.awt.event.ActionEvent event) { |
196 |
196 |
try { |
197 |
197 |
triggerMethod.setAccessible(true); |
198 |
198 |
if (triggerMethod.getParameterTypes().length == 0) { |
199 |
199 |
// FIXME: Next line throws a warning? |
200 |
200 |
triggerMethod.invoke(triggerObject, null); |
201 |
201 |
} |
202 |
202 |
else { |
203 |
203 |
triggerMethod.invoke(triggerObject, new Object[]{event}); |
204 |
204 |
} |
205 |
205 |
} |
206 |
206 |
catch (Exception useless) { |
207 |
207 |
/* |
208 |
208 |
* XXX: Some info on why I don't just throw said |
209 |
209 |
* Exception to the caller: |
210 |
210 |
* Java has this awful language constraint, which |
211 |
211 |
* forces every damn exception that isn't a subclass |
212 |
212 |
* of RuntimeException, to be declared in the method |
213 |
213 |
* declaration. This tends to infect all underlying |
214 |
214 |
* methods as well, and all that for reasons I can't |
215 |
215 |
* comprehend. In order to keep JSugar a simple and |
216 |
216 |
* clean library, I'll rather just handle it here, |
217 |
217 |
* and throw a RuntimeException with appropriate |
218 |
218 |
* details. |
219 |
219 |
*/ |
220 |
220 |
throw new IllegalArgumentException("triggerMethod is not accessible from this context."); |
221 |
221 |
} |
222 |
222 |
} |
223 |
223 |
}); |
224 |
224 |
this.addComponent(button); |
225 |
225 |
return button; |
226 |
226 |
} |
227 |
227 |
|
228 |
228 |
/** |
229 |
229 |
* Ask the user for input through a dialog box. |
230 |
230 |
* This method presents the user with an input field, that can accept |
231 |
231 |
* textual input. The method will return the given input after the user's |
232 |
232 |
* clicked a button to send. |
233 |
233 |
* @param text The text/question to be asked to the user. |
234 |
234 |
* @return A String, equal to what the user entered. |
235 |
235 |
* @throws NullPointerException if text is a null pointer. |
236 |
236 |
*/ |
237 |
237 |
public String inputDialog(String text) { |
238 |
238 |
if (text == null) { |
239 |
239 |
throw new NullPointerException("The given text/question was a null pointer."); |
240 |
240 |
} |
241 |
241 |
return JOptionPane.showInputDialog(text); |
242 |
242 |
} |
243 |
243 |
|
244 |
244 |
/** |
245 |
245 |
* Give the user a dialog box. |
246 |
246 |
* This method can be used to provide a simple dialog to the user. |
247 |
247 |
* This will show the user the given question, after which a boolean value |
248 |
248 |
* is returned, holding the choice. |
249 |
249 |
* @param text The text/question to be asked to the user. |
250 |
250 |
* @return True if the user confirms, False if he denies. |
251 |
251 |
* @throws NullPointerException if text is a null pointer. |
252 |
252 |
*/ |
253 |
253 |
public boolean confirmDialog(String text) { |
254 |
254 |
if (text == null) { |
255 |
255 |
throw new NullPointerException("The given text/question was a null pointer."); |
256 |
256 |
} |
257 |
257 |
final int ACCEPTED = 0; |
258 |
258 |
//final int DENIED = 1; // Not used in the current context. |
259 |
259 |
int result = this.choiceDialog(text, new String[]{"Confirm", "Deny"}); |
260 |
260 |
if (result == ACCEPTED) { |
261 |
261 |
return true; |
262 |
262 |
} |
263 |
263 |
else { |
264 |
264 |
return false; |
265 |
265 |
} |
266 |
266 |
} |
267 |
267 |
|
268 |
268 |
/** |
269 |
269 |
* Give the user a choice dialog box. |
270 |
270 |
* This method gives the user a simple dialog with predefined choices. |
271 |
271 |
* These choices are to be provided by the caller in a simple array. |
272 |
272 |
* |
273 |
273 |
* Tip: This method works extremely well with arbitrary created choices. |
274 |
274 |
* That is: if the outcome of the dialog is trivial (e.g. Only 1 choice), |
275 |
275 |
* then that value is immediately returned. |
276 |
276 |
* @param text The text/question to be asked to the user. |
277 |
277 |
* @param choices An array of Strings, containing the choices the user can |
278 |
278 |
* pick. |
279 |
279 |
* @return The index value of the picked choice, or -1 if no choices were |
280 |
280 |
* given. |
281 |
281 |
* @throws NullPointerException if text is a null pointer. |
282 |
282 |
*/ |
283 |
283 |
public int choiceDialog(String text, String[] choices) { |
284 |
284 |
if (text == null) { |
285 |
285 |
throw new NullPointerException("The given text/question was a null pointer."); |
286 |
286 |
} |
287 |
287 |
// First: handling the trivial cases: |
288 |
288 |
if (choices.length == 0) { |
289 |
289 |
return -1; |
290 |
290 |
} |
291 |
291 |
else if (choices.length == 1) { |
292 |
292 |
return 0; |
293 |
293 |
} |
294 |
294 |
int answer = JOptionPane.CLOSED_OPTION; |
295 |
295 |
// The dialog needs to be shown again until the user has made a possible |
296 |
296 |
// choice, i.e. Chickening out using the close button is not possible |
297 |
297 |
// (Because that returns CLOSED_OPTION). |
298 |
298 |
while (answer == JOptionPane.CLOSED_OPTION) { |
299 |
299 |
JOptionPane.showOptionDialog( |
300 |
300 |
null, // The parent component. May become the panel? |
301 |
301 |
text, // The text/question to describe the goal |
302 |
302 |
"Dialog", // The text in the title bar |
303 |
303 |
JOptionPane.DEFAULT_OPTION, // The kind of available options |
304 |
304 |
JOptionPane.QUESTION_MESSAGE, // The type of message |
305 |
305 |
null, // The icon to show |
306 |
306 |
choices, // The possible choices |
307 |
307 |
choices[0] // The standard choice |
308 |
308 |
); |
309 |
309 |
} |
310 |
310 |
return answer; |
311 |
311 |
} |
312 |
312 |
|
313 |
313 |
|
314 |
314 |
/** |
315 |
315 |
* Creates a label in the GUI for interaction. |
316 |
316 |
* This function offers a convenient way to create a label, that can be |
317 |
317 |
* directly interacted with by the user. After creation, the label itself |
318 |
318 |
* is returned to the caller, if he wishes to do something else with it. |
319 |
319 |
* @param text The text that will be displayed in the label. |
320 |
320 |
* @return The label that was created. |
321 |
321 |
*/ |
322 |
322 |
public JLabel createLabel(String text) { |
323 |
323 |
JLabel label = new JLabel(text); |
324 |
324 |
this.addComponent(label); |
325 |
325 |
return label; |
326 |
326 |
} |
327 |
327 |
|
328 |
328 |
/** |
329 |
329 |
* Adds a checkbox to the window. |
330 |
330 |
* By providing a String, you can use this method to easily |
331 |
331 |
* create a checkbox, and add it to the window. |
332 |
332 |
* @param text The text to put next to the checkbox. |
333 |
333 |
* @return The checkbox that was created and added to the GUI. |
334 |
334 |
*/ |
335 |
335 |
public JCheckBox createCheckbox(String text) { |
336 |
336 |
JCheckBox checkbox = new JCheckBox(text); |
337 |
337 |
this.addComponent(checkbox); |
338 |
338 |
return checkbox; |
339 |
339 |
} |
340 |
340 |
|
341 |
341 |
/** |
342 |
342 |
* Adds radio buttons to the window. |
343 |
343 |
* Given a list of Strings, this method will create the same amount of radio |
344 |
344 |
* buttons. |
345 |
345 |
* |
346 |
346 |
* The radio buttons will silently be grouped in a ButtonGroup object, |
347 |
347 |
* making them automatically disable each other, so only 1 radio button can |
348 |
348 |
* be enabled. This ButtonGroup is immutable. |
349 |
349 |
* |
350 |
350 |
* If you need a mutable ButtonGroup, create your own, and use the |
351 |
351 |
* {@link #addComponent} method to add the radio buttons manually. |
352 |
352 |
* @param text An array of Strings. The length of the array will determine |
353 |
353 |
* the amount of radio buttons that will be created. |
354 |
354 |
* @return An array of radio buttons, in the same order as text. |
355 |
355 |
*/ |
356 |
356 |
public JRadioButton[] createRadioButtons(String text[]) { |
357 |
357 |
JRadioButton[] radioButtons = new JRadioButton[text.length]; |
358 |
358 |
ButtonGroup buttonGroup = new ButtonGroup(); |
359 |
359 |
for (int i=0; i<radioButtons.length; i++) { |
360 |
360 |
radioButtons[i] = new JRadioButton(text[i]); |
361 |
361 |
//radioButtons[i].setText(text[i]); |
362 |
362 |
buttonGroup.add(radioButtons[i]); |
363 |
363 |
this.addComponent(radioButtons[i]); |
364 |
364 |
} |
365 |
365 |
|
366 |
366 |
assert radioButtons.length == buttonGroup.getButtonCount() : "The amount of radio buttons ("+ radioButtons.length +") differs from the amount of buttons in buttonGroup ("+ buttonGroup.getButtonCount() +")."; |
367 |
367 |
return radioButtons; |
368 |
368 |
} |
369 |
369 |
|
370 |
370 |
public JTextField createTextField(String text) { |
371 |
371 |
JTextField textField = new JTextField(text); |
372 |
372 |
this.addComponent(textField); |
373 |
373 |
return textField; |
374 |
374 |
} |
375 |
375 |
|
376 |
376 |
/** |
377 |
377 |
* Adds a number spinner component to the GUI. |
378 |
378 |
* This method allows the caller to create a so-called "spinner component" |
379 |
379 |
* to the window. This spinner is an input box, in which only integers can |
380 |
380 |
* be put. |
381 |
381 |
* |
382 |
382 |
* The caller can set a range, a start value, and a step size. |
383 |
383 |
* |
384 |
384 |
* The spinner created with this method may modify itself based on the |
385 |
385 |
* parameters. |
386 |
386 |
* For example: If the minimum and maximum value are equal, the spinner will |
387 |
387 |
* be disabled. |
388 |
388 |
* |
389 |
389 |
* @param minimum The minimum value that can be selected. |
390 |
390 |
* @param maximum The maximum value that can be selected. |
391 |
391 |
* @param start The value that will initially be shown in the component. |
392 |
392 |
* @param stepSize The step size when the user increases/decreases the |
393 |
393 |
* value. |
394 |
394 |
* @throws IllegalArgumentException if minimum is larger than maximum, |
395 |
395 |
* start is not in the range of the selectable values, or stepsize is not a |
396 |
396 |
* natural number. |
397 |
397 |
* @return The JSpinner that was added to the window. |
398 |
398 |
*/ |
399 |
399 |
public JSpinner createSpinner(int minimum, int maximum, int start, int stepSize) { |
400 |
400 |
// As usual, we begin with checking the contract: |
401 |
401 |
if(minimum > maximum) { |
402 |
402 |
throw new IllegalArgumentException("The minimum value ("+ minimum +") was larger than the maximum value ("+ maximum +")"); |
403 |
403 |
} |
404 |
404 |
// The "start ∉ [minimum, maximum]" is done by the SpinnerNumberModel |
405 |
405 |
// constructor, which will be constructed later. |
406 |
406 |
if(stepSize <= 0) { // stepSize ∉ ℕ¹ (In Belgium: ℕ₀) |
407 |
407 |
throw new IllegalArgumentException("The stepSize ("+ stepSize +") is not a natural number (excluding 0)."); |
408 |
408 |
} |
409 |
409 |
// If the contract is valid, we can begin: |
410 |
410 |
/* |
411 |
411 |
* I'd like to interject here, because this is a nice example of why |
412 |
412 |
* JSugar was a good idea: |
413 |
413 |
* If you want a spinner, you'll typically want an integer spinner. But |
414 |
414 |
* in Swing, when you create a JSpinner, it creates a JSpinner, with a |
415 |
415 |
* predefined 'SpinnerNumberModel' attached to it. |
416 |
416 |
* It's this model you then have to extract from the created spinner, on |
417 |
417 |
* which you need to apply the configuration. |
418 |
418 |
* What you actually have to do, is create a SpinnerNumberModel |
419 |
419 |
* yourself, put your settings on that, and then, create a JSpinner to |
420 |
420 |
* which you give that SpinnerNumberModel. |
421 |
421 |
* In essence: The entire Java framework is shit. |
422 |
422 |
*/ |
423 |
423 |
SpinnerNumberModel spinnerSettings = new SpinnerNumberModel( |
424 |
424 |
start, |
425 |
425 |
minimum, |
426 |
426 |
maximum, |
427 |
427 |
stepSize |
428 |
428 |
); |
429 |
429 |
JSpinner spinner = new JSpinner(spinnerSettings); |
430 |
430 |
if(minimum == maximum) { // Trivial value is set already, --> disable. |
431 |
431 |
spinner.setEnabled(false); |
432 |
432 |
} |
433 |
433 |
this.addComponent(spinner); |
434 |
434 |
return spinner; |
435 |
435 |
} |
436 |
436 |
|
437 |
437 |
/** |
438 |
438 |
* Adds a number spinner component to the GUI. |
439 |
439 |
* This method allows the caller to create a so-called "spinner component" |
440 |
440 |
* to the window. This spinner is an input box, in which only integers can |
441 |
441 |
* be put. |
442 |
442 |
* |
443 |
443 |
* Tip: This method is a convenience method, and works extremely well with |
444 |
444 |
* arbitrary data. |
445 |
445 |
* For example: The start value is automatically set to the minimal possible |
446 |
446 |
* value, and the step size defaults to 1. |
447 |
447 |
* If the minimum and maximum are equal, the component will be disabled, and |
448 |
448 |
* thus, be locked on the only (trivially) possible value. |
449 |
449 |
* If minimum is larger than maximum, the method will notify you of this, |
450 |
450 |
* but also swap the values. So you can rest assured that the spinner will |
451 |
451 |
* handle errors, but also, inform you about it. |
452 |
452 |
* @param minimum The minimum value that can be selected. |
453 |
453 |
* @param maximum The maximum value that can be selected. |
454 |
454 |
* @return The JSpinner component that was added to the window. |
455 |
455 |
*/ |
456 |
456 |
public JSpinner createSpinner(int minimum, int maximum) { |
457 |
457 |
// The disabling of equal values is done in the full createSpinner(), so |
458 |
458 |
// this is merely switching values if they need to be swapped. |
459 |
459 |
if(minimum > maximum) { |
460 |
460 |
System.err.println("minimum ("+ minimum +") was larger than maximum ("+ maximum +")."); |
461 |
461 |
// FIXME: Consider whether it's appropriate to print a stacktrace |
462 |
462 |
// here, which may be convenient for debugging. |
463 |
463 |
|
464 |
464 |
// XXX: I know you don't need the help variable when swapping |
465 |
465 |
// integers, because you can also do basic arithmetics. Change it if |
466 |
466 |
// it causes too much eye burn for you. |
467 |
467 |
int swapValue = minimum; |
468 |
468 |
minimum = maximum; |
469 |
469 |
maximum = swapValue; |
470 |
470 |
} |
471 |
471 |
|
472 |
472 |
// Yeah, these 2 variables make you cringe huh, performance addicts? |
473 |
473 |
// Drown me in the tears of your useless performance-related opinions. |
474 |
474 |
int startValue = minimum; |
475 |
475 |
int stepSize = 1; |
476 |
476 |
return this.createSpinner(minimum, maximum, startValue, stepSize); |
477 |
477 |
} |
478 |
478 |
|
479 |
479 |
/** |
480 |
480 |
* Adds a combobox to the GUI. |
481 |
481 |
* Allows the caller to create a combobox by providing the values that |
482 |
482 |
* should be put in it. |
483 |
483 |
* |
484 |
484 |
* This method can only be used for String values. If that is not what you |
485 |
485 |
* need, consider creating your own combobox and adding it manually. Or, if |
486 |
486 |
* you need a combobox for integers, consider {@link #createSpinner}. |
487 |
487 |
* |
488 |
488 |
* WARNING: {@link JComboBox#getSelectedItem} returns an object, not a |
489 |
489 |
* String. You need to manually typecast this. This is a constraint of the |
490 |
490 |
* Swing framework. |
491 |
491 |
* @param items An array of Strings that will be put in the combobox. |
492 |
492 |
* @throws NullPointerException if one of the values in items is a null |
493 |
493 |
* pointer. |
494 |
494 |
* @throws IllegalArgumentException if items is empty. |
495 |
495 |
* @return The JCombobox that was added to the window. |
496 |
496 |
*/ |
497 |
497 |
public JComboBox<String> addComboBox(String[] items) { |
498 |
498 |
// Contract validation: |
499 |
499 |
if(items.length == 0) { |
500 |
500 |
throw new IllegalArgumentException("The given array of items was empty."); |
501 |
501 |
} |
502 |
502 |
for(String item : items) { |
503 |
503 |
if(item == null) { |
504 |
504 |
throw new NullPointerException("One of the given Strings is a null pointer."); |
505 |
505 |
} |
506 |
506 |
} |
507 |
507 |
// Contract validated, create the component: |
508 |
508 |
JComboBox<String> comboBox = new JComboBox<String>(items); |
509 |
509 |
comboBox.setSelectedIndex(0); |
510 |
510 |
if(comboBox.getItemCount() == 1) { // Trivial selection |
511 |
511 |
comboBox.setEnabled(false); |
512 |
512 |
} |
513 |
513 |
this.addComponent(comboBox); |
514 |
514 |
return comboBox; |
515 |
515 |
} |
516 |
516 |
|
517 |
517 |
/** |
518 |
518 |
* Creates a list of the given data, and adds it to the GUI. |
519 |
519 |
* This will create a JList component, containing the given data. |
520 |
520 |
* To jar up your memory: A list in this context, is a component in which |
521 |
521 |
* data of the same type is printed out. The user of said list, can then |
522 |
522 |
* select a subset of these items. |
523 |
523 |
* |
524 |
524 |
* @see JList for a collection of possible operations. |
525 |
525 |
* @param items The String items that will be put in the list. |
526 |
526 |
* @throws NullPointerException if one of the values in items is a null |
527 |
527 |
* pointer. |
528 |
528 |
* @throws IllegalArgumentException if items is empty. |
529 |
529 |
* @return A JList component, that was a added to the GUI. |
530 |
530 |
*/ |
531 |
531 |
public JList createList(String[] items) { |
532 |
532 |
// Contract validation: |
533 |
533 |
if(items.length == 0) { |
534 |
534 |
throw new IllegalArgumentException("The given array of items was empty."); |
535 |
535 |
} |
536 |
536 |
for(String item : items) { |
537 |
537 |
if(item == null) { |
538 |
538 |
throw new NullPointerException("One of the given Strings is a null pointer."); |
539 |
539 |
} |
540 |
540 |
} |
541 |
541 |
// Contract validated, create the component: |
542 |
542 |
JList list = new JList(items); |
543 |
543 |
this.addComponent(list); |
544 |
544 |
return list; |
545 |
545 |
} |
546 |
546 |
|
547 |
547 |
/** |
548 |
548 |
* Creates a table of the given data, and adds it to the GUI. |
549 |
549 |
* This method allows you to create a table with sorting functionality in |
550 |
550 |
* the GUI. |
551 |
551 |
* This method relies on implications, deducted from the given data. That |
552 |
552 |
* is, the length of the rows and columns is calculated by the longest |
553 |
553 |
* length it can find in the nested array. |
554 |
554 |
* To change the data, take a look at the JTable documentation. |
555 |
555 |
* |
556 |
556 |
* @see JTable for a collection of possible operations. |
557 |
557 |
* @param items The String items that will be put in the list. |
558 |
558 |
* @throws NullPointerException if one of the values in items is a null |
559 |
559 |
* pointer. |
560 |
560 |
* @throws IllegalArgumentException if items is empty, or the amount of |
561 |
561 |
* column names does not correspond with the given amount of items. |
562 |
562 |
* @return A JTable component, that was a added to the GUI. |
563 |
563 |
*/ |
564 |
564 |
public JTable createTable(String[] columns, String[][] items) { |
565 |
565 |
// Contract validation: |
566 |
566 |
if(items.length == 0) { |
567 |
567 |
throw new IllegalArgumentException("The given array of items was empty."); |
568 |
568 |
} |
569 |
569 |
if(columns.length != items[0].length) { |
570 |
570 |
throw new IllegalArgumentException("The amount of columns does not correspond to the given amount of items."); |
571 |
571 |
} |
572 |
572 |
for(int i=0; i<items.length; i++) { |
573 |
573 |
if(items[i] == null) { |
574 |
574 |
throw new NullPointerException("One of the given Strings is a null pointer."); |
575 |
575 |
} |
576 |
576 |
} |
577 |
577 |
// Contract validated, create the component: |
578 |
578 |
// Deducting max length: |
579 |
579 |
int columnCount = columns.length; |
580 |
580 |
int rowCount = items.length; |
581 |
581 |
|
582 |
582 |
JTable table = new JTable(items, columns); |
583 |
583 |
this.addComponent(table); |
584 |
584 |
return table; |
585 |
585 |
} |
586 |
586 |
|
587 |
587 |
/** |
588 |
588 |
* Adds the given component to the GUI. |
589 |
589 |
* This method allows its caller to give a pre-made component, so that it |
590 |
590 |
* can be added to the GUI. Even though its main use is for the Window class |
591 |
591 |
* itself, the user of JSugar can also use it to create components himself, |
592 |
592 |
* and then add them. As such, this method doesn't provide parameters for |
593 |
593 |
* reflection/action triggering purposes. |
594 |
594 |
* @param component The component to be added to the window. |
595 |
595 |
* @throws NullPointerException if the given component is a null pointer. |
596 |
596 |
*/ |
597 |
597 |
public void addComponent(JComponent component) { |
598 |
598 |
int originalSize = this.panel.getComponentCount(); |
599 |
599 |
this.panel.add(component); // Throws the exception if null. |
600 |
600 |
this.updateWindow(); |
601 |
601 |
|
602 |
602 |
assert originalSize == this.panel.getComponentCount()-1 : "A component was supposed to be added to the window, but the total amount of components was unchanged after the addition."; |
603 |
603 |
} |
604 |
604 |
|
605 |
605 |
/** |
606 |
606 |
* Removes the given component from the GUI. |
607 |
607 |
* This method allows its caller to remove a component from the GUI. |
608 |
608 |
* @param component The component to be removed. |
609 |
609 |
* @throws NoSuchElementException if the given component does not exist in |
610 |
610 |
* the GUI. |
611 |
611 |
* @throws NullPointerException if the given component is a null pointer. |
612 |
612 |
*/ |
613 |
613 |
public void removeComponent(JComponent component) { |
614 |
614 |
int originalSize = this.panel.getComponentCount(); |
615 |
615 |
this.panel.remove(component); |
616 |
616 |
int newSize = this.panel.getComponentCount(); |
617 |
617 |
if (originalSize != newSize+1) { |
618 |
618 |
throw new NoSuchElementException("The given component does not exist in the GUI."); |
619 |
619 |
} |
620 |
620 |
this.updateWindow(); |
621 |
621 |
} |
622 |
622 |
/** |
623 |
623 |
* Prompts the user with a file chooser dialog. |
624 |
624 |
* By calling this method, the user will be presented with a file chooser |
625 |
625 |
* dialog, out of which a single file can be selected. If the selected file |
626 |
626 |
* exists, a File object is returned, a null pointer if the user cancelled. |
627 |
627 |
* @return A File object representing the file the user selected, or null |
628 |
628 |
* otherwise. |
629 |
629 |
*/ |
630 |
630 |
public File openFileChooserDialog() { |
631 |
631 |
JFileChooser fileDialog = new JFileChooser(); |
632 |
632 |
fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY); |
633 |
633 |
|
634 |
634 |
int userResponse = fileDialog.showOpenDialog(this.panel); |
635 |
635 |
if(userResponse == JFileChooser.APPROVE_OPTION) { |
636 |
636 |
return fileDialog.getSelectedFile(); |
637 |
637 |
} |
638 |
638 |
else { |
639 |
639 |
return null; |
640 |
640 |
} |
641 |
641 |
} |
642 |
642 |
|
643 |
643 |
/** Prompts the user with a message dialog. |
644 |
644 |
* This method creates a dialog that is purely informative. As such, it |
645 |
645 |
* only presents the user with the option to confirm it. |
646 |
646 |
* @param text The text to be presented in the dialog box. |
647 |
647 |
* @pre text mustn't be null or an empty string. |
648 |
648 |
* @throws NullPointerException if text is a null pointer. |
649 |
649 |
* @throws IllegalArgumentException if text is an empty string. |
650 |
650 |
*/ |
651 |
651 |
public void messageDialog(String text) { |
652 |
652 |
if(text.isEmpty()) |
653 |
653 |
throw IllegalArgumentException("text mustn't be an empty string."); |
654 |
- | JOptionPane.showMessageDialog(this.frame, text); |
+ |
654 |
JOptionPane.showMessageDialog(this.frame, text); |
655 |
655 |
} |
656 |
656 |
|
657 |
657 |
/** |
658 |
658 |
* Closes this window. |
659 |
659 |
* This method tells this Window's JFrame that a closing event has been |
660 |
660 |
* fired. |
661 |
661 |
* Use this method when you need to programatically close this window. |
662 |
662 |
*/ |
663 |
663 |
public void close() { |
664 |
664 |
this.frame.dispatchEvent(new WindowEvent(this.frame, WindowEvent.WINDOW_CLOSING)); |
665 |
665 |
} |
666 |
666 |
} |
667 |
667 |
Challenge 6/ontwerpkeuzes2.md ¶
9 additions and 0 deletions.
View changes Hide changes
1 |
1 |
|
2 |
2 |
## GRASP |
3 |
3 |
Om zoveel mogelijk tegemoet te komen aan de GRASP-richtlijnen, heb ik volgende |
4 |
4 |
ontwerpkeuzes toegepast voor de verschillende richtlijnen. |
5 |
5 |
|
6 |
6 |
### High cohesion & Low coupling |
7 |
7 |
- Veel classes zijn totaal van elkaar losgekoppeld. Een goed voorbeeld hiervan |
8 |
8 |
is Bed; Bed weet niet over dat er Reservaties bestaan, zelfs niet in welke |
9 |
9 |
Room het 'staat'. |
10 |
10 |
- Als er toch gekoppeld moet worden, dan blijft dit tot een minimum beperkt; Een |
11 |
11 |
Room weet wel welke Bedden het heeft, maar niet hoe deze gebruikt worden, een |
12 |
12 |
Reservation weet wel welke Bedden voor hem gereserveerd zijn, maar heeft geen |
13 |
13 |
weet van welke kamer dat deze staat. |
14 |
14 |
- Uitzondering op deze regel vormen de Controller-classes; omwille van hun taken |
15 |
15 |
zijn deze vaak verplicht om van veel verschillende classes op de hoogte te |
16 |
16 |
zijn. |
17 |
17 |
|
18 |
18 |
### Indirectie |
19 |
19 |
- In plaats van functionaliteit toe te wijzen aan class X om met class Y te |
20 |
20 |
interageren, wordt er vaak gebruik gemaakt van een class Z, die deze |
21 |
21 |
verantwoordelijkheid op zich neemt. |
22 |
22 |
- Dit wordt toegepast d.m.v. een MVC-structuur, waarin de \*Controller-classes |
23 |
23 |
als 'tussenpersoon' werken. |
24 |
24 |
- Dit komt ook tegemoet aan de eigenschappen van Information Expertise. |
25 |
25 |
|
26 |
26 |
### Creator |
27 |
27 |
- Controllers staan ook in als zgn. Creators van de models die ze beheren. Ze |
28 |
28 |
voldoen dan ook aan de eigenschappen zoals |
29 |
29 |
[hier](https://en.wikipedia.org/wiki/GRASP_(object-oriented_design)#Creator) |
30 |
30 |
opgesomd wordt. |
31 |
31 |
|
32 |
32 |
|
33 |
33 |
## SOLID |
34 |
34 |
|
35 |
35 |
### Single Responsibility |
36 |
36 |
|
37 |
37 |
## Algemene keuzes ter bevordering codekwaliteit |
38 |
38 |
|
39 |
39 |
## Strings, integers, ... i.p.v. Enums/Eigen classes |
40 |
40 |
In het programma zijn er sommige members die doen vermoeden dat ze beter als |
41 |
41 |
Enums aangeduid kunnen worden; Het type van een kamer, faciliteiten, ... |
42 |
42 |
|
43 |
43 |
In plaats van een speciale class hiervoor te maken, heb ik gekozen om deze |
44 |
44 |
gewoon te behandelen als simpele Strings in lijsten. |
45 |
45 |
|
46 |
46 |
Het voordeel aan deze werkwijze, is dat Strings in Java SE quasi universeel |
47 |
47 |
voorkomen; data kunnen geconverteerd worden naar Strings (en vice versa), veel |
48 |
48 |
gebruikte GUI-libraries (waaronder Swing, JavaFX, ...) gebruiken Strings voor |
49 |
49 |
tekst voor te stellen in de GUI-widgets, ... |
50 |
50 |
Daarnaast weet iedereen die ook maar een beetje geschoold is in Java, direct hoe |
51 |
51 |
deze members behandeld kunnen worden. Had ik een class gemaakt die bv. |
52 |
52 |
RoomFacilities heette, dan is die parate kennis over de taal zelf nutteloos. |
53 |
53 |
|
54 |
54 |
Strings zijn ook voorzien van extreem veel methods, en zijn dus zeer flexibele |
55 |
55 |
classes om te gebruiken. Het feit dat ze zo makkelijk doorheen het programma te |
56 |
56 |
gebruiken zijn is dus een groot pluspunt. |
57 |
57 |
|
58 |
58 |
Als dat nog niet genoeg is: Het gebruiken van de hulpmiddelen uit de _standard |
59 |
59 |
library_ is niet enkel waarvoor libraries überhaupt bestaan, het beperkt ook |
60 |
60 |
het benodigde aantal classes in de eigen software, waardoor het geheel |
61 |
61 |
uiteindelijk veel overzichtelijker blijft, en bijgevolg, makkelijk uit te |
62 |
62 |
breiden, te testen, te begrijpen, ... |
63 |
63 |
|
64 |
64 |
Al deze voordelen graan grotendeels verloren als ik beslis om zelf |
65 |
65 |
gespecialiseerde classes op te stellen. |
66 |
66 |
Men kan misschien stellen dat "de voordelen die het schrijven van eigen |
67 |
67 |
classes veel beter tegemoet komt aan de essentie van object-georiënteerd |
68 |
68 |
programmeren, dan zich _beperken_ tot slechts een handvol generieke classes, die |
69 |
69 |
misschien niet volledig aan de benodigdheden beantwoorden". |
70 |
70 |
|
71 |
71 |
Toch laat ik mijn afweging overslaan in het voordeel van minder classes, en meer |
72 |
72 |
uniformiteit. _Sometimes, less is more._ Meer classes betekent ook: |
73 |
73 |
- Meer onderhoud |
74 |
74 |
- Meer kans op bugs |
75 |
75 |
- Groter programma |
76 |
76 |
|
77 |
77 |
En al die problemen resulteren op lange termijn in: |
78 |
78 |
- Slechtere uitbreidbaarheid |
79 |
79 |
- Slechtere onderhoudbaarheid |
80 |
80 |
- Slechtere schaalbaarheid |
81 |
81 |
|
82 |
82 |
Zijn al die problemen de moeite van een (zogezegd) "beter object-georiënteerd |
83 |
83 |
design" wel waard? |
84 |
84 |
Veel mensen stellen juist dat OOP net voordelig is als men op zoek is naar |
85 |
85 |
- Uitbreidbaarheid |
86 |
86 |
- Onderhoudbaarheid |
87 |
87 |
- Modulariteit |
88 |
88 |
- Schaalbaarheid |
89 |
89 |
|
90 |
90 |
Als de voordelen van het paradigma verdwijnen als ik dat "beter design" volg, |
91 |
91 |
is dat design dan wel echt beter? |
92 |
92 |
|
93 |
93 |
|
94 |
94 |
**Conclusie: Uniforme classes zijn volgens mij soms beter dan een stel |
95 |
95 |
gespecialiseerde classes. Daarom dat ik mij soms behelp met Strings, intergers, |
96 |
96 |
... i.p.v. zelf een oplossing te schrijven.** |
97 |
97 |
|
98 |
98 |
|
99 |
99 |
### Null pointers |
100 |
100 |
Het valt misschien op dat ik doorheen mijn programma in veel contracten eis dat |
101 |
101 |
geen enkele parameter een zgn. *null pointer* is. |
102 |
102 |
|
103 |
103 |
Het gebruik van null pointers staat garant voor een overvloed aan moeilijk te |
104 |
104 |
vinden bugs die (door het design van objectgeörienteerd programmeren) enorm diep |
105 |
105 |
kunnen doorpropageren. |
106 |
106 |
|
107 |
107 |
Ik maak er een kerntaak van dat, als er aan mijn programma gewerkt wordt, de |
108 |
108 |
programmeur zichzelf steeds kan garanderen dat **alle** data die hij |
109 |
109 |
ontvangt, valide data is. |
110 |
110 |
Op deze manier valt er een hele last van de schouders van de programmeur; een |
111 |
111 |
reeks fouten worden voorkomen, simpelweg door een strikt schema aan te houden. |
112 |
112 |
|
113 |
113 |
Het controleren op null pointers wordt op 2 manieren gedaan: |
114 |
114 |
- Gebruik van *methods* aanwezig in het gegeven type. Als de gegeven variabele |
115 |
115 |
een null pointer is, zal het programma direct crashen, en een |
116 |
116 |
NullPointerException geven. |
117 |
117 |
- Expliciet testen of *var == null*. Wordt toegepast op parameters die direct |
118 |
118 |
als members opgeslagen dienen te worden. |
119 |
119 |
|
120 |
120 |
Deze (contractuele) controle laat toe dat, mocht er een null pointer gebruikt |
121 |
121 |
worden, het programma de programmeur hiervan direct op de hoogte stelt, en dit |
122 |
122 |
op het laagst mogelijke niveau (namelijk de eerste method waar deze waarde |
123 |
123 |
gebruikt wordt). |
124 |
124 |
|
125 |
125 |
### Cloning |
126 |
126 |
members 'private' maken (encapsuleren) is volledig nutteloos als men getters en |
127 |
127 |
setters op deze members toepast; In Java worden references doorgegeven (m.u.v. |
128 |
128 |
primitives), die de hele notie van encapsulatie voorbijgaan (bij sommige types). |
129 |
129 |
Een voorbeeld hiervan is het privatiseren van een Set<T>-member: men kan daar |
130 |
130 |
een 'getSet()'-method op plaatsen, en dan toch de inhoud van deze 'private' |
131 |
131 |
aanpassen. |
132 |
132 |
|
133 |
133 |
Ik heb geopteerd om, waar van toepassing, deze variabelen te 'clonen', om zo |
134 |
134 |
exacte kopieën terug te geven. |
135 |
135 |
Deze manier van werken brengt enkele voordelen teweeg: |
136 |
136 |
- Zeer defensief programmeren; De ontwikkelaar kan geen members aanpassen als |
137 |
137 |
dat niet de bedoeling was |
138 |
138 |
- Duidelijkheid code: getSet().clear() zal de member niet meer leegmaken. Om dat |
139 |
139 |
te doen, moet men gebruikmaken van de method die daarvoor bedoeld is: |
140 |
140 |
setSet(clearedSet) |
141 |
141 |
|
142 |
142 |
### Inheritance |
143 |
143 |
Overerving is een goed concept over het algemeen, maar **niet** in OOP. |
144 |
144 |
De problemen omtrent impliciet gedrag en onnodige *state* zijn al te vaak |
145 |
145 |
beschreven met OOP-inheritance. |
146 |
146 |
|
147 |
147 |
Ik heb in mijn programma geen gebruik gemaakt van inheritance, exact omwille van |
148 |
148 |
de problemen die het voortbrengt, zeker in termen van herbruikbaarheid en |
149 |
149 |
robuustheid, wat toch zware vereisten waren voor deze opdracht. |
150 |
150 |
|
151 |
151 |
Ik heb al mijn problemen makkelijk kunnen oplossen d.m.v. compositie. |
152 |
152 |
|
153 |
153 |
### Benaming variabelen |
154 |
154 |
Doorheen mijn programma maak ik heel veel gebruik van dezelfde benamingen. |
155 |
155 |
Bijvoorbeeld: Een variabele van het type Reservation zal haast altijd |
156 |
156 |
'reservation' heten, een Set van een bepaald type zal de naam van datzelfde type |
157 |
157 |
dragen, in het meervoud, ... |
158 |
158 |
|
159 |
159 |
Sommige programmeurs gebruiken liever afkortingen (bv. 'reservation' --> |
160 |
160 |
'resv'), omdat dit sneller schrijft. |
161 |
161 |
|
162 |
162 |
Naar mijn mening moet men bij deze werkwijze inleveren aan leesbaarheid, vooral |
163 |
163 |
wanneer iemand die nog nooit met de code gewerkt heeft, dit programma moet |
164 |
164 |
overnemen. |
165 |
165 |
|
166 |
166 |
Daarnaast zorgt de consistentie van woordgebruik ervoor dat een andere |
167 |
167 |
programmeur, doorheen het hele programma dezelfde context in zijn/haar gedachten |
168 |
168 |
kan gebruiken. |
169 |
169 |
|
170 |
170 |
|
171 |
171 |
|
172 |
172 |
|
173 |
173 |
------------------------------------------------------------ TODO |
174 |
174 |
## Toepassing types/classes |
175 |
175 |
Doorheen het programma heb ik getracht zoveel mogelijk gebruik te maken van |
176 |
176 |
types/classes die |
177 |
177 |
- Veelvoorkomend zijn in de Java STL (Zoals String, Set, List, ...) |
178 |
178 |
- primitief zijn (ints, ...), omdat deze operatoren hebben en de code |
179 |
179 |
leesbaarder maken |
180 |
180 |
|
181 |
181 |
Een goed voorbeeld hiervan zijn bv. de faciliteiten: |
182 |
182 |
I.p.v. een aparte "Facility"-class te maken, heb ik de verschillende |
183 |
183 |
faciliteiten voorgesteld door een simpele String. De voordelen van deze aanpak |
184 |
184 |
zijn ook direct duidelijk: |
185 |
185 |
- Betekenis is direct duidelijk; de faciliteit letterlijk in de code vernoemd |
186 |
186 |
- Makkelijke interactie met GUI, die sowieso Strings vraagt voor bv. JLabel |
187 |
187 |
- Uitbreidbaarheid wordt bekomen door simpelweg een nieuwe String te |
188 |
188 |
introduceren |
189 |
189 |
|
190 |
190 |
## View en GUI |
191 |
191 |
Werken met GUI's is vaak tijdrovend en veroorzaakt snel errors, zeker met bv. |
192 |
192 |
anonieme methods, exceptions, ... |
193 |
193 |
Alhoewel mijn programma grotendeels in een MVC-stijl is geschreven, maken de |
194 |
194 |
view-classes (RegistrationView, SearchView, ...) achterliggend gebruik van een |
195 |
195 |
zelfgemaakt framework om makkelijk vensters te maken. |
196 |
196 |
Dit kleine framework is een persoonlijk hobbyproject dat ik JSugar noem. |
197 |
197 |
Het biedt een heleboel voordelen, vergeleken met elk GUI-venster zelf opstellen: |
198 |
198 |
- Vaak gebruikte GUI-widgets (zoals een label, textfield) worden aangemaakt en |
199 |
199 |
toegevoegd door slechts 1 method op te roepen |
200 |
200 |
- JSugar maakt gebruik van reflectie om op een leesbare en uitbreidbare manier |
201 |
201 |
knoppen te activeren: |
202 |
202 |
public JButton createButton(String text, String action, String methodName, Object triggerObject) |
203 |
203 |
'methodName' is een simpele String, en 'triggerObject' is het object waar deze |
204 |
204 |
method moet worden opgeroepen. |
205 |
205 |
- Automatische uitlijning van widgets |
206 |
206 |
Voor meer informatie kunt u JSugar-readme.md raadplegen. |
207 |
207 |
|
208 |
208 |
## java.util.Date |
209 |
209 |
|
210 |
210 |
Doorheen het programma maak ik vaak gebruik van de Date-class uit de Java STL, |
211 |
211 |
om de volgende redenen: |
212 |
212 |
- Uitbreidbaarheid: Door overal eenzelfde type te gebruiken, is het gemakkelijk |
213 |
213 |
om nieuwe modules toe te voegen, zonder dat daarvoor abstractielagen etc. |
214 |
214 |
nodig zijn. |
215 |
215 |
- Uniformiteit: Eenzelfde type uit de STL laat de ontwikkelaar toe om door het |
216 |
216 |
hele programma heen hetzelfde denkpatroon aan te houden; een |
217 |
217 |
Stringvoorstelling hier, een integer daar, ... veroorzaken problemen en bugs, |
218 |
218 |
die met deze class voorkomen worden. |
219 |
219 |
|
220 |
220 |
## Bedden |
+ |
221 |
|
+ |
222 |
Het moeilijkste om te implementeren waren de data voor ontbijt. |
+ |
223 |
Er wordt voor de reservaties steeds een set bijgehouden van de data waarop |
+ |
224 |
ontbijt besteld is. |
+ |
225 |
Op zich is dit niet zo moeilijk. Het moeilijke hieraan, is de gebruiker van het |
+ |
226 |
programma op een begrijpelijke manier deze data te laten invoeren. |
+ |
227 |
Data worden ingevoerd via tekstvelden, omdat Swing geen zgn. "DatePicker" heeft. |
+ |
228 |
|
+ |
229 |
## Bedden |
221 |
230 |
|
222 |
231 |
Ik heb voor bedden een aparte class aangemaakt, omdat deze een bepaalde state |
223 |
232 |
bijhouden, nml. wanneer ze gereserveerd zijn. |
224 |
233 |
|
225 |
234 |
|
226 |
235 |
## Reservation |
227 |
236 |
Voor de reservaties heb ik besloten om enkel die data bij te houden die inherent |
228 |
237 |
gerelateerd is aan die reservatie: |
229 |
238 |
- Enkel gereserveerde bedden i.p.v. gereserveerde kamers. Qua uitbreidbaarheid |
230 |
239 |
heeft dit tot gevolg dat men gemakkelijk kan uitbreiden naar reservaties, |
231 |
240 |
gespreid over verschillende kamers. |
232 |
241 |
|
233 |
242 |
|
234 |
243 |
## ReservationView |
235 |
244 |
Merk op hoe in ReservationView de data van de Reservation direct in de GUI wordt |
236 |
245 |
geplaatst. |
237 |
246 |
Dit staat toe om zeer gemakkelijk deze class te hergebruiken voor zowel het |
238 |
247 |
**aanmaken** als het **updaten** van de reservatie. |
239 |
248 |