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 hours.
034 */
035public class HourlyFrequency extends Frequency {
036
037    /** The minute of the hour the event should happen at. */
038    private int minute;
039
040    /**
041     * Create a new HourlyFrequency that happens every numUnits hours, at an unspecified minute.
042     *
043     * @param numUnits Number of hours from one event to the next
044     * @throws ArgumentNotValid if numUnits if 0 or negative
045     */
046    public HourlyFrequency(int numUnits) {
047        super(numUnits, true);
048    }
049
050    /**
051     * Create a new HourlyFrequency that happens every numUnits hours, on the minute.
052     *
053     * @param numUnits Number of hours from one event to the next
054     * @param minute What minute of the hour the event should happen on.
055     * @throws ArgumentNotValid if numUnits if 0 or negative or minutes is <0 or >59
056     */
057    public HourlyFrequency(int numUnits, int minute) {
058        super(numUnits, false);
059        Calendar cal = GregorianCalendar.getInstance();
060        if (minute < cal.getMinimum(Calendar.MINUTE) || minute > cal.getMaximum(Calendar.MINUTE)) {
061            throw new ArgumentNotValid("Minute must be in legal range '" + cal.getMinimum(Calendar.MINUTE) + "' to '"
062                    + cal.getMaximum(Calendar.MINUTE) + "'");
063        }
064
065        this.minute = minute;
066    }
067
068    /**
069     * Given when the last event happened, tell us when the next event should happen (even if the new event is in the
070     * past).
071     * <p>
072     * The time of the next event is guaranteed to be later that lastEvent. For certain frequencies (e.g. once a day,
073     * 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
074     * time of the next event is the first matching time after lastEvent.
075     *
076     * @param lastEvent A time from which the next event should be calculated.
077     * @return At what point the event should happen next.
078     * @throws ArgumentNotValid if lastEvent is null
079     */
080    public Date getNextEvent(Date lastEvent) {
081        ArgumentNotValid.checkNotNull(lastEvent, "lastEvent");
082
083        Calendar last = new GregorianCalendar();
084        last.setTime(getFirstEvent(lastEvent));
085        last.add(Calendar.HOUR_OF_DAY, getNumUnits());
086        return getFirstEvent(last.getTime());
087    }
088
089    /**
090     * Given a starting time, tell us when the first event should happen.
091     *
092     * @param startTime The earliest time the event can happen.
093     * @return At what point the event should happen the first time.
094     * @throws ArgumentNotValid if startTime is null
095     */
096    public Date getFirstEvent(Date startTime) {
097        ArgumentNotValid.checkNotNull(startTime, "startTime");
098
099        if (isAnytime()) {
100            return startTime;
101        }
102        Calendar start = new GregorianCalendar();
103        start.setTime(startTime);
104        start.set(Calendar.MINUTE, minute);
105        if (start.getTime().before(startTime)) {
106            start.add(Calendar.HOUR_OF_DAY, 1);
107        }
108        return start.getTime();
109
110    }
111
112    /**
113     * If not anytime, the minute at which events should start.
114     *
115     * @return the minute
116     */
117    public int getMinute() {
118        return minute;
119    }
120
121    /**
122     * Autogenerated equals.
123     *
124     * @param o The object to compare with
125     * @return Whether objects are equal
126     */
127    public boolean equals(Object o) {
128        if (this == o) {
129            return true;
130        }
131        if (!(o instanceof HourlyFrequency)) {
132            return false;
133        }
134        if (!super.equals(o)) {
135            return false;
136        }
137
138        final HourlyFrequency hourlyFrequency = (HourlyFrequency) o;
139
140        if (isAnytime()) {
141            return true;
142        }
143
144        if (minute != hourlyFrequency.minute) {
145            return false;
146        }
147
148        return true;
149    }
150
151    /**
152     * Autogenerated hashcode method.
153     *
154     * @return the hashcode
155     */
156    public int hashCode() {
157        int result = super.hashCode();
158        result = 29 * result + minute;
159        return result;
160    }
161
162    /**
163     * Return the exact minute event should happen on, or null if this is an anyTime event or doesn't define what minute
164     * it should happen on.
165     *
166     * @return the exact minute event should happen on
167     */
168    public Integer getOnMinute() {
169        if (!isAnytime()) {
170            return minute;
171        }
172        return null;
173    }
174
175    /**
176     * Return the exact hour event should happen on, or null if this is an anyTime event or doesn't define what hour it
177     * should happen on.
178     *
179     * @return null always
180     */
181    public Integer getOnHour() {
182        return null;
183    }
184
185    /**
186     * Return the exact day of week event should happen on, or null if this is an anyTime event or doesn't define what
187     * day of week it should happen on.
188     *
189     * @return null always
190     */
191    public Integer getOnDayOfWeek() {
192        return null;
193    }
194
195    /**
196     * Return the exact day of month event should happen on, or null if this is an anyTime event or doesn't define what
197     * day of month it should happen on.
198     *
199     * @return null (always)
200     */
201    public Integer getOnDayOfMonth() {
202        return null;
203    }
204
205    /**
206     * Return an integer that can be used to identify the kind of frequency. No two subclasses should use the same
207     * integer
208     *
209     * @return an integer that can be used to identify the kind of frequency
210     */
211    public int ordinal() {
212        return TimeUnit.HOURLY.ordinal();
213    }
214
215    /**
216     * Human readable representation of this object.
217     *
218     * @return Human readable representation
219     */
220    public String toString() {
221        if (isAnytime()) {
222            return "every " + getNumUnits() + " hours";
223        }
224        return "every " + getNumUnits() + " hours at " + minute;
225    }
226
227}