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