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