File D:\maven-qcr-plugin-testsunitaires\qcr4java\jenkins-master\target\site\qc\core\src\main\java\hudson\scheduler\CronTab.java.html

1 /*
2 * The MIT License
3 *
4 * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, InfraDNA, Inc.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24 package hudson.scheduler;
25
26 import antlr.ANTLRException;
27
28 import java.io.StringReader;
29 import java.util.Calendar;
30 import java.util.GregorianCalendar;
31 import java.util.Locale;
32
33 import static java.util.Calendar.*;
34
35 /**
36 * Table for driving scheduled tasks.
37 *
38 * @author Kohsuke Kawaguchi
39 */
40 public final class CronTab {
41 /**
42 * bits[0]: minutes
43 * bits[1]: hours
44 * bits[2]: days
45 * bits[3]: months
46 *
47 * false:not scheduled &lt;-> true scheduled
48 */
49 final long[] bits = new long[4];
50
51 int dayOfWeek;
52
53 /**
54 * Textual representation.
55 */
56 private String spec;
57
58 public CronTab(String format) throws ANTLRException {
59 this(format,null);
60 }
61
62 public CronTab(String format, Hash hash) throws ANTLRException {
63 this(format,1,hash);
64 }
65
66 /**
67 * @deprecated as of 1.448
68 * Use {@link #CronTab(String, int, Hash)}
69 */
70 public CronTab(String format, int line) throws ANTLRException {
71 set(format, line, null);
72 }
73
74 /**
75 * @param hash
76 * Used to spread out token like "@daily". Null to preserve the legacy behaviour
77 * of not spreading it out at all.
78 */
79 public CronTab(String format, int line, Hash hash) throws ANTLRException {
80 set(format, line, hash);
81 }
82
83 private void set(String format, int line, Hash hash) throws ANTLRException {
84 CrontabLexer lexer = new CrontabLexer(new StringReader(format));
85 lexer.setLine(line);
86 CrontabParser parser = new CrontabParser(lexer);
87 parser.setHash(hash);
88 spec = format;
89
90 parser.startRule(this);
91 if((dayOfWeek&(1<<7))!=0) {
92 dayOfWeek |= 1; // copy bit 7 over to bit 0
93 dayOfWeek &= ~(1<<7); // clear bit 7 or CalendarField#ceil will return an invalid value 7
94 }
95 }
96
97
98 /**
99 * Returns true if the given calendar matches
100 */
101 boolean check(Calendar cal) {
102 if(!checkBits(bits[0],cal.get(MINUTE)))
103 return false;
104 if(!checkBits(bits[1],cal.get(HOUR_OF_DAY)))
105 return false;
106 if(!checkBits(bits[2],cal.get(DAY_OF_MONTH)))
107 return false;
108 if(!checkBits(bits[3],cal.get(MONTH)+1))
109 return false;
110 if(!checkBits(dayOfWeek,cal.get(Calendar.DAY_OF_WEEK)-1))
111 return false;
112
113 return true;
114 }
115
116 private static abstract class CalendarField {
117 /**
118 * {@link Calendar} field ID.
119 */
120 final int field;
121 /**
122 * Lower field is a calendar field whose value needs to be reset when we change the value in this field.
123 * For example, if we modify the value in HOUR, MINUTES must be reset.
124 */
125 final CalendarField lowerField;
126 /**
127 * Whether this field is 0-origin or 1-origin differs between Crontab and {@link Calendar},
128 * so this field adjusts that. If crontab is 1 origin and calendar is 0 origin, this field is 1
129 * that is the value is {@code (cronOrigin-calendarOrigin)}
130 */
131 final int offset;
132 /**
133 * When we reset this field, we set the field to this value.
134 * For example, resetting {@link Calendar#DAY_OF_MONTH} means setting it to 1.
135 */
136 final int min;
137 /**
138 * If this calendar field has other aliases such that a change in this field
139 * modifies other field values, then true.
140 */
141 final boolean redoAdjustmentIfModified;
142
143 /**
144 * What is this field? Useful for debugging
145 */
146 @SuppressWarnings("unused")
147 private final String displayName;
148
149 private CalendarField(String displayName, int field, int min, int offset, boolean redoAdjustmentIfModified, CalendarField lowerField) {
150 this.displayName = displayName;
151 this.field = field;
152 this.min = min;
153 this.redoAdjustmentIfModified= redoAdjustmentIfModified;
154 this.lowerField = lowerField;
155 this.offset = offset;
156 }
157
158 /**
159 * Gets the current value of this field in the given calendar.
160 */
161 int valueOf(Calendar c) {
162 return c.get(field)+offset;
163 }
164
165 void addTo(Calendar c, int i) {
166 c.add(field,i);
167 }
168
169 void setTo(Calendar c, int i) {
170 c.set(field,i-offset);
171 }
172
173 void clear(Calendar c) {
174 setTo(c, min);
175 }
176
177 /**
178 * Given the value 'n' (which represents the current value), finds the smallest x such that:
179 * 1) x matches the specified {@link CronTab} (as far as this field is concerned.)
180 * 2) x>=n (inclusive)
181 *
182 * If there's no such bit, return -1. Note that if 'n' already matches the crontab, the same n will be returned.
183 */
184 private int ceil(CronTab c, int n) {
185 long bits = bits(c);
186 while ((bits|(1L<<n))!=bits) {
187 if (n>60) return -1;
188 n++;
189 }
190 return n;
191 }
192
193 /**
194 * Given a bit mask, finds the first bit that's on, and return its index.
195 */
196 private int first(CronTab c) {
197 return ceil(c,0);
198 }
199
200 private int floor(CronTab c, int n) {
201 long bits = bits(c);
202 while ((bits|(1L<<n))!=bits) {
203 if (n==0) return -1;
204 n--;
205 }
206 return n;
207 }
208
209 private int last(CronTab c) {
210 return floor(c,63);
211 }
212
213 /**
214 * Extracts the bit masks from the given {@link CronTab} that matches this field.
215 */
216 abstract long bits(CronTab c);
217
218 /**
219 * Increment the next field.
220 */
221 abstract void rollUp(Calendar cal, int i);
222
223 private static final CalendarField MINUTE = new CalendarField("minute", Calendar.MINUTE, 0, 0, false, null) {
224 long bits(CronTab c) { return c.bits[0]; }
225 void rollUp(Calendar cal, int i) { cal.add(Calendar.HOUR_OF_DAY,i); }
226 };
227 private static final CalendarField HOUR = new CalendarField("hour", Calendar.HOUR_OF_DAY, 0, 0, false, MINUTE) {
228 long bits(CronTab c) { return c.bits[1]; }
229 void rollUp(Calendar cal, int i) { cal.add(Calendar.DAY_OF_MONTH,i); }
230 };
231 private static final CalendarField DAY_OF_MONTH = new CalendarField("day", Calendar.DAY_OF_MONTH, 1, 0, true, HOUR) {
232 long bits(CronTab c) { return c.bits[2]; }
233 void rollUp(Calendar cal, int i) { cal.add(Calendar.MONTH,i); }
234 };
235 private static final CalendarField MONTH = new CalendarField("month", Calendar.MONTH, 1, 1, false, DAY_OF_MONTH) {
236 long bits(CronTab c) { return c.bits[3]; }
237 void rollUp(Calendar cal, int i) { cal.add(Calendar.YEAR,i); }
238 };
239 private static final CalendarField DAY_OF_WEEK = new CalendarField("dow", Calendar.DAY_OF_WEEK, 1,-1, true, HOUR) {
240 long bits(CronTab c) { return c.dayOfWeek; }
241 void rollUp(Calendar cal, int i) {
242 cal.add(Calendar.DAY_OF_WEEK, 7 * i);
243 }
244
245 @Override
246 void setTo(Calendar c, int i) {
247 int v = i-offset;
248 int was = c.get(field);
249 c.set(field,v);
250 final int firstDayOfWeek = c.getFirstDayOfWeek();
251 if (v < firstDayOfWeek && was >= firstDayOfWeek) {
252 // in crontab, the first DoW is always Sunday, but in Java, it can be Monday or in theory arbitrary other days.
253 // When first DoW is 1/2 Monday, calendar points to 1/2 Monday, setting the DoW to Sunday makes
254 // the calendar moves forward to 1/8 Sunday, instead of 1/1 Sunday. So we need to compensate that effect here.
255 addTo(c,-7);
256 } else if (was < firstDayOfWeek && firstDayOfWeek <= v) {
257 // If we wrap the other way around, we need to adjust in the opposite direction of above.
258 addTo(c, 7);
259 }
260 }
261 };
262
263 private static final CalendarField[] ADJUST_ORDER = {
264 MONTH, DAY_OF_MONTH, DAY_OF_WEEK, HOUR, MINUTE
265 };
266 }
267
268
269 /**
270 * Computes the nearest future timestamp that matches this cron tab.
271 * <p>
272 * More precisely, given the time 't', computes another smallest time x such that:
273 *
274 * <ul>
275 * <li>x >= t (inclusive)
276 * <li>x matches this crontab
277 * </ul>
278 *
279 * <p>
280 * Note that if t already matches this cron, it's returned as is.
281 */
282 public Calendar ceil(long t) {
283 Calendar cal = new GregorianCalendar(Locale.US);
284 cal.setTimeInMillis(t);
285 return ceil(cal);
286 }
287
288 /**
289 * See {@link #ceil(long)}.
290 *
291 * This method modifies the given calendar and returns the same object.
292 */
293 public Calendar ceil(Calendar cal) {
294 OUTER:
295 while (true) {
296 for (CalendarField f : CalendarField.ADJUST_ORDER) {
297 int cur = f.valueOf(cal);
298 int next = f.ceil(this,cur);
299 if (cur==next) continue; // this field is already in a good shape. move on to next
300
301 // we are modifying this field, so clear all the lower level fields
302 for (CalendarField l=f.lowerField; l!=null; l=l.lowerField)
303 l.clear(cal);
304
305 if (next<0) {
306 // we need to roll over to the next field.
307 f.rollUp(cal, 1);
308 f.setTo(cal,f.first(this));
309 // since higher order field is affected by this, we need to restart from all over
310 continue OUTER;
311 } else {
312 f.setTo(cal,next);
313 if (f.redoAdjustmentIfModified)
314 continue OUTER; // when we modify DAY_OF_MONTH and DAY_OF_WEEK, do it all over from the top
315 }
316 }
317 return cal; // all fields adjusted
318 }
319 }
320
321 /**
322 * Computes the nearest past timestamp that matched this cron tab.
323 * <p>
324 * More precisely, given the time 't', computes another smallest time x such that:
325 *
326 * <ul>
327 * <li>x &lt;= t (inclusive)
328 * <li>x matches this crontab
329 * </ul>
330 *
331 * <p>
332 * Note that if t already matches this cron, it's returned as is.
333 */
334 public Calendar floor(long t) {
335 Calendar cal = new GregorianCalendar(Locale.US);
336 cal.setTimeInMillis(t);
337 return floor(cal);
338 }
339
340 /**
341 * See {@link #floor(long)}
342 *
343 * This method modifies the given calendar and returns the same object.
344 */
345 public Calendar floor(Calendar cal) {
346 OUTER:
347 while (true) {
348 for (CalendarField f : CalendarField.ADJUST_ORDER) {
349 int cur = f.valueOf(cal);
350 int next = f.floor(this,cur);
351 if (cur==next) continue; // this field is already in a good shape. move on to next
352
353 // we are modifying this field, so clear all the lower level fields
354 for (CalendarField l=f.lowerField; l!=null; l=l.lowerField)
355 l.clear(cal);
356
357 if (next<0) {
358 // we need to borrow from the next field.
359 f.rollUp(cal,-1);
360 // the problem here, in contrast with the ceil method, is that
361 // the maximum value of the field is not always a fixed value (that is, day of month)
362 // so we zero-clear all the lower fields, set the desired value +1,
363 f.setTo(cal,f.last(this));
364 f.addTo(cal,1);
365 // then subtract a minute to achieve maximum values on all the lower fields,
366 // with the desired value in 'f'
367 CalendarField.MINUTE.addTo(cal,-1);
368 // since higher order field is affected by this, we need to restart from all over
369 continue OUTER;
370 } else {
371 f.setTo(cal,next);
372 f.addTo(cal,1);
373 CalendarField.MINUTE.addTo(cal,-1);
374 if (f.redoAdjustmentIfModified)
375 continue OUTER; // when we modify DAY_OF_MONTH and DAY_OF_WEEK, do it all over from the top
376 }
377 }
378 return cal; // all fields adjusted
379 }
380 }
381
382 void set(String format, Hash hash) throws ANTLRException {
383 set(format,1,hash);
384 }
385
386 /**
387 * Returns true if n-th bit is on.
388 */
389 private boolean checkBits(long bitMask, int n) {
390 return (bitMask|(1L<<n))==bitMask;
391 }
392
393 public String toString() {
394 return super.toString()+"["+
395 toString("minute",bits[0])+','+
396 toString("hour",bits[1])+','+
397 toString("dayOfMonth",bits[2])+','+
398 toString("month",bits[3])+','+
399 toString("dayOfWeek",dayOfWeek)+']';
400 }
401
402 private String toString(String key, long bit) {
403 return key+'='+Long.toHexString(bit);
404 }
405
406 /**
407 * Checks if this crontab entry looks reasonable,
408 * and if not, return an warning message.
409 *
410 * <p>
411 * The point of this method is to catch syntactically correct
412 * but semantically suspicious combinations, like
413 * "* 0 * * *"
414 */
415 public String checkSanity() {
416 for( int i=0; i<5; i++ ) {
417 long bitMask = (i<4)?bits[i]:(long)dayOfWeek;
418 for( int j=BaseParser.LOWER_BOUNDS[i]; j<=BaseParser.UPPER_BOUNDS[i]; j++ ) {
419 if(!checkBits(bitMask,j)) {
420 // this rank has a sparse entry.
421 // if we have a sparse rank, one of them better be the left-most.
422 if(i>0)
423 return "Do you really mean \"every minute\" when you say \""+spec+"\"? "+
424 "Perhaps you meant \"0 "+spec.substring(spec.indexOf(' ')+1)+"\"";
425 // once we find a sparse rank, upper ranks don't matter
426 return null;
427 }
428 }
429 }
430
431 return null;
432 }
433 }
434