PageRenderTime 2006ms CodeModel.GetById 215ms app.highlight 639ms RepoModel.GetById 937ms app.codeStats 1ms

/pentaho-gwt-widgets/src/org/pentaho/gwt/widgets/client/utils/CronExpression.java

https://github.com/wgorman/pentaho-commons-gwt-modules
Java | 866 lines | 557 code | 97 blank | 212 comment | 310 complexity | bb794ae9763aeb89baa1da2b58c25993 MD5 | raw file
  1/*
  2 ***********************************************************************************************************************
  3 *
  4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  5 * the License. You may obtain a copy of the License at
  6 *
  7 *     http://www.apache.org/licenses/LICENSE-2.0
  8 *
  9 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 10 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 11 * specific language governing permissions and limitations under the License.
 12 *
 13 * This file is a copy from the Quartz project - http://quartz-scheduler.org/ from version 1.5.1 
 14 * with the following changes:
 15 *   a- Removed all non-GWT friendly classes (Locale, Calendar, StringTokenizer, ObjectOutputStream, etc.
 16 *   b- Re-implemented the buildExpression method using split instead of StringTokenizer
 17 *   c- Externalized all strings to the i18n package
 18 *   d- Code-formatted to meet Pentaho standards
 19 *   e- Removed extraneous helper methods not needed for cron validation
 20 *   
 21 * These changes were expressly made to allow GWT compilation (read Javascript translation) for validation of 
 22 * Quartz-specific cron expressions. All other comments and attributions remain intact.
 23 * 
 24 * Copyright 2012 Pentaho Corporation.  All rights reserved.
 25 * @author Marc Batchelor
 26 *
 27 ***********************************************************************************************************************
 28 */
 29package org.pentaho.gwt.widgets.client.utils;
 30
 31import java.util.HashMap;
 32import java.util.Map;
 33import java.util.TreeSet;
 34
 35import org.pentaho.gwt.widgets.client.i18n.WidgetsLocalizedMessages;
 36import org.pentaho.gwt.widgets.client.i18n.WidgetsLocalizedMessagesSingleton;
 37
 38/**
 39 * Provides a parser and evaluator for unix-like cron expressions. Cron 
 40 * expressions provide the ability to specify complex time combinations such as
 41 * "At 8:00am every Monday through Friday" or "At 1:30am every 
 42 * last Friday of the month". 
 43 * <P>
 44 * Cron expressions are comprised of 6 required fields and one optional field
 45 * separated by white space. The fields respectively are described as follows:
 46 * 
 47 * <table cellspacing="8">
 48 * <tr>
 49 * <th align="left">Field Name</th>
 50 * <th align="left">&nbsp;</th>
 51 * <th align="left">Allowed Values</th>
 52 * <th align="left">&nbsp;</th>
 53 * <th align="left">Allowed Special Characters</th>
 54 * </tr>
 55 * <tr>
 56 * <td align="left"><code>Seconds</code></td>
 57 * <td align="left">&nbsp;</th>
 58 * <td align="left"><code>0-59</code></td>
 59 * <td align="left">&nbsp;</th>
 60 * <td align="left"><code>, - * /</code></td>
 61 * </tr>
 62 * <tr>
 63 * <td align="left"><code>Minutes</code></td>
 64 * <td align="left">&nbsp;</th>
 65 * <td align="left"><code>0-59</code></td>
 66 * <td align="left">&nbsp;</th>
 67 * <td align="left"><code>, - * /</code></td>
 68 * </tr>
 69 * <tr>
 70 * <td align="left"><code>Hours</code></td>
 71 * <td align="left">&nbsp;</th>
 72 * <td align="left"><code>0-23</code></td>
 73 * <td align="left">&nbsp;</th>
 74 * <td align="left"><code>, - * /</code></td>
 75 * </tr>
 76 * <tr>
 77 * <td align="left"><code>Day-of-month</code></td>
 78 * <td align="left">&nbsp;</th>
 79 * <td align="left"><code>1-31</code></td>
 80 * <td align="left">&nbsp;</th>
 81 * <td align="left"><code>, - * ? / L W C</code></td>
 82 * </tr>
 83 * <tr>
 84 * <td align="left"><code>Month</code></td>
 85 * <td align="left">&nbsp;</th>
 86 * <td align="left"><code>1-12 or JAN-DEC</code></td>
 87 * <td align="left">&nbsp;</th>
 88 * <td align="left"><code>, - * /</code></td>
 89 * </tr>
 90 * <tr>
 91 * <td align="left"><code>Day-of-Week</code></td>
 92 * <td align="left">&nbsp;</th>
 93 * <td align="left"><code>1-7 or SUN-SAT</code></td>
 94 * <td align="left">&nbsp;</th>
 95 * <td align="left"><code>, - * ? / L #</code></td>
 96 * </tr>
 97 * <tr>
 98 * <td align="left"><code>Year (Optional)</code></td>
 99 * <td align="left">&nbsp;</th>
100 * <td align="left"><code>empty, 1970-2099</code></td>
101 * <td align="left">&nbsp;</th>
102 * <td align="left"><code>, - * /</code></td>
103 * </tr>
104 * </table>
105 * <P>
106 * The '*' character is used to specify all values. For example, &quot;*&quot; 
107 * in the minute field means &quot;every minute&quot;.
108 * <P>
109 * The '?' character is allowed for the day-of-month and day-of-week fields. It
110 * is used to specify 'no specific value'. This is useful when you need to
111 * specify something in one of the two fileds, but not the other.
112 * <P>
113 * The '-' character is used to specify ranges For example &quot;10-12&quot; in
114 * the hour field means &quot;the hours 10, 11 and 12&quot;.
115 * <P>
116 * The ',' character is used to specify additional values. For example
117 * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
118 * Wednesday, and Friday&quot;.
119 * <P>
120 * The '/' character is used to specify increments. For example &quot;0/15&quot;
121 * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And 
122 * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
123 * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
124 * the value to start with. Essentially, for each field in the expression, there
125 * is a set of numbers that can be turned on or off. For seconds and minutes, 
126 * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
127 * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
128 * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
129 * month field only turns on month &quot;7&quot;, it does NOT mean every 6th 
130 * month, please note that subtlety.  
131 * <P>
132 * The 'L' character is allowed for the day-of-month and day-of-week fields.
133 * This character is short-hand for &quot;last&quot;, but it has different 
134 * meaning in each of the two fields. For example, the value &quot;L&quot; in 
135 * the day-of-month field means &quot;the last day of the month&quot; - day 31 
136 * for January, day 28 for February on non-leap years. If used in the 
137 * day-of-week field by itself, it simply means &quot;7&quot; or 
138 * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
139 * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
140 * means &quot;the last friday of the month&quot;. When using the 'L' option, it
141 * is important not to specify lists, or ranges of values, as you'll get 
142 * confusing results.
143 * <P>
144 * The 'W' character is allowed for the day-of-month field.  This character 
145 * is used to specify the weekday (Monday-Friday) nearest the given day.  As an 
146 * example, if you were to specify &quot;15W&quot; as the value for the 
147 * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
148 * the month&quot;. So if the 15th is a Saturday, the trigger will fire on 
149 * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
150 * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. 
151 * However if you specify &quot;1W&quot; as the value for day-of-month, and the
152 * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 
153 * 'jump' over the boundary of a month's days.  The 'W' character can only be 
154 * specified when the day-of-month is a single day, not a range or list of days.
155 * <P>
156 * The 'L' and 'W' characters can also be combined for the day-of-month 
157 * expression to yield 'LW', which translates to &quot;last weekday of the 
158 * month&quot;.
159 * <P>
160 * The '#' character is allowed for the day-of-week field. This character is
161 * used to specify &quot;the nth&quot; XXX day of the month. For example, the 
162 * value of &quot;6#3&quot; in the day-of-week field means the third Friday of 
163 * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month). 
164 * Other examples: &quot;2#1&quot; = the first Monday of the month and 
165 * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
166 * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
167 * no firing will occur that month.
168 * <P>
169 * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
170 * This character is short-hand for "calendar". This means values are
171 * calculated against the associated calendar, if any. If no calendar is
172 * associated, then it is equivalent to having an all-inclusive calendar. A
173 * value of "5C" in the day-of-month field means "the first day included by the
174 * calendar on or after the 5th". A value of "1C" in the day-of-week field
175 * means "the first day included by the calendar on or after sunday".-->
176 * <P>
177 * The legal characters and the names of months and days of the week are not
178 * case sensitive.
179 * 
180 * <p>
181 * <b>NOTES:</b>
182 * <ul>
183 * <li>Support for specifying both a day-of-week and a day-of-month value is
184 * not complete (you'll need to use the '?' character in on of these fields).
185 * </li>
186 * </ul>
187 * </p>
188 * 
189 * 
190 * @author Sharada Jambula, James House
191 * @author Contributions from Mads Henderson
192 * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
193 */
194public class CronExpression {
195
196  private static final WidgetsLocalizedMessages MSGS = WidgetsLocalizedMessagesSingleton.getInstance().getMessages();
197  private static final long serialVersionUID = 12423409423L;
198
199  protected static final int SECOND = 0;
200
201  protected static final int MINUTE = 1;
202
203  protected static final int HOUR = 2;
204
205  protected static final int DAY_OF_MONTH = 3;
206
207  protected static final int MONTH = 4;
208
209  protected static final int DAY_OF_WEEK = 5;
210
211  protected static final int YEAR = 6;
212
213  protected static final int ALL_SPEC_INT = 99; // '*'
214
215  protected static final int NO_SPEC_INT = 98; // '?'
216
217  protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT);
218
219  protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT);
220
221  protected static Map monthMap = new HashMap(20);
222
223  protected static Map dayMap = new HashMap(60);
224  static {
225    monthMap.put("JAN", new Integer(0));
226    monthMap.put("FEB", new Integer(1));
227    monthMap.put("MAR", new Integer(2));
228    monthMap.put("APR", new Integer(3));
229    monthMap.put("MAY", new Integer(4));
230    monthMap.put("JUN", new Integer(5));
231    monthMap.put("JUL", new Integer(6));
232    monthMap.put("AUG", new Integer(7));
233    monthMap.put("SEP", new Integer(8));
234    monthMap.put("OCT", new Integer(9));
235    monthMap.put("NOV", new Integer(10));
236    monthMap.put("DEC", new Integer(11));
237
238    dayMap.put("SUN", new Integer(1));
239    dayMap.put("MON", new Integer(2));
240    dayMap.put("TUE", new Integer(3));
241    dayMap.put("WED", new Integer(4));
242    dayMap.put("THU", new Integer(5));
243    dayMap.put("FRI", new Integer(6));
244    dayMap.put("SAT", new Integer(7));
245  }
246
247  private String cronExpression = null;
248
249  protected transient TreeSet seconds;
250
251  protected transient TreeSet minutes;
252
253  protected transient TreeSet hours;
254
255  protected transient TreeSet daysOfMonth;
256
257  protected transient TreeSet months;
258
259  protected transient TreeSet daysOfWeek;
260
261  protected transient TreeSet years;
262
263  protected transient boolean lastdayOfWeek = false;
264
265  protected transient int nthdayOfWeek = 0;
266
267  protected transient boolean lastdayOfMonth = false;
268
269  protected transient boolean nearestWeekday = false;
270
271  protected transient boolean calendardayOfWeek = false;
272
273  protected transient boolean calendardayOfMonth = false;
274
275  protected transient boolean expressionParsed = false;
276
277  /**
278  * Constructs a new <CODE>CronExpression</CODE> based on the specified 
279  * parameter.
280  * 
281  * @param cronExpression String representation of the cron expression the
282  *                       new object should represent
283  * @throws java.text.ParseException
284  *         if the string expression cannot be parsed into a valid 
285  *         <CODE>CronExpression</CODE>
286  */
287  public CronExpression(String cronExpression) throws ParseException {
288    if (cronExpression == null) {
289      throw new IllegalArgumentException(MSGS.cronExpressionNull());
290    }
291
292    this.cronExpression = cronExpression;
293
294    buildExpression(cronExpression.toUpperCase());
295  }
296
297  /**
298   * Returns the string representation of the <CODE>CronExpression</CODE>
299   * 
300   * @return a string representation of the <CODE>CronExpression</CODE>
301   */
302  public String toString() {
303    return cronExpression;
304  }
305
306  /**
307   * Indicates whether the specified cron expression can be parsed into a 
308   * valid cron expression
309   * 
310   * @param cronExpression the expression to evaluate
311   * @return a boolean indicating whether the given expression is a valid cron
312   *         expression
313   */
314  public static boolean isValidExpression(String cronExpression) {
315
316    try {
317      new CronExpression(cronExpression);
318    } catch (ParseException pe) {
319      return false;
320    }
321
322    return true;
323  }
324
325  ////////////////////////////////////////////////////////////////////////////
326  //
327  // Expression Parsing Functions
328  //
329  ////////////////////////////////////////////////////////////////////////////
330
331  protected void buildExpression(String expression) throws ParseException {
332    expressionParsed = true;
333
334    try {
335
336      if (seconds == null)
337        seconds = new TreeSet();
338      if (minutes == null)
339        minutes = new TreeSet();
340      if (hours == null)
341        hours = new TreeSet();
342      if (daysOfMonth == null)
343        daysOfMonth = new TreeSet();
344      if (months == null)
345        months = new TreeSet();
346      if (daysOfWeek == null)
347        daysOfWeek = new TreeSet();
348      if (years == null)
349        years = new TreeSet();
350
351      int exprOn = SECOND;
352
353      String[] exprsTok = expression.split(" |\\t");
354
355      for (int i = 0; i < exprsTok.length; i++) {
356        if (exprOn > YEAR) {
357          break;
358        }
359        String expr = exprsTok[i];
360        String[] vtok = expr.split(",");
361        for (int j = 0; j < vtok.length; j++) {
362          String v = vtok[j];
363          storeExpressionVals(0, v, exprOn);
364        }
365        exprOn++;
366      }
367
368      if (exprOn <= DAY_OF_WEEK)
369        throw new ParseException(MSGS.cronUnexpectedEndOfExpression(), expression.length());
370
371      if (exprOn <= YEAR)
372        storeExpressionVals(0, "*", YEAR);
373
374    } catch (ParseException pe) {
375      throw pe;
376    } catch (Exception e) {
377      throw new ParseException(MSGS.cronIllegalExpressionFormat(e.toString()), 0);
378    }
379  }
380
381  protected int storeExpressionVals(int pos, String s, int type) throws ParseException {
382    int incr = 0;
383    int i = skipWhiteSpace(pos, s);
384    if (i >= s.length())
385      return i;
386    char c = s.charAt(i);
387    if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW"))) {
388      String sub = s.substring(i, i + 3);
389      int sval = -1;
390      int eval = -1;
391      if (type == MONTH) {
392        sval = getMonthNumber(sub) + 1;
393        if (sval < 0) {
394          throw new ParseException(MSGS.cronInvalidMonthValue(sub), i);
395        }
396        if (s.length() > i + 3) {
397          c = s.charAt(i + 3);
398          if (c == '-') {
399            i += 4;
400            sub = s.substring(i, i + 3);
401            eval = getMonthNumber(sub) + 1;
402            if (eval < 0) {
403              throw new ParseException(MSGS.cronInvalidMonthValue(sub), i);
404            }
405          }
406        }
407      } else if (type == DAY_OF_WEEK) {
408        sval = getDayOfWeekNumber(sub);
409        if (sval < 0) {
410          throw new ParseException(MSGS.cronInvalidDOWValue(sub), i);
411        }
412        if (s.length() > i + 3) {
413          c = s.charAt(i + 3);
414          if (c == '-') {
415            i += 4;
416            sub = s.substring(i, i + 3);
417            eval = getDayOfWeekNumber(sub);
418            if (eval < 0) {
419              throw new ParseException(MSGS.cronInvalidDOWValue(sub), i);
420            }
421            if (sval > eval) {
422              throw new ParseException(MSGS.cronInvalidDOWSequence(Integer.toString(sval), Integer.toString(eval)), i);
423            }
424
425          } else if (c == '#') {
426            try {
427              i += 4;
428              nthdayOfWeek = Integer.parseInt(s.substring(i));
429              if (nthdayOfWeek < 1 || nthdayOfWeek > 5)
430                throw new Exception();
431            } catch (Exception e) {
432              throw new ParseException(MSGS.cronIllegalHashFollowingNumeric(), i);
433            }
434          } else if (c == 'L') {
435            lastdayOfWeek = true;
436            i++;
437          }
438        }
439
440      } else {
441        throw new ParseException(MSGS.cronIllegalCharactersForPosition(sub), i);
442      }
443      if (eval != -1) {
444        incr = 1;
445      }
446      addToSet(sval, eval, incr, type);
447      return (i + 3);
448    }
449
450    if (c == '?') {
451      i++;
452      if ((i + 1) < s.length() && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
453        throw new ParseException(
454            MSGS.cronIllegalCharacterAfter( "?", String.valueOf(s.charAt(i))), i);
455      }
456      if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
457        throw new ParseException(MSGS.cronIllegalQuestionMark(), i);
458      }
459      if (type == DAY_OF_WEEK && !lastdayOfMonth) {
460        int val = ((Integer) daysOfMonth.last()).intValue();
461        if (val == NO_SPEC_INT) {
462          throw new ParseException(MSGS.cronIllegalQuestionMark(), i);
463        }
464      }
465
466      addToSet(NO_SPEC_INT, -1, 0, type);
467      return i;
468    }
469
470    if (c == '*' || c == '/') {
471      if (c == '*' && (i + 1) >= s.length()) {
472        addToSet(ALL_SPEC_INT, -1, incr, type);
473        return i + 1;
474      } else if (c == '/' && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s.charAt(i + 1) == '\t')) {
475        throw new ParseException(MSGS.cronIllegalSlash(), i);
476      } else if (c == '*') {
477        i++;
478      }
479      c = s.charAt(i);
480      if (c == '/') { // is an increment specified?
481        i++;
482        if (i >= s.length()) {
483          throw new ParseException(MSGS.cronUnexpectedEndOfString(), i);
484        }
485
486        incr = getNumericValue(s, i);
487
488        i++;
489        if (incr > 10)
490          i++;
491        if (incr > 59 && (type == SECOND || type == MINUTE)) {
492          throw new ParseException(MSGS.cronIllegalIncrement("60", Integer.toString(incr)), i);
493        } else if (incr > 23 && (type == HOUR)) {
494          throw new ParseException(MSGS.cronIllegalIncrement("24", Integer.toString(incr)), i);
495        } else if (incr > 31 && (type == DAY_OF_MONTH)) {
496          throw new ParseException(MSGS.cronIllegalIncrement("31", Integer.toString(incr)), i);
497        } else if (incr > 7 && (type == DAY_OF_WEEK)) {
498          throw new ParseException(MSGS.cronIllegalIncrement("7", Integer.toString(incr)), i);
499        } else if (incr > 12 && (type == MONTH)) {
500          throw new ParseException(MSGS.cronIllegalIncrement("12", Integer.toString(incr)), i);
501        }
502      } else
503        incr = 1;
504
505      addToSet(ALL_SPEC_INT, -1, incr, type);
506      return i;
507    } else if (c == 'L') {
508      i++;
509      if (type == DAY_OF_MONTH)
510        lastdayOfMonth = true;
511      if (type == DAY_OF_WEEK)
512        addToSet(7, 7, 0, type);
513      if (type == DAY_OF_MONTH && s.length() > i) {
514        c = s.charAt(i);
515        if (c == 'W') {
516          nearestWeekday = true;
517          i++;
518        }
519      }
520      return i;
521    } else if (c >= '0' && c <= '9') {
522      int val = Integer.parseInt(String.valueOf(c));
523      i++;
524      if (i >= s.length()) {
525        addToSet(val, -1, -1, type);
526      } else {
527        c = s.charAt(i);
528        if (c >= '0' && c <= '9') {
529          ValueSet vs = getValue(val, s, i);
530          val = vs.value;
531          i = vs.pos;
532        }
533        i = checkNext(i, s, val, type);
534        return i;
535      }
536    } else {
537      throw new ParseException(MSGS.cronUnexpectedCharacter(String.valueOf(c)), i);
538    }
539
540    return i;
541  }
542
543  protected int checkNext(int pos, String s, int val, int type) throws ParseException {
544    int end = -1;
545    int i = pos;
546
547    if (i >= s.length()) {
548      addToSet(val, end, -1, type);
549      return i;
550    }
551
552    char c = s.charAt(pos);
553
554    if (c == 'L') {
555      if (type == DAY_OF_WEEK) {
556        lastdayOfWeek = true;
557      } else {
558        throw new ParseException(MSGS.cronOptionIsNotValidHere("L", Integer.toString(i)), i);
559      }
560      TreeSet set = getSet(type);
561      set.add(new Integer(val));
562      i++;
563      return i;
564    }
565
566    if (c == 'W') {
567      if (type == DAY_OF_MONTH) {
568        nearestWeekday = true;
569      } else {
570        throw new ParseException(MSGS.cronOptionIsNotValidHere("W", Integer.toString(i)), i);
571      }
572      TreeSet set = getSet(type);
573      set.add(new Integer(val));
574      i++;
575      return i;
576    }
577
578    if (c == '#') {
579      if (type != DAY_OF_WEEK) {
580        throw new ParseException(MSGS.cronOptionIsNotValidHere("#", Integer.toString(i)), i);
581      }
582      i++;
583      try {
584        nthdayOfWeek = Integer.parseInt(s.substring(i));
585        if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
586          throw new Exception();
587        }
588      } catch (Exception e) {
589        throw new ParseException(MSGS.cronIllegalHashFollowingNumeric(), i);
590      }
591
592      TreeSet set = getSet(type);
593      set.add(new Integer(val));
594      i++;
595      return i;
596    }
597
598    if (c == 'C') {
599      if (type == DAY_OF_WEEK) {
600        calendardayOfWeek = true;
601      }  else if (type == DAY_OF_MONTH) {
602        calendardayOfMonth = true;
603      } else {
604        throw new ParseException(MSGS.cronOptionIsNotValidHere("C", Integer.toString(i)), i);
605      }
606      TreeSet set = getSet(type);
607      set.add(new Integer(val));
608      i++;
609      return i;
610    }
611
612    if (c == '-') {
613      i++;
614      c = s.charAt(i);
615      int v = Integer.parseInt(String.valueOf(c));
616      end = v;
617      i++;
618      if (i >= s.length()) {
619        addToSet(val, end, 1, type);
620        return i;
621      }
622      c = s.charAt(i);
623      if (c >= '0' && c <= '9') {
624        ValueSet vs = getValue(v, s, i);
625        int v1 = vs.value;
626        end = v1;
627        i = vs.pos;
628      }
629      if (i < s.length() && ((c = s.charAt(i)) == '/')) {
630        i++;
631        c = s.charAt(i);
632        int v2 = Integer.parseInt(String.valueOf(c));
633        i++;
634        if (i >= s.length()) {
635          addToSet(val, end, v2, type);
636          return i;
637        }
638        c = s.charAt(i);
639        if (c >= '0' && c <= '9') {
640          ValueSet vs = getValue(v2, s, i);
641          int v3 = vs.value;
642          addToSet(val, end, v3, type);
643          i = vs.pos;
644          return i;
645        } else {
646          addToSet(val, end, v2, type);
647          return i;
648        }
649      } else {
650        addToSet(val, end, 1, type);
651        return i;
652      }
653    }
654
655    if (c == '/') {
656      i++;
657      c = s.charAt(i);
658      int v2 = Integer.parseInt(String.valueOf(c));
659      i++;
660      if (i >= s.length()) {
661        addToSet(val, end, v2, type);
662        return i;
663      }
664      c = s.charAt(i);
665      if (c >= '0' && c <= '9') {
666        ValueSet vs = getValue(v2, s, i);
667        int v3 = vs.value;
668        addToSet(val, end, v3, type);
669        i = vs.pos;
670        return i;
671      } else {
672        throw new ParseException(MSGS.cronUnexpectedCharacterAfterSlash(String.valueOf(c)), i);
673      }
674    }
675
676    addToSet(val, end, 0, type);
677    i++;
678    return i;
679  }
680
681  public String getCronExpression() {
682    return cronExpression;
683  }
684
685
686  protected int skipWhiteSpace(int i, String s) {
687    for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++)
688      ;
689
690    return i;
691  }
692
693  protected int findNextWhiteSpace(int i, String s) {
694    for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++)
695      ;
696
697    return i;
698  }
699
700  protected void addToSet(int val, int end, int incr, int type) throws ParseException {
701    TreeSet set = getSet(type);
702
703    if (type == SECOND || type == MINUTE) {
704      if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
705        throw new ParseException(MSGS.cronInvalidMinuteSecondValue(), -1);
706      }        
707    } else if (type == HOUR) {
708      if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
709        throw new ParseException(MSGS.cronInvalidHourValue(), -1);
710      }
711    } else if (type == DAY_OF_MONTH) {
712      if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
713        throw new ParseException(MSGS.cronInvalidDayOfMonthValue(), -1);
714      }
715    } else if (type == MONTH) {
716      if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
717        throw new ParseException(MSGS.cronInvalidMonthValueGeneral(), -1);
718      }
719    } else if (type == DAY_OF_WEEK) {
720      if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT) && (val != NO_SPEC_INT)) {
721        throw new ParseException(MSGS.cronInvalidDayOfWeekValue(), -1);
722      }
723    }
724
725    if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
726      if (val != -1) {
727        set.add(new Integer(val));
728      } else {
729        set.add(NO_SPEC);
730      }
731      return;
732    }
733
734    int startAt = val;
735    int stopAt = end;
736
737    if (val == ALL_SPEC_INT && incr <= 0) {
738      incr = 1;
739      set.add(ALL_SPEC); // put in a marker, but also fill values
740    }
741
742    if (type == SECOND || type == MINUTE) {
743      if (stopAt == -1) {
744        stopAt = 59;
745      }
746      if (startAt == -1 || startAt == ALL_SPEC_INT) {
747        startAt = 0;
748      }
749    } else if (type == HOUR) {
750      if (stopAt == -1) {
751        stopAt = 23;
752      }
753      if (startAt == -1 || startAt == ALL_SPEC_INT) {
754        startAt = 0;
755      }
756    } else if (type == DAY_OF_MONTH) {
757      if (stopAt == -1) {
758        stopAt = 31;
759      }
760      if (startAt == -1 || startAt == ALL_SPEC_INT) {
761        startAt = 1;
762      }
763    } else if (type == MONTH) {
764      if (stopAt == -1) {
765        stopAt = 12;
766      }
767      if (startAt == -1 || startAt == ALL_SPEC_INT) {
768        startAt = 1;
769      }
770    } else if (type == DAY_OF_WEEK) {
771      if (stopAt == -1) {
772        stopAt = 7;
773      }
774      if (startAt == -1 || startAt == ALL_SPEC_INT) {
775        startAt = 1;
776      }
777    } else if (type == YEAR) {
778      if (stopAt == -1) {
779        stopAt = 2099;
780      }
781      if (startAt == -1 || startAt == ALL_SPEC_INT) {
782        startAt = 1970;
783      }
784    }
785
786    for (int i = startAt; i <= stopAt; i += incr) {
787      set.add(new Integer(i));
788    }
789  }
790
791  protected TreeSet getSet(int type) {
792    switch (type) {
793      case SECOND:
794        return seconds;
795      case MINUTE:
796        return minutes;
797      case HOUR:
798        return hours;
799      case DAY_OF_MONTH:
800        return daysOfMonth;
801      case MONTH:
802        return months;
803      case DAY_OF_WEEK:
804        return daysOfWeek;
805      case YEAR:
806        return years;
807      default:
808        return null;
809    }
810  }
811
812  protected ValueSet getValue(int v, String s, int i) {
813    char c = s.charAt(i);
814    String s1 = String.valueOf(v);
815    while (c >= '0' && c <= '9') {
816      s1 += c;
817      i++;
818      if (i >= s.length()) {
819        break;
820      }
821      c = s.charAt(i);
822    }
823    ValueSet val = new ValueSet();
824    if (i < s.length()) {
825      val.pos = i;
826    } else {
827      val.pos = i + 1;
828    }
829    val.value = Integer.parseInt(s1);
830    return val;
831  }
832
833  protected int getNumericValue(String s, int i) {
834    int endOfVal = findNextWhiteSpace(i, s);
835    String val = s.substring(i, endOfVal);
836    return Integer.parseInt(val);
837  }
838
839  protected int getMonthNumber(String s) {
840    Integer integer = (Integer) monthMap.get(s);
841
842    if (integer == null) {
843      return -1;
844    }
845
846    return integer.intValue();
847  }
848
849  protected int getDayOfWeekNumber(String s) {
850    Integer integer = (Integer) dayMap.get(s);
851
852    if (integer == null) {
853      return -1;
854    }
855
856    return integer.intValue();
857  }
858
859
860}
861
862class ValueSet {
863  public int value;
864
865  public int pos;
866}