001/*
002 * #%L
003 * Netarchivesuite - harvester
004 * %%
005 * Copyright (C) 2005 - 2018 The Royal Danish 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.Date;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import dk.netarkivet.common.exceptions.ArgumentNotValid;
032import dk.netarkivet.common.exceptions.NotImplementedException;
033
034/**
035 * This class defines various frequencies at which things can happen, such as midnight every day, 13:45 the first monday
036 * of a month, etc.
037 */
038public abstract class Frequency {
039
040    private static final Logger log = LoggerFactory.getLogger(Frequency.class);
041
042    /** How many units of time between each event? */
043    private int numUnits;
044
045    /** If this Frequency happens any time rather than at a specified time. */
046    private boolean isAnytime;
047
048    /**
049     * Initialise a frequency with information about how many periods between events, and whether it's at a specified
050     * time in the period.
051     * <p>
052     * The actual length of the period is defined by subclasses
053     *
054     * @param numUnits Number of periods between events
055     * @param isAnytime Whether it's at a specified time in the period
056     * @throws ArgumentNotValid if numUnits if 0 or negative
057     */
058    public Frequency(int numUnits, boolean isAnytime) {
059        ArgumentNotValid.checkPositive(numUnits, "numUnits");
060
061        this.numUnits = numUnits;
062        this.isAnytime = isAnytime;
063    }
064
065    /**
066     * Given when the last event happened, tell us when the next event should happen (even if the new event is in the
067     * past).
068     * <p>
069     * The time of the next event is guaranteed to be later that lastEvent. For certain frequencies (e.g. once a day,
070     * 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
071     * time of the next event is the first matching time after lastEvent.
072     * <p>
073     * These methods are used by the schedule methods for calculating when events should happen.
074     *
075     * @param lastEvent A time from which the next event should be calculated.
076     * @return At what point the event should happen next.
077     */
078    public abstract Date getNextEvent(Date lastEvent);
079
080    /**
081     * Given a starting time, tell us when the first event should happen.
082     * <p>
083     * This method is used by the schedule methods for calculating when events should happen.
084     *
085     * @param startTime The earliest time the event can happen.
086     * @return At what point the event should happen the first time.
087     */
088    public abstract Date getFirstEvent(Date startTime);
089
090    /**
091     * Returns the number of periods between events.
092     *
093     * @return that number
094     */
095    public int getNumUnits() {
096        return numUnits;
097    }
098
099    /**
100     * Returns whether this frequency allows events to happen any time of day, rather than at a specific time.
101     *
102     * @return true if the events may happen at any time.
103     */
104    public boolean isAnytime() {
105        return isAnytime;
106    }
107
108    /**
109     * Autogenerated equals.
110     *
111     * @param o The object to compare with
112     * @return Whether objects are equal
113     */
114    public boolean equals(Object o) {
115        if (this == o) {
116            return true;
117        }
118        if (!(o instanceof Frequency)) {
119            return false;
120        }
121
122        final Frequency frequency = (Frequency) o;
123
124        if (isAnytime != frequency.isAnytime) {
125            return false;
126        }
127        if (numUnits != frequency.numUnits) {
128            return false;
129        }
130
131        return true;
132    }
133
134    /**
135     * Autogenerated hashcode method.
136     *
137     * @return the hashcode
138     */
139    public int hashCode() {
140        int result;
141        result = numUnits;
142        result = 29 * result + (isAnytime ? 1 : 0);
143        return result;
144    }
145
146    /**
147     * Return the exact minute event should happen on, or null if this is an anyTime event or doesn't define what minute
148     * it should happen on.
149     *
150     * @return the exact minute event should happen on
151     */
152    public abstract Integer getOnMinute();
153
154    /**
155     * Return the exact hour event should happen on, or null if this is an anyTime event or doesn't define what hour it
156     * should happen on.
157     *
158     * @return the exact hour event should happen on
159     */
160    public abstract Integer getOnHour();
161
162    /**
163     * Return the exact day of week event should happen on, or null if this is an anyTime event or doesn't define what
164     * day of week it should happen on.
165     *
166     * @return the exact day of week event should happen on
167     */
168    public abstract Integer getOnDayOfWeek();
169
170    /**
171     * Return the exact day of month event should happen on, or null if this is an anyTime event or doesn't define what
172     * day of month it should happen on.
173     *
174     * @return the exact day of month event should happen on
175     */
176    public abstract Integer getOnDayOfMonth();
177
178    /**
179     * Return an integer that can be used to identify the kind of frequency. No two subclasses should use the same
180     * integer
181     *
182     * @return Return an integer that can be used to identify the kind of frequency
183     */
184    public abstract int ordinal();
185
186    /**
187     * Get a new instance of Frequency. The type of Frequency (either Hourly, Daily, Monthly, or Weekly) returned
188     * depends on the 'timeunit' argument.
189     *
190     * @param timeunit The type or frequency
191     * @param anytime Allow events to start anytime. If false, the starting point is described precisely. If true, the
192     * starting point will be immediately.
193     * @param numtimeunits The number of periods between events
194     * @param minute A given minute. Used to create hourly, daily, and monthly frequencies, if anytime is false.
195     * @param hour A given hour. Used to create hourly, daily, and monthly frequencies, if anytime is false.
196     * @param dayofweek A given day of the week. Used only to create weekly frequencies, if anytime is false.
197     * @param dayofmonth A given day of month. Used only to create monthly frequencies, if anytime is false.
198     * @return a new instance of the Frequency class.
199     * @throws ArgumentNotValid If the given timeunit is illegal, or the values of timeunit and numtimeunits is
200     * negative.
201     * @throws NotImplementedException If we can't yet make a Frequency for a legal timeunit.
202     */
203    public static Frequency getNewInstance(int timeunit, boolean anytime, int numtimeunits, Integer minute,
204            Integer hour, Integer dayofweek, Integer dayofmonth) {
205        ArgumentNotValid.checkPositive(timeunit, "int timeunit");
206        ArgumentNotValid.checkPositive(numtimeunits, "int timeunits");
207
208        Frequency freq;
209        TimeUnit tu = TimeUnit.fromOrdinal(timeunit);
210        log.debug("Creating a " + tu.name() + " frequency.");
211        if (!anytime) {
212            ArgumentNotValid.checkTrue(minute != null, "Arg. minute should not be null, if anytime is false");
213            ArgumentNotValid
214                    .checkTrue(hour != null || tu.equals(TimeUnit.HOURLY),
215                            "Arg. hour should not be null, if anytime is false unless"
216                                    + " we are creating a Hourly frequency.");
217            ArgumentNotValid.checkTrue(dayofweek != null || !tu.equals(TimeUnit.WEEKLY),
218                    "Arg. dayofweek should not be null, if anytime is false "
219                            + " and we are creating a Weekly frequency.");
220            ArgumentNotValid.checkTrue(dayofmonth != null || !tu.equals(TimeUnit.MONTHLY),
221                    "Arg. dayofmonth should not be null, if anytime is false "
222                            + "and we are creating a Monthly frequency.");
223        }
224
225        switch (tu) {
226        case HOURLY:
227            if (anytime) {
228                freq = new HourlyFrequency(numtimeunits);
229            } else {
230                freq = new HourlyFrequency(numtimeunits, minute);
231            }
232            break;
233        case DAILY:
234            if (anytime) {
235                freq = new DailyFrequency(numtimeunits);
236            } else {
237                freq = new DailyFrequency(numtimeunits, hour, minute);
238            }
239            break;
240        case WEEKLY:
241            if (anytime) {
242                freq = new WeeklyFrequency(numtimeunits);
243            } else {
244                freq = new WeeklyFrequency(numtimeunits, dayofweek, hour, minute);
245            }
246            break;
247        case MONTHLY:
248            if (anytime) {
249                freq = new MonthlyFrequency(numtimeunits);
250            } else {
251                freq = new MonthlyFrequency(numtimeunits, dayofmonth, hour, minute);
252            }
253            break;
254        case MINUTE: // Minute frequencies are always "anytime"
255            freq = new MinuteFrequency(numtimeunits);
256            break;
257        default:
258            throw new NotImplementedException("We don't know how to make a Frequency for timeunit " + timeunit);
259        }
260        return freq;
261    }
262
263}