/* * Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de) * * Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://creativecommons.org/licenses/by-nc-sa/3.0/ * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.knurt.fam.core.util.booking; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Properties; import de.knurt.fam.core.model.config.BookingRule; import de.knurt.fam.core.model.config.FacilityBookable; import de.knurt.fam.core.model.config.TimeBasedBookingRule; import de.knurt.fam.core.model.persist.FacilityAvailability; import de.knurt.fam.core.model.persist.User; import de.knurt.fam.core.model.persist.booking.BookingStatus; import de.knurt.fam.core.model.persist.booking.TimeBooking; import de.knurt.heinzelmann.util.shopping.Purchasable; import de.knurt.heinzelmann.util.time.SimpleTimeFrame; import de.knurt.heinzelmann.util.time.TimeFrame; /** * before booking a time slot, any user must request a booking with this object. * this class has a double function. on the one hand, it searches for free time * slots and, as a next step, it is the base for a concrete booking. in other * words use this class for this purposes: * <ul> * <li>user requested a facility for a specific time</li> * <li>answer question: what is, when user would request a facility at a * specific time?</li> * <li>what are requests resulting in positive answers?</li> * </ul> * * @see TimeBooking#getNewBooking(de.knurt.fam.core.util.booking.TimeBookingRequest) * @author Daniel Oltmanns * @since 0.20090624 */ public class TimeBookingRequest implements Purchasable, Cloneable { private BookingRule bookingRule; private User user; private int requestedCapacityUnits; private int requestedTimeUnits; private Calendar requestedStartTime; /** * clone it (without exception) and return * * @return the clone */ @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { throw new Error("implements Cloneable!"); } } /** * construct a time booking request. set all facts for this requests. * * @param bookingRule * used to book the facility * @param user * requesting the booking * @param capacityUnitsInterestedIn * capacity units requested * @param timeUnitsInterestedIn * time units requested * @param startTimeInterestedIn * the start time requested */ public TimeBookingRequest(BookingRule bookingRule, User user, int capacityUnitsInterestedIn, int timeUnitsInterestedIn, Calendar startTimeInterestedIn) { this.bookingRule = bookingRule; this.user = user; this.requestedCapacityUnits = capacityUnitsInterestedIn; this.requestedTimeUnits = timeUnitsInterestedIn; startTimeInterestedIn.set(Calendar.MILLISECOND, 0); startTimeInterestedIn.set(Calendar.SECOND, 0); this.requestedStartTime = startTimeInterestedIn; } /** * return true, if the request is valid and available. * * @see TimeBooking#isAvailableForInsertion() * @return true, if the request is valid and available */ public boolean isAvailable() { return this.isValidRequest() && TimeBooking.getNewBooking(this).isAvailableForInsertion(); } /** * return free time slots for the requested day. if there are less hits then * needed, search next day. if there are more hits then allowed, cut result * set. * * @param minTimeSlots * min time slots needed * @param maxTimeSlots * max time slots allowed * @return free time slots for the requested day. */ public List<TimeBookingRequest> getFreeBookingRequestsOfDay(int minTimeSlots, int maxTimeSlots) { List<TimeBookingRequest> freeTimeSlots = this.getFreeBookingRequestsStartingSameDay(); if (freeTimeSlots.size() < minTimeSlots) { int nextDayCount = 1; List<TimeBookingRequest> tmp = this.getFreeBookingRequestsOfNextDay(nextDayCount); while (freeTimeSlots.size() < minTimeSlots) { freeTimeSlots.addAll(tmp); tmp = this.getFreeBookingRequestsOfNextDay(++nextDayCount); } freeTimeSlots.subList(minTimeSlots - 1, freeTimeSlots.size() - 1).clear(); // cut // to // min } else if (freeTimeSlots.size() > maxTimeSlots) { // to many time slots freeTimeSlots.subList(maxTimeSlots - 1, freeTimeSlots.size() - 1).clear(); // cut // to // max } return freeTimeSlots; } /** * return true, if the requested time frame of <code>other</code> overlaps * with this requested time frame. * * @see #getRequestedTimeFrame() * @param other * to check * @return true, if the requested time frame of <code>other</code> overlaps * with this requested time frame. */ public boolean overlaps(TimeBookingRequest other) { return this.getRequestedTimeFrame().overlaps(other.getRequestedTimeFrame()); } /** * return true, if the requested start time is on the same day of year on * <code>this</code> and <code>given</code>. the year does not matter, so * this is true on requested start times: 3.5.2008 and 3.5.1981 * * @param given * compared to * @return true, if the requested start time is on the same day of year on * <code>this</code> and <code>given</code>. */ public boolean startsOnSameDayAs(TimeBookingRequest given) { return this.getRequestedStartTime().get(Calendar.DAY_OF_YEAR) == given.getRequestedStartTime().get(Calendar.DAY_OF_YEAR); } private TimeBookingRequest getConcreteBookingRequest(TimeFrame fromTo) { return new TimeBookingRequest(bookingRule, user, requestedCapacityUnits, requestedTimeUnits, fromTo.getCalendarStart()); } /** * return the facility, this request is for. * * @return the facility, this request is for. */ public FacilityBookable getFacility() { return this.bookingRule.getFacility(); } /** * return a time frame starting at {@link #requestedStartTime} and ending * after {@link #requestedTimeUnits} * * @return a time frame starting at {@link #requestedStartTime} and ending * after {@link #requestedTimeUnits} */ public TimeFrame getRequestedTimeFrame() { Calendar end = (Calendar) this.getRequestedStartTime().clone(); end.add(Calendar.MINUTE, this.getRequestedDurationInMinutes()); return new SimpleTimeFrame(this.getRequestedStartTime(), end); } /** * return the entire day as requested * * @return the entire day as requested */ private Calendar getCalendarOfRequestedDay() { Calendar day = (Calendar) this.getRequestedStartTime().clone(); day.set(Calendar.HOUR_OF_DAY, 0); day.set(Calendar.MINUTE, 0); day.set(Calendar.SECOND, 0); day.set(Calendar.MILLISECOND, 0); return day; } /** * return all free time slots of the day, the user is interested in. * * it is a free time slot if:<br /> * <ul> * <li>{@link FacilityAvailability} is * {@link FacilityAvailability#COMPLETE_AVAILABLE}</li> * <li>the {@link TimeBooking} IS NOT {@link BookingStatus#STATUS_APPLIED} * or {@link BookingStatus#STATUS_BOOKED}</li> * <li>the {@link TimeBooking} IS {@link BookingStatus#STATUS_APPLIED} or * {@link BookingStatus#STATUS_BOOKED} BUT the {@link BookingRule} has * enough capacity units ({@link BookingRule#getMaxBookableCapacityUnits()}) * left</li> * </ul> * * return <code>null</code> on in invalid requests (asking for too many or * less units). * * @return all free time slots of the day, the user is interested in. */ public List<TimeFrame> getFreeTimeSlotsOfDay() { ArrayList<TimeFrame> result = null; if (this.isValidRequest()) { // result is not null but empty result = new ArrayList<TimeFrame>(); // get candidates ArrayList<TimeFrame> candidates = this.getCandidatesForFreeTimeSlotsOfDay(); TimeBooking bookingTry = TimeBooking.getNewBooking(this); // check every single candidate for (TimeFrame candidate : candidates) { if (bookingTry.isAvailableForInsertion(candidate)) { result.add(candidate); } } } return result; } /** * return BookingRequests instead of TimeFrames and do the same as * {@link TimeBookingRequest#getFreeTimeSlotsOfDay()}. * * @return BookingRequests instead of TimeFrames and do the same as * {@link TimeBookingRequest#getFreeTimeSlotsOfDay()}. */ public List<TimeBookingRequest> getFreeBookingRequestsStartingSameDay() { List<TimeBookingRequest> result = new ArrayList<TimeBookingRequest>(); List<TimeFrame> tfs = this.getFreeTimeSlotsOfDay(); assert tfs != null; for (TimeFrame tf : tfs) { result.add(this.getConcreteBookingRequest(tf)); } return result; } /** * return all free time slots candidates starting on requested day. * * the free time slot candidates are simply the requested day sliced in * pieces as given in the requested time units. * * steps are used as defined in {@link #getDefaultMinutesOfOneSteps()) or, * if it is smaller, in {@link BookingRule#getSmallestMinutesBookable} * * the duration between the slices is as defined in the booking rule. * * @see BookingRule#smallestMinutesBookable * @return all free time slots candidates for the requested day. */ private ArrayList<TimeFrame> getCandidatesForFreeTimeSlotsOfDay() { ArrayList<TimeFrame> candidates = new ArrayList<TimeFrame>(); int minuteSteps = getMinutesOfOneStep(this.bookingRule); if (Calendar.getInstance().before(this.getCalendarOfRequestedDay()) || this.isRequest42day()) { Calendar start = this.getCalendarOfRequestedDay(); if (this.getBookingRule().getMustStartAt() != null) { start.set(Calendar.MINUTE, this.getBookingRule().getMustStartAt().intValue()); } Calendar end = (Calendar) start.clone(); int requestedMinutes = this.getRequestedDurationInMinutes(); end.add(Calendar.MINUTE, requestedMinutes); TimeFrame candidate = new SimpleTimeFrame(start, end); start.add(Calendar.DAY_OF_YEAR, 1); long stopOn = start.getTimeInMillis(); start.add(Calendar.DAY_OF_YEAR, -1); if (this.isRequest42day()) { while (candidate.getCalendarStart().before(Calendar.getInstance())) { candidate.add(Calendar.MINUTE, minuteSteps); } } while (candidate.getStart() < stopOn) { candidates.add(candidate.clone()); candidate.add(Calendar.MINUTE, minuteSteps); } } return candidates; } /** * return the minutes of one step. use * {@link TimeBasedBookingRule#getSmallestMinutesBookable()} for that to * avoid bad "booking parking". * * @param br * rules using to book a facility. * @return the minutes of one step. */ public static int getMinutesOfOneStep(BookingRule br) { return br.getSmallestMinutesBookable(); } private List<TimeBookingRequest> getFreeBookingRequestsOfNextDay(int days) { Calendar nextDay = (Calendar) this.getCalendarOfRequestedDay().clone(); if (this.getBookingRule().getMustStartAt() != null) { nextDay.set(Calendar.MINUTE, this.getBookingRule().getMustStartAt().intValue()); } nextDay.add(Calendar.DAY_OF_YEAR, days); TimeBookingRequest br4nextDay = new TimeBookingRequest(this.bookingRule, this.user, this.requestedCapacityUnits, this.requestedTimeUnits, nextDay); return br4nextDay.getFreeBookingRequestsStartingSameDay(); } private int getRequestedDurationInMinutes() { return this.getRequestedTimeUnits() * this.bookingRule.getSmallestMinutesBookable(); // minutes } /** * return the booking rule used for this request. * * @return the booking rule used for this request. */ public BookingRule getBookingRule() { return bookingRule; } /** * set the booking rule used for this request. * * @param bookingRule * the booking rule used for this request. */ public void setBookingRule(BookingRule bookingRule) { this.bookingRule = bookingRule; } /** * return user requests a time booking. * * @return the user requests a time booking. */ public User getUser() { return user; } /** * set user requests a time booking. * * @param user * requests a time booking */ public void setUser(User user) { this.user = user; } /** * return capacity units requested. * * @return capacity units requested. */ public int getRequestedCapacityUnits() { return requestedCapacityUnits; } /** * set capacity units requested. * * @param requestedCapacityUnits * capacity units requested. */ public void setRequestedCapacityUnits(int requestedCapacityUnits) { this.requestedCapacityUnits = requestedCapacityUnits; } /** * return time units requested * * @return the time units requested */ public int getRequestedTimeUnits() { return requestedTimeUnits; } /** * set time units requested * * @param requestedTimeUnits * time units requested */ public void setRequestedTimeUnits(int requestedTimeUnits) { this.requestedTimeUnits = requestedTimeUnits; } /** * return true, if it is a valid request * * @return true, if it is a valid request */ public boolean isValidRequest() { return this.hasValidTimeUnits() && this.hasValidCapacityUnits(); } /** * return true, if the requested time units are allowed to book. this does * not check the real existing time units, that might be smaller because of * bookings or crashes. * * @return true, if the requested time units are allowed to book. */ public boolean hasValidTimeUnits() { assert this.getUser() != null; return this.getRequestedTimeUnits() >= this.bookingRule.getMinBookableTimeUnits(this.getUser()) && this.getRequestedTimeUnits() <= this.bookingRule.getMaxBookableTimeUnits(this.getUser()); } /** * return start of time requested. * * @return the start of time requested */ public Calendar getRequestedStartTime() { return requestedStartTime; } /** * start of time requested. * * @param requestedStartTime * the start of time requested. */ public void setRequestedStartTime(Calendar requestedStartTime) { requestedStartTime.set(Calendar.MILLISECOND, 0); requestedStartTime.set(Calendar.SECOND, 0); this.requestedStartTime = requestedStartTime; } /** * return true, if the requested capacity units is allowed to book in * general. this does not check the current capacity units, that might differ * because of bookings or crashes. * * @return true, if the requested capacity units is allowed to book. */ public boolean hasValidCapacityUnits() { assert this.getUser() != null; return this.getRequestedCapacityUnits() >= this.bookingRule.getMinBookableCapacityUnits(this.getUser()) && this.getRequestedCapacityUnits() <= this.bookingRule.getMaxBookableCapacityUnits(this.getUser()); } /** * return true, if the request is for today (servertime) * * @return true, if the request is for today (servertime) */ public boolean isRequest42day() { return Calendar.getInstance().get(Calendar.DAY_OF_YEAR) == this.getRequestedStartTime().get(Calendar.DAY_OF_YEAR); } /** {@inheritDoc} */ @Override public String toString() { Properties p = new Properties(); p.put("start", this.getRequestedStartTime().getTime().toString()); p.put("time units", this.getRequestedTimeUnits()); p.put("cap units", this.getRequestedCapacityUnits()); p.put("user", this.getUser().getUsername()); p.put("booking rule", this.getBookingRule().toString()); return p.toString(); } /** * return true, if it is a request for yesterdays but not today! * * @return true, if it is a request for yesterdays but not today! */ public boolean isRequest4Yesterdays() { return this.getRequestedStartTime().before(Calendar.getInstance()) && !this.isRequest42day(); } /** * return the article number as it were an booking * * @return the article number as it were an booking */ @Override public String getArticleNumber() { return this.getBooking().getArticleNumber(); } private TimeBooking getBooking() { return TimeBooking.getNewBooking(this); } @Override public boolean purchase() { return this.getBooking().purchase(); } }