001/*
002 * #%L
003 * Netarchivesuite - harvester
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.harvester.datamodel;
025
026import java.util.Calendar;
027import java.util.Date;
028import java.util.GregorianCalendar;
029
030import dk.netarkivet.common.exceptions.ArgumentNotValid;
031
032/**
033 * This class implements a frequency of a number of days.
034 */
035public class DailyFrequency extends Frequency {
036
037    /** The minute of the hour the event should happen at. */
038    private int minute;
039    /** The hour of the day the event should happen at. */
040    private int hour;
041
042    /**
043     * Create a new daily frequency that happens every numUnits days, anytime.
044     *
045     * @param numUnits Number of days from event to event.
046     * @throws ArgumentNotValid if numUnits if 0 or negative
047     */
048    public DailyFrequency(int numUnits) {
049        super(numUnits, true);
050    }
051
052    /**
053     * Create a new daily frequency that happens every numUnits days, on the given hour and minute.
054     *
055     * @param numUnits Number of days from event to event.
056     * @param hour The hour on which the event should happen.
057     * @param minute The minute of hour on which the event should happen.
058     * @throws ArgumentNotValid if numUnits if 0 or negative or hours is <0 or >23 or minutes is <0 or >59
059     */
060    public DailyFrequency(int numUnits, int hour, int minute) {
061        super(numUnits, false);
062        Calendar cal = GregorianCalendar.getInstance();
063        if (hour < cal.getMinimum(Calendar.HOUR_OF_DAY) || hour > cal.getMaximum(Calendar.HOUR_OF_DAY)) {
064            throw new ArgumentNotValid("Hour of day must be in legal range '" + cal.getMinimum(Calendar.HOUR_OF_DAY)
065                    + "' to '" + cal.getMaximum(Calendar.HOUR_OF_DAY) + "'");
066        }
067        if (minute < cal.getMinimum(Calendar.MINUTE) || minute > cal.getMaximum(Calendar.MINUTE)) {
068            throw new ArgumentNotValid("Minute must be in legal range '" + cal.getMinimum(Calendar.MINUTE) + "' to '"
069                    + cal.getMaximum(Calendar.MINUTE) + "'");
070        }
071
072        this.minute = minute;
073        this.hour = hour;
074    }
075
076    /**
077     * Given when the last event happened, tell us when the next event should happen (even if the new event is in the
078     * past).
079     * <p>
080     * The time of the next event is guaranteed to be later that lastEvent. For certain frequencies (e.g. once a day,
081     * any time of day), the time of the next event is derived from lastEvent, for others (e.g. once a day at 13:00) the
082     * time of the next event is the first matching time after lastEvent.
083     *
084     * @param lastEvent A time from which the next event should be calculated.
085     * @return At what point the event should happen next.
086     */
087    public Date getNextEvent(Date lastEvent) {
088        ArgumentNotValid.checkNotNull(lastEvent, "lastEvent");
089
090        Calendar last = new GregorianCalendar();
091        last.setTime(getFirstEvent(lastEvent));
092        last.add(Calendar.DAY_OF_YEAR, getNumUnits());
093        return getFirstEvent(last.getTime());
094    }
095
096    /**
097     * Given a starting time, tell us when the first event should happen.
098     *
099     * @param startTime The earliest time the event can happen.
100     * @return At what point the event should happen the first time.
101     */
102    public Date getFirstEvent(Date startTime) {
103        ArgumentNotValid.checkNotNull(startTime, "startTime");
104
105        if (isAnytime()) {
106            return startTime;
107        }
108        Calendar start = new GregorianCalendar();
109        start.setTime(startTime);
110        start.set(Calendar.MINUTE, minute);
111        start.set(Calendar.HOUR_OF_DAY, hour);
112        if (start.getTime().before(startTime)) {
113            start.add(Calendar.DAY_OF_YEAR, 1);
114        }
115        return start.getTime();
116    }
117
118    /**
119     * If not anytime, the minute at which events should start.
120     *
121     * @return the minute
122     */
123    public int getMinute() {
124        return minute;
125    }
126
127    /**
128     * If not anytime, the hour at which events should start.
129     *
130     * @return the hour
131     */
132    public int getHour() {
133        return hour;
134    }
135
136    /**
137     * Autogenerated equals.
138     *
139     * @param o The object to compare with
140     * @return Whether objects are equal
141     */
142    public boolean equals(Object o) {
143        if (this == o) {
144            return true;
145        }
146        if (!(o instanceof DailyFrequency)) {
147            return false;
148        }
149        if (!super.equals(o)) {
150            return false;
151        }
152
153        final DailyFrequency dailyFrequency = (DailyFrequency) o;
154
155        if (isAnytime()) {
156            return true;
157        }
158
159        if (hour != dailyFrequency.hour) {
160            return false;
161        }
162        if (minute != dailyFrequency.minute) {
163            return false;
164        }
165
166        return true;
167    }
168
169    /**
170     * Autogenerated hashcode method.
171     *
172     * @return the hashcode
173     */
174    public int hashCode() {
175        int result = super.hashCode();
176        result = 29 * result + minute;
177        result = 29 * result + hour;
178        return result;
179    }
180
181    /**
182     * Return the exact minute event should happen on, or null if this is an anyTime event or doesn't define what minute
183     * it should happen on.
184     *
185     * @return the exact minute event should happen on, or null if this is an anyTime event or doesn't define what
186     * minute it should happen on
187     */
188    public Integer getOnMinute() {
189        if (!isAnytime()) {
190            return minute;
191        }
192        return null;
193    }
194
195    /**
196     * Return the exact hour event should happen on, or null if this is an anyTime event or doesn't define what hour it
197     * should happen on.
198     *
199     * @return the exact hour event should happen on, or null if this is an anyTime event or doesn't define what hour it
200     * should happen on
201     */
202    public Integer getOnHour() {
203        if (!isAnytime()) {
204            return hour;
205        }
206        return null;
207    }
208
209    /**
210     * Return the exact day of week event should happen on, or null if this is an anyTime event or doesn't define what
211     * day of week it should happen on.
212     *
213     * @return the exact day of week event should happen on, or null if this is an anyTime event or doesn't define what
214     * day of week it should happen on
215     */
216    public Integer getOnDayOfWeek() {
217        return null;
218    }
219
220    /**
221     * Return the exact day of month event should happen on, or null if this is an anyTime event or doesn't define what
222     * day of month it should happen on.
223     *
224     * @return the exact day of month event should happen on, or null if this is an anyTime event or doesn't define what
225     * day of month it should happen on
226     */
227    public Integer getOnDayOfMonth() {
228        return null;
229    }
230
231    /**
232     * Return an integer that can be used to identify the kind of frequency. No two subclasses should use the same
233     * integer
234     *
235     * @return an integer that can be used to identify the kind of frequency
236     */
237    public int ordinal() {
238        return TimeUnit.DAILY.ordinal();
239    }
240
241    /**
242     * Human readable representation of this object.
243     *
244     * @return Human readable representation
245     */
246    public String toString() {
247        if (isAnytime()) {
248            return "every " + getNumUnits() + " days";
249        }
250        return "every " + getNumUnits() + " days at " + hour + ":" + minute;
251    }
252
253}