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 months. 034 */ 035 036public class MonthlyFrequency extends Frequency { 037 038 /** The minute of the hour the event should happen at. */ 039 private int minute; 040 /** The hour of the day the event should happen at. */ 041 private int hour; 042 /** The day of the month the event should happen at. */ 043 private int dayOfMonth; 044 045 /** 046 * Create a new monthly frequency that happens every numUnits month, anytime. 047 * 048 * @param numUnits Number of days from event to event. 049 * @throws ArgumentNotValid if numUnits if 0 or negative 050 */ 051 public MonthlyFrequency(int numUnits) { 052 super(numUnits, true); 053 } 054 055 /** 056 * Create a new monthly frequency that happens every numUnits month, on the given day of month, hour and minute. 057 * 058 * @param numUnits Number of days from event to event. 059 * @param dayOfMonth The day of the month the event should happen. The month starts on day 1. 060 * @param hour The hour on which the event should happen. 061 * @param minute The minute of hour on which the event should happen. 062 * @throws ArgumentNotValid if numUnits if 0 or negative or dayOfMonth <1 or >31 or hour is <0 or >23 or minutes is 063 * <0 or >59 064 */ 065 public MonthlyFrequency(int numUnits, int dayOfMonth, int hour, int minute) { 066 super(numUnits, false); 067 068 Calendar cal = GregorianCalendar.getInstance(); 069 if (dayOfMonth < cal.getMinimum(Calendar.DAY_OF_MONTH) || dayOfMonth > cal.getMaximum(Calendar.DAY_OF_MONTH)) { 070 throw new ArgumentNotValid("Day of month must be in legal range '" + cal.getMinimum(Calendar.DAY_OF_MONTH) 071 + "' to '" + cal.getMaximum(Calendar.DAY_OF_MONTH) + "'"); 072 } 073 if (hour < cal.getMinimum(Calendar.HOUR_OF_DAY) || hour > cal.getMaximum(Calendar.HOUR_OF_DAY)) { 074 throw new ArgumentNotValid("Hour of day must be in legal range '" + cal.getMinimum(Calendar.HOUR_OF_DAY) 075 + "' to '" + cal.getMaximum(Calendar.HOUR_OF_DAY) + "'"); 076 } 077 if (minute < cal.getMinimum(Calendar.MINUTE) || minute > cal.getMaximum(Calendar.MINUTE)) { 078 throw new ArgumentNotValid("Minute must be in legal range '" + cal.getMinimum(Calendar.MINUTE) + "' to '" 079 + cal.getMaximum(Calendar.MINUTE) + "'"); 080 } 081 this.dayOfMonth = dayOfMonth; 082 this.hour = hour; 083 this.minute = minute; 084 } 085 086 /** 087 * Given when the last event happened, tell us when the next event should happen (even if the new event is in the 088 * past). 089 * <p> 090 * The time of the next event is guaranteed to be later that lastEvent. For certain frequencies (e.g. once a day, 091 * 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 092 * time of the next event is the first matching time after lastEvent. 093 * 094 * @param lastEvent A time from which the next event should be calculated. 095 * @return At what point the event should happen next. 096 */ 097 public Date getNextEvent(Date lastEvent) { 098 ArgumentNotValid.checkNotNull(lastEvent, "lastEvent"); 099 100 Calendar last = new GregorianCalendar(); 101 last.setTime(getFirstEvent(lastEvent)); 102 // Note: If the dayOfMonth becomes impossible by this addition, it is 103 // set back to the maximum possible date for this month 104 last.add(Calendar.MONTH, getNumUnits()); 105 return getFirstEvent(last.getTime()); 106 } 107 108 /** 109 * Given a starting time, tell us when the first event should happen. 110 * 111 * @param startTime The earliest time the event can happen. 112 * @return At what point the event should happen the first time. 113 */ 114 public Date getFirstEvent(Date startTime) { 115 ArgumentNotValid.checkNotNull(startTime, "startTime"); 116 117 if (isAnytime()) { 118 return startTime; 119 } 120 Calendar start = new GregorianCalendar(); 121 start.setTime(startTime); 122 start.set(Calendar.MINUTE, minute); 123 start.set(Calendar.HOUR_OF_DAY, hour); 124 // set day in month, to the given value if possible, or maximum 125 start.set(Calendar.DAY_OF_MONTH, Math.min(start.getActualMaximum(Calendar.DAY_OF_MONTH), dayOfMonth)); 126 if (start.getTime().before(startTime)) { 127 start.add(Calendar.MONTH, 1); 128 } 129 // reset day in month, the last day might be later in this month 130 start.set(Calendar.DAY_OF_MONTH, Math.min(start.getActualMaximum(Calendar.DAY_OF_MONTH), dayOfMonth)); 131 return start.getTime(); 132 133 } 134 135 /** 136 * If not anytime, the minute at which events should start. 137 * 138 * @return the minute 139 */ 140 public int getMinute() { 141 return minute; 142 } 143 144 /** 145 * If not anytime, the hour at which events should start. 146 * 147 * @return the hour 148 */ 149 public int getHour() { 150 return hour; 151 } 152 153 /** 154 * If not anytime, the day in the month at which events should start. 155 * 156 * @return the day 157 */ 158 public int getDayOfMonth() { 159 return dayOfMonth; 160 } 161 162 /** 163 * Autogenerated equals. 164 * 165 * @param o The object to compare with 166 * @return Whether objects are equal 167 */ 168 public boolean equals(Object o) { 169 if (this == o) { 170 return true; 171 } 172 if (!(o instanceof MonthlyFrequency)) { 173 return false; 174 } 175 if (!super.equals(o)) { 176 return false; 177 } 178 179 final MonthlyFrequency monthlyFrequency = (MonthlyFrequency) o; 180 181 if (isAnytime()) { 182 return true; 183 } 184 185 if (dayOfMonth != monthlyFrequency.dayOfMonth) { 186 return false; 187 } 188 if (hour != monthlyFrequency.hour) { 189 return false; 190 } 191 if (minute != monthlyFrequency.minute) { 192 return false; 193 } 194 195 return true; 196 } 197 198 /** 199 * Autogenerated hashcode method. 200 * 201 * @return the hashcode 202 */ 203 public int hashCode() { 204 int result = super.hashCode(); 205 result = 29 * result + minute; 206 result = 29 * result + hour; 207 result = 29 * result + dayOfMonth; 208 return result; 209 } 210 211 /** 212 * Return the exact minute event should happen on, or null if this is an anyTime event or doesn't define what minute 213 * it should happen on. 214 * 215 * @return the exact minute event should happen on 216 */ 217 public Integer getOnMinute() { 218 if (!isAnytime()) { 219 return minute; 220 } 221 return null; 222 } 223 224 /** 225 * Return the exact hour event should happen on, or null if this is an anyTime event or doesn't define what hour it 226 * should happen on. 227 * 228 * @return the exact hour event should happen on 229 */ 230 public Integer getOnHour() { 231 if (!isAnytime()) { 232 return hour; 233 } 234 return null; 235 } 236 237 /** 238 * Return the exact day of week event should happen on, or null if this is an anyTime event or doesn't define what 239 * day of week it should happen on. 240 * 241 * @return the exact day of week event should happen on 242 */ 243 public Integer getOnDayOfWeek() { 244 return null; 245 } 246 247 /** 248 * Return the exact day of month event should happen on, or null if this is an anyTime event or doesn't define what 249 * day of month it should happen on. 250 * 251 * @return the exact day of month event should happen on 252 */ 253 public Integer getOnDayOfMonth() { 254 if (!isAnytime()) { 255 return dayOfMonth; 256 } 257 return null; 258 } 259 260 /** 261 * Return an integer that can be used to identify the kind of frequency. No two subclasses should use the same 262 * integer 263 * 264 * @return an integer that can be used to identify the kind of frequency 265 */ 266 public int ordinal() { 267 return TimeUnit.MONTHLY.ordinal(); 268 } 269 270 /** 271 * Human readable representation of this object. 272 * 273 * @return Human readable representation 274 */ 275 public String toString() { 276 if (isAnytime()) { 277 return "every " + getNumUnits() + " months"; 278 } 279 return "every " + getNumUnits() + " months, on day " + dayOfMonth + " at " + hour + ":" + minute; 280 } 281 282}