Home » quartz-1.6.0 » org » quartz » [javadoc | source]

    1   package org.quartz;
    2   
    3   import java.io.Serializable;
    4   import java.text.ParseException;
    5   import java.util.Calendar;
    6   import java.util.Date;
    7   import java.util.HashMap;
    8   import java.util.Iterator;
    9   import java.util.Locale;
   10   import java.util.Map;
   11   import java.util.SortedSet;
   12   import java.util.StringTokenizer;
   13   import java.util.TimeZone;
   14   import java.util.TreeSet;
   15   
   16   /**
   17    * Provides a parser and evaluator for unix-like cron expressions. Cron 
   18    * expressions provide the ability to specify complex time combinations such as
   19    * "At 8:00am every Monday through Friday" or "At 1:30am every 
   20    * last Friday of the month". 
   21    * <P>
   22    * Cron expressions are comprised of 6 required fields and one optional field
   23    * separated by white space. The fields respectively are described as follows:
   24    * 
   25    * <table cellspacing="8">
   26    * <tr>
   27    * <th align="left">Field Name</th>
   28    * <th align="left">&nbsp;</th>
   29    * <th align="left">Allowed Values</th>
   30    * <th align="left">&nbsp;</th>
   31    * <th align="left">Allowed Special Characters</th>
   32    * </tr>
   33    * <tr>
   34    * <td align="left"><code>Seconds</code></td>
   35    * <td align="left">&nbsp;</th>
   36    * <td align="left"><code>0-59</code></td>
   37    * <td align="left">&nbsp;</th>
   38    * <td align="left"><code>, - * /</code></td>
   39    * </tr>
   40    * <tr>
   41    * <td align="left"><code>Minutes</code></td>
   42    * <td align="left">&nbsp;</th>
   43    * <td align="left"><code>0-59</code></td>
   44    * <td align="left">&nbsp;</th>
   45    * <td align="left"><code>, - * /</code></td>
   46    * </tr>
   47    * <tr>
   48    * <td align="left"><code>Hours</code></td>
   49    * <td align="left">&nbsp;</th>
   50    * <td align="left"><code>0-23</code></td>
   51    * <td align="left">&nbsp;</th>
   52    * <td align="left"><code>, - * /</code></td>
   53    * </tr>
   54    * <tr>
   55    * <td align="left"><code>Day-of-month</code></td>
   56    * <td align="left">&nbsp;</th>
   57    * <td align="left"><code>1-31</code></td>
   58    * <td align="left">&nbsp;</th>
   59    * <td align="left"><code>, - * ? / L W</code></td>
   60    * </tr>
   61    * <tr>
   62    * <td align="left"><code>Month</code></td>
   63    * <td align="left">&nbsp;</th>
   64    * <td align="left"><code>1-12 or JAN-DEC</code></td>
   65    * <td align="left">&nbsp;</th>
   66    * <td align="left"><code>, - * /</code></td>
   67    * </tr>
   68    * <tr>
   69    * <td align="left"><code>Day-of-Week</code></td>
   70    * <td align="left">&nbsp;</th>
   71    * <td align="left"><code>1-7 or SUN-SAT</code></td>
   72    * <td align="left">&nbsp;</th>
   73    * <td align="left"><code>, - * ? / L #</code></td>
   74    * </tr>
   75    * <tr>
   76    * <td align="left"><code>Year (Optional)</code></td>
   77    * <td align="left">&nbsp;</th>
   78    * <td align="left"><code>empty, 1970-2099</code></td>
   79    * <td align="left">&nbsp;</th>
   80    * <td align="left"><code>, - * /</code></td>
   81    * </tr>
   82    * </table>
   83    * <P>
   84    * The '*' character is used to specify all values. For example, &quot;*&quot; 
   85    * in the minute field means &quot;every minute&quot;.
   86    * <P>
   87    * The '?' character is allowed for the day-of-month and day-of-week fields. It
   88    * is used to specify 'no specific value'. This is useful when you need to
   89    * specify something in one of the two fileds, but not the other.
   90    * <P>
   91    * The '-' character is used to specify ranges For example &quot;10-12&quot; in
   92    * the hour field means &quot;the hours 10, 11 and 12&quot;.
   93    * <P>
   94    * The ',' character is used to specify additional values. For example
   95    * &quot;MON,WED,FRI&quot; in the day-of-week field means &quot;the days Monday,
   96    * Wednesday, and Friday&quot;.
   97    * <P>
   98    * The '/' character is used to specify increments. For example &quot;0/15&quot;
   99    * in the seconds field means &quot;the seconds 0, 15, 30, and 45&quot;. And 
  100    * &quot;5/15&quot; in the seconds field means &quot;the seconds 5, 20, 35, and
  101    * 50&quot;.  Specifying '*' before the  '/' is equivalent to specifying 0 is
  102    * the value to start with. Essentially, for each field in the expression, there
  103    * is a set of numbers that can be turned on or off. For seconds and minutes, 
  104    * the numbers range from 0 to 59. For hours 0 to 23, for days of the month 0 to
  105    * 31, and for months 1 to 12. The &quot;/&quot; character simply helps you turn
  106    * on every &quot;nth&quot; value in the given set. Thus &quot;7/6&quot; in the
  107    * month field only turns on month &quot;7&quot;, it does NOT mean every 6th 
  108    * month, please note that subtlety.  
  109    * <P>
  110    * The 'L' character is allowed for the day-of-month and day-of-week fields.
  111    * This character is short-hand for &quot;last&quot;, but it has different 
  112    * meaning in each of the two fields. For example, the value &quot;L&quot; in 
  113    * the day-of-month field means &quot;the last day of the month&quot; - day 31 
  114    * for January, day 28 for February on non-leap years. If used in the 
  115    * day-of-week field by itself, it simply means &quot;7&quot; or 
  116    * &quot;SAT&quot;. But if used in the day-of-week field after another value, it
  117    * means &quot;the last xxx day of the month&quot; - for example &quot;6L&quot;
  118    * means &quot;the last friday of the month&quot;. When using the 'L' option, it
  119    * is important not to specify lists, or ranges of values, as you'll get 
  120    * confusing results.
  121    * <P>
  122    * The 'W' character is allowed for the day-of-month field.  This character 
  123    * is used to specify the weekday (Monday-Friday) nearest the given day.  As an 
  124    * example, if you were to specify &quot;15W&quot; as the value for the 
  125    * day-of-month field, the meaning is: &quot;the nearest weekday to the 15th of
  126    * the month&quot;. So if the 15th is a Saturday, the trigger will fire on 
  127    * Friday the 14th. If the 15th is a Sunday, the trigger will fire on Monday the
  128    * 16th. If the 15th is a Tuesday, then it will fire on Tuesday the 15th. 
  129    * However if you specify &quot;1W&quot; as the value for day-of-month, and the
  130    * 1st is a Saturday, the trigger will fire on Monday the 3rd, as it will not 
  131    * 'jump' over the boundary of a month's days.  The 'W' character can only be 
  132    * specified when the day-of-month is a single day, not a range or list of days.
  133    * <P>
  134    * The 'L' and 'W' characters can also be combined for the day-of-month 
  135    * expression to yield 'LW', which translates to &quot;last weekday of the 
  136    * month&quot;.
  137    * <P>
  138    * The '#' character is allowed for the day-of-week field. This character is
  139    * used to specify &quot;the nth&quot; XXX day of the month. For example, the 
  140    * value of &quot;6#3&quot; in the day-of-week field means the third Friday of 
  141    * the month (day 6 = Friday and &quot;#3&quot; = the 3rd one in the month). 
  142    * Other examples: &quot;2#1&quot; = the first Monday of the month and 
  143    * &quot;4#5&quot; = the fifth Wednesday of the month. Note that if you specify
  144    * &quot;#5&quot; and there is not 5 of the given day-of-week in the month, then
  145    * no firing will occur that month.
  146    * <P>
  147    * <!--The 'C' character is allowed for the day-of-month and day-of-week fields.
  148    * This character is short-hand for "calendar". This means values are
  149    * calculated against the associated calendar, if any. If no calendar is
  150    * associated, then it is equivalent to having an all-inclusive calendar. A
  151    * value of "5C" in the day-of-month field means "the first day included by the
  152    * calendar on or after the 5th". A value of "1C" in the day-of-week field
  153    * means "the first day included by the calendar on or after sunday".-->
  154    * <P>
  155    * The legal characters and the names of months and days of the week are not
  156    * case sensitive.
  157    * 
  158    * <p>
  159    * <b>NOTES:</b>
  160    * <ul>
  161    * <li>Support for specifying both a day-of-week and a day-of-month value is
  162    * not complete (you'll need to use the '?' character in on of these fields).
  163    * </li>
  164    * </ul>
  165    * </p>
  166    * 
  167    * 
  168    * @author Sharada Jambula, James House
  169    * @author Contributions from Mads Henderson
  170    * @author Refactoring from CronTrigger to CronExpression by Aaron Craven
  171    */
  172   public class CronExpression implements Serializable, Cloneable {
  173   
  174       private static final long serialVersionUID = 12423409423L;
  175       
  176       protected static final int SECOND = 0;
  177       protected static final int MINUTE = 1;
  178       protected static final int HOUR = 2;
  179       protected static final int DAY_OF_MONTH = 3;
  180       protected static final int MONTH = 4;
  181       protected static final int DAY_OF_WEEK = 5;
  182       protected static final int YEAR = 6;
  183       protected static final int ALL_SPEC_INT = 99; // '*'
  184       protected static final int NO_SPEC_INT = 98; // '?'
  185       protected static final Integer ALL_SPEC = new Integer(ALL_SPEC_INT);
  186       protected static final Integer NO_SPEC = new Integer(NO_SPEC_INT);
  187       
  188       protected static Map monthMap = new HashMap(20);
  189       protected static Map dayMap = new HashMap(60);
  190       static {
  191           monthMap.put("JAN", new Integer(0));
  192           monthMap.put("FEB", new Integer(1));
  193           monthMap.put("MAR", new Integer(2));
  194           monthMap.put("APR", new Integer(3));
  195           monthMap.put("MAY", new Integer(4));
  196           monthMap.put("JUN", new Integer(5));
  197           monthMap.put("JUL", new Integer(6));
  198           monthMap.put("AUG", new Integer(7));
  199           monthMap.put("SEP", new Integer(8));
  200           monthMap.put("OCT", new Integer(9));
  201           monthMap.put("NOV", new Integer(10));
  202           monthMap.put("DEC", new Integer(11));
  203   
  204           dayMap.put("SUN", new Integer(1));
  205           dayMap.put("MON", new Integer(2));
  206           dayMap.put("TUE", new Integer(3));
  207           dayMap.put("WED", new Integer(4));
  208           dayMap.put("THU", new Integer(5));
  209           dayMap.put("FRI", new Integer(6));
  210           dayMap.put("SAT", new Integer(7));
  211       }
  212   
  213       private String cronExpression = null;
  214       private TimeZone timeZone = null;
  215       protected transient TreeSet seconds;
  216       protected transient TreeSet minutes;
  217       protected transient TreeSet hours;
  218       protected transient TreeSet daysOfMonth;
  219       protected transient TreeSet months;
  220       protected transient TreeSet daysOfWeek;
  221       protected transient TreeSet years;
  222   
  223       protected transient boolean lastdayOfWeek = false;
  224       protected transient int nthdayOfWeek = 0;
  225       protected transient boolean lastdayOfMonth = false;
  226       protected transient boolean nearestWeekday = false;
  227       protected transient boolean expressionParsed = false;
  228       
  229       /**
  230        * Constructs a new <CODE>CronExpression</CODE> based on the specified 
  231        * parameter.
  232        * 
  233        * @param cronExpression String representation of the cron expression the
  234        *                       new object should represent
  235        * @throws java.text.ParseException
  236        *         if the string expression cannot be parsed into a valid 
  237        *         <CODE>CronExpression</CODE>
  238        */
  239       public CronExpression(String cronExpression) throws ParseException {
  240           if (cronExpression == null) {
  241               throw new IllegalArgumentException("cronExpression cannot be null");
  242           }
  243           
  244           this.cronExpression = cronExpression;
  245           
  246           buildExpression(cronExpression.toUpperCase(Locale.US));
  247       }
  248       
  249       /**
  250        * Indicates whether the given date satisfies the cron expression. Note that
  251        * milliseconds are ignored, so two Dates falling on different milliseconds
  252        * of the same second will always have the same result here.
  253        * 
  254        * @param date the date to evaluate
  255        * @return a boolean indicating whether the given date satisfies the cron
  256        *         expression
  257        */
  258       public boolean isSatisfiedBy(Date date) {
  259           Calendar testDateCal = Calendar.getInstance();
  260           testDateCal.setTime(date);
  261           testDateCal.set(Calendar.MILLISECOND, 0);
  262           Date originalDate = testDateCal.getTime();
  263           
  264           testDateCal.add(Calendar.SECOND, -1);
  265           
  266           Date timeAfter = getTimeAfter(testDateCal.getTime());
  267           
  268           return ((timeAfter != null) && (timeAfter.equals(originalDate)));
  269       }
  270       
  271       /**
  272        * Returns the next date/time <I>after</I> the given date/time which
  273        * satisfies the cron expression.
  274        * 
  275        * @param date the date/time at which to begin the search for the next valid
  276        *             date/time
  277        * @return the next valid date/time
  278        */
  279       public Date getNextValidTimeAfter(Date date) {
  280           return getTimeAfter(date);
  281       }
  282       
  283       /**
  284        * Returns the next date/time <I>after</I> the given date/time which does
  285        * <I>not</I> satisfy the expression
  286        * 
  287        * @param date the date/time at which to begin the search for the next 
  288        *             invalid date/time
  289        * @return the next valid date/time
  290        */
  291       public Date getNextInvalidTimeAfter(Date date) {
  292           long difference = 1000;
  293           
  294           //move back to the nearest second so differences will be accurate
  295           Calendar adjustCal = Calendar.getInstance();
  296           adjustCal.setTime(date);
  297           adjustCal.set(Calendar.MILLISECOND, 0);
  298           Date lastDate = adjustCal.getTime();
  299           
  300           Date newDate = null;
  301           
  302           //TODO: (QUARTZ-481) IMPROVE THIS! The following is a BAD solution to this problem. Performance will be very bad here, depending on the cron expression. It is, however A solution.
  303           
  304           //keep getting the next included time until it's farther than one second
  305           // apart. At that point, lastDate is the last valid fire time. We return
  306           // the second immediately following it.
  307           while (difference == 1000) {
  308               newDate = getTimeAfter(lastDate);
  309               
  310               difference = newDate.getTime() - lastDate.getTime();
  311               
  312               if (difference == 1000) {
  313                   lastDate = newDate;
  314               }
  315           }
  316           
  317           return new Date(lastDate.getTime() + 1000);
  318       }
  319       
  320       /**
  321        * Returns the time zone for which this <code>CronExpression</code> 
  322        * will be resolved.
  323        */
  324       public TimeZone getTimeZone() {
  325           if (timeZone == null) {
  326               timeZone = TimeZone.getDefault();
  327           }
  328   
  329           return timeZone;
  330       }
  331   
  332       /**
  333        * Sets the time zone for which  this <code>CronExpression</code> 
  334        * will be resolved.
  335        */
  336       public void setTimeZone(TimeZone timeZone) {
  337           this.timeZone = timeZone;
  338       }
  339       
  340       /**
  341        * Returns the string representation of the <CODE>CronExpression</CODE>
  342        * 
  343        * @return a string representation of the <CODE>CronExpression</CODE>
  344        */
  345       public String toString() {
  346           return cronExpression;
  347       }
  348       
  349       /**
  350        * Indicates whether the specified cron expression can be parsed into a 
  351        * valid cron expression
  352        * 
  353        * @param cronExpression the expression to evaluate
  354        * @return a boolean indicating whether the given expression is a valid cron
  355        *         expression
  356        */
  357       public static boolean isValidExpression(String cronExpression) {
  358           
  359           try {
  360               new CronExpression(cronExpression);
  361           } catch (ParseException pe) {
  362               return false;
  363           }
  364           
  365           return true;
  366       }
  367       
  368       ////////////////////////////////////////////////////////////////////////////
  369       //
  370       // Expression Parsing Functions
  371       //
  372       ////////////////////////////////////////////////////////////////////////////
  373   
  374       protected void buildExpression(String expression) throws ParseException {
  375           expressionParsed = true;
  376   
  377           try {
  378   
  379               if (seconds == null) {
  380                   seconds = new TreeSet();
  381               }
  382               if (minutes == null) {
  383                   minutes = new TreeSet();
  384               }
  385               if (hours == null) {
  386                   hours = new TreeSet();
  387               }
  388               if (daysOfMonth == null) {
  389                   daysOfMonth = new TreeSet();
  390               }
  391               if (months == null) {
  392                   months = new TreeSet();
  393               }
  394               if (daysOfWeek == null) {
  395                   daysOfWeek = new TreeSet();
  396               }
  397               if (years == null) {
  398                   years = new TreeSet();
  399               }
  400   
  401               int exprOn = SECOND;
  402   
  403               StringTokenizer exprsTok = new StringTokenizer(expression, " \t",
  404                       false);
  405   
  406               while (exprsTok.hasMoreTokens() && exprOn <= YEAR) {
  407                   String expr = exprsTok.nextToken().trim();
  408                   StringTokenizer vTok = new StringTokenizer(expr, ",");
  409                   while (vTok.hasMoreTokens()) {
  410                       String v = vTok.nextToken();
  411                       storeExpressionVals(0, v, exprOn);
  412                   }
  413   
  414                   exprOn++;
  415               }
  416   
  417               if (exprOn <= DAY_OF_WEEK) {
  418                   throw new ParseException("Unexpected end of expression.",
  419                               expression.length());
  420               }
  421   
  422               if (exprOn <= YEAR) {
  423                   storeExpressionVals(0, "*", YEAR);
  424               }
  425   
  426           } catch (ParseException pe) {
  427               throw pe;
  428           } catch (Exception e) {
  429               throw new ParseException("Illegal cron expression format ("
  430                       + e.toString() + ")", 0);
  431           }
  432       }
  433   
  434       protected int storeExpressionVals(int pos, String s, int type)
  435           throws ParseException {
  436           
  437           int incr = 0;
  438           int i = skipWhiteSpace(pos, s);
  439           if (i >= s.length()) {
  440               return i;
  441           }
  442           char c = s.charAt(i);
  443           if ((c >= 'A') && (c <= 'Z') && (!s.equals("L")) && (!s.equals("LW"))) {
  444               String sub = s.substring(i, i + 3);
  445               int sval = -1;
  446               int eval = -1;
  447               if (type == MONTH) {
  448                   sval = getMonthNumber(sub) + 1;
  449                   if (sval < 0) {
  450                       throw new ParseException("Invalid Month value: '" + sub + "'", i);
  451                   }
  452                   if (s.length() > i + 3) {
  453                       c = s.charAt(i + 3);
  454                       if (c == '-') {
  455                           i += 4;
  456                           sub = s.substring(i, i + 3);
  457                           eval = getMonthNumber(sub) + 1;
  458                           if (eval < 0) {
  459                               throw new ParseException("Invalid Month value: '" + sub + "'", i);
  460                           }
  461                       }
  462                   }
  463               } else if (type == DAY_OF_WEEK) {
  464                   sval = getDayOfWeekNumber(sub);
  465                   if (sval < 0) {
  466                       throw new ParseException("Invalid Day-of-Week value: '"
  467                                   + sub + "'", i);
  468                   }
  469                   if (s.length() > i + 3) {
  470                       c = s.charAt(i + 3);
  471                       if (c == '-') {
  472                           i += 4;
  473                           sub = s.substring(i, i + 3);
  474                           eval = getDayOfWeekNumber(sub);
  475                           if (eval < 0) {
  476                               throw new ParseException(
  477                                       "Invalid Day-of-Week value: '" + sub
  478                                           + "'", i);
  479                           }
  480                           if (sval > eval) {
  481                               throw new ParseException(
  482                                       "Invalid Day-of-Week sequence: " + sval 
  483                                           + " > " + eval, i);
  484                           }
  485                       } else if (c == '#') {
  486                           try {
  487                               i += 4;
  488                               nthdayOfWeek = Integer.parseInt(s.substring(i));
  489                               if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
  490                                   throw new Exception();
  491                               }
  492                           } catch (Exception e) {
  493                               throw new ParseException(
  494                                       "A numeric value between 1 and 5 must follow the '#' option",
  495                                       i);
  496                           }
  497                       } else if (c == 'L') {
  498                           lastdayOfWeek = true;
  499                           i++;
  500                       }
  501                   }
  502   
  503               } else {
  504                   throw new ParseException(
  505                           "Illegal characters for this position: '" + sub + "'",
  506                           i);
  507               }
  508               if (eval != -1) {
  509                   incr = 1;
  510               }
  511               addToSet(sval, eval, incr, type);
  512               return (i + 3);
  513           }
  514   
  515           if (c == '?') {
  516               i++;
  517               if ((i + 1) < s.length() 
  518                       && (s.charAt(i) != ' ' && s.charAt(i + 1) != '\t')) {
  519                   throw new ParseException("Illegal character after '?': "
  520                               + s.charAt(i), i);
  521               }
  522               if (type != DAY_OF_WEEK && type != DAY_OF_MONTH) {
  523                   throw new ParseException(
  524                               "'?' can only be specfied for Day-of-Month or Day-of-Week.",
  525                               i);
  526               }
  527               if (type == DAY_OF_WEEK && !lastdayOfMonth) {
  528                   int val = ((Integer) daysOfMonth.last()).intValue();
  529                   if (val == NO_SPEC_INT) {
  530                       throw new ParseException(
  531                                   "'?' can only be specfied for Day-of-Month -OR- Day-of-Week.",
  532                                   i);
  533                   }
  534               }
  535   
  536               addToSet(NO_SPEC_INT, -1, 0, type);
  537               return i;
  538           }
  539   
  540           if (c == '*' || c == '/') {
  541               if (c == '*' && (i + 1) >= s.length()) {
  542                   addToSet(ALL_SPEC_INT, -1, incr, type);
  543                   return i + 1;
  544               } else if (c == '/'
  545                       && ((i + 1) >= s.length() || s.charAt(i + 1) == ' ' || s
  546                               .charAt(i + 1) == '\t')) { 
  547                   throw new ParseException("'/' must be followed by an integer.", i);
  548               } else if (c == '*') {
  549                   i++;
  550               }
  551               c = s.charAt(i);
  552               if (c == '/') { // is an increment specified?
  553                   i++;
  554                   if (i >= s.length()) {
  555                       throw new ParseException("Unexpected end of string.", i);
  556                   }
  557   
  558                   incr = getNumericValue(s, i);
  559   
  560                   i++;
  561                   if (incr > 10) {
  562                       i++;
  563                   }
  564                   if (incr > 59 && (type == SECOND || type == MINUTE)) {
  565                       throw new ParseException("Increment > 60 : " + incr, i);
  566                   } else if (incr > 23 && (type == HOUR)) { 
  567                       throw new ParseException("Increment > 24 : " + incr, i);
  568                   } else if (incr > 31 && (type == DAY_OF_MONTH)) { 
  569                       throw new ParseException("Increment > 31 : " + incr, i);
  570                   } else if (incr > 7 && (type == DAY_OF_WEEK)) { 
  571                       throw new ParseException("Increment > 7 : " + incr, i);
  572                   } else if (incr > 12 && (type == MONTH)) {
  573                       throw new ParseException("Increment > 12 : " + incr, i);
  574                   }
  575               } else {
  576                   incr = 1;
  577               }
  578   
  579               addToSet(ALL_SPEC_INT, -1, incr, type);
  580               return i;
  581           } else if (c == 'L') {
  582               i++;
  583               if (type == DAY_OF_MONTH) {
  584                   lastdayOfMonth = true;
  585               }
  586               if (type == DAY_OF_WEEK) {
  587                   addToSet(7, 7, 0, type);
  588               }
  589               if(type == DAY_OF_MONTH && s.length() > i) {
  590                   c = s.charAt(i);
  591                   if(c == 'W') {
  592                       nearestWeekday = true;
  593                       i++;
  594                   }
  595               }
  596               return i;
  597           } else if (c >= '0' && c <= '9') {
  598               int val = Integer.parseInt(String.valueOf(c));
  599               i++;
  600               if (i >= s.length()) {
  601                   addToSet(val, -1, -1, type);
  602               } else {
  603                   c = s.charAt(i);
  604                   if (c >= '0' && c <= '9') {
  605                       ValueSet vs = getValue(val, s, i);
  606                       val = vs.value;
  607                       i = vs.pos;
  608                   }
  609                   i = checkNext(i, s, val, type);
  610                   return i;
  611               }
  612           } else {
  613               throw new ParseException("Unexpected character: " + c, i);
  614           }
  615   
  616           return i;
  617       }
  618   
  619       protected int checkNext(int pos, String s, int val, int type)
  620           throws ParseException {
  621           
  622           int end = -1;
  623           int i = pos;
  624   
  625           if (i >= s.length()) {
  626               addToSet(val, end, -1, type);
  627               return i;
  628           }
  629   
  630           char c = s.charAt(pos);
  631   
  632           if (c == 'L') {
  633               if (type == DAY_OF_WEEK) {
  634                   lastdayOfWeek = true;
  635               } else {
  636                   throw new ParseException("'L' option is not valid here. (pos=" + i + ")", i);
  637               }
  638               TreeSet set = getSet(type);
  639               set.add(new Integer(val));
  640               i++;
  641               return i;
  642           }
  643           
  644           if (c == 'W') {
  645               if (type == DAY_OF_MONTH) {
  646                   nearestWeekday = true;
  647               } else {
  648                   throw new ParseException("'W' option is not valid here. (pos=" + i + ")", i);
  649               }
  650               TreeSet set = getSet(type);
  651               set.add(new Integer(val));
  652               i++;
  653               return i;
  654           }
  655   
  656           if (c == '#') {
  657               if (type != DAY_OF_WEEK) {
  658                   throw new ParseException("'#' option is not valid here. (pos=" + i + ")", i);
  659               }
  660               i++;
  661               try {
  662                   nthdayOfWeek = Integer.parseInt(s.substring(i));
  663                   if (nthdayOfWeek < 1 || nthdayOfWeek > 5) {
  664                       throw new Exception();
  665                   }
  666               } catch (Exception e) {
  667                   throw new ParseException(
  668                           "A numeric value between 1 and 5 must follow the '#' option",
  669                           i);
  670               }
  671   
  672               TreeSet set = getSet(type);
  673               set.add(new Integer(val));
  674               i++;
  675               return i;
  676           }
  677   
  678           if (c == '-') {
  679               i++;
  680               c = s.charAt(i);
  681               int v = Integer.parseInt(String.valueOf(c));
  682               end = v;
  683               i++;
  684               if (i >= s.length()) {
  685                   addToSet(val, end, 1, type);
  686                   return i;
  687               }
  688               c = s.charAt(i);
  689               if (c >= '0' && c <= '9') {
  690                   ValueSet vs = getValue(v, s, i);
  691                   int v1 = vs.value;
  692                   end = v1;
  693                   i = vs.pos;
  694               }
  695               if (i < s.length() && ((c = s.charAt(i)) == '/')) {
  696                   i++;
  697                   c = s.charAt(i);
  698                   int v2 = Integer.parseInt(String.valueOf(c));
  699                   i++;
  700                   if (i >= s.length()) {
  701                       addToSet(val, end, v2, type);
  702                       return i;
  703                   }
  704                   c = s.charAt(i);
  705                   if (c >= '0' && c <= '9') {
  706                       ValueSet vs = getValue(v2, s, i);
  707                       int v3 = vs.value;
  708                       addToSet(val, end, v3, type);
  709                       i = vs.pos;
  710                       return i;
  711                   } else {
  712                       addToSet(val, end, v2, type);
  713                       return i;
  714                   }
  715               } else {
  716                   addToSet(val, end, 1, type);
  717                   return i;
  718               }
  719           }
  720   
  721           if (c == '/') {
  722               i++;
  723               c = s.charAt(i);
  724               int v2 = Integer.parseInt(String.valueOf(c));
  725               i++;
  726               if (i >= s.length()) {
  727                   addToSet(val, end, v2, type);
  728                   return i;
  729               }
  730               c = s.charAt(i);
  731               if (c >= '0' && c <= '9') {
  732                   ValueSet vs = getValue(v2, s, i);
  733                   int v3 = vs.value;
  734                   addToSet(val, end, v3, type);
  735                   i = vs.pos;
  736                   return i;
  737               } else {
  738                   throw new ParseException("Unexpected character '" + c + "' after '/'", i);
  739               }
  740           }
  741   
  742           addToSet(val, end, 0, type);
  743           i++;
  744           return i;
  745       }
  746   
  747       public String getCronExpression() {
  748           return cronExpression;
  749       }
  750       
  751       public String getExpressionSummary() {
  752           StringBuffer buf = new StringBuffer();
  753   
  754           buf.append("seconds: ");
  755           buf.append(getExpressionSetSummary(seconds));
  756           buf.append("\n");
  757           buf.append("minutes: ");
  758           buf.append(getExpressionSetSummary(minutes));
  759           buf.append("\n");
  760           buf.append("hours: ");
  761           buf.append(getExpressionSetSummary(hours));
  762           buf.append("\n");
  763           buf.append("daysOfMonth: ");
  764           buf.append(getExpressionSetSummary(daysOfMonth));
  765           buf.append("\n");
  766           buf.append("months: ");
  767           buf.append(getExpressionSetSummary(months));
  768           buf.append("\n");
  769           buf.append("daysOfWeek: ");
  770           buf.append(getExpressionSetSummary(daysOfWeek));
  771           buf.append("\n");
  772           buf.append("lastdayOfWeek: ");
  773           buf.append(lastdayOfWeek);
  774           buf.append("\n");
  775           buf.append("nearestWeekday: ");
  776           buf.append(nearestWeekday);
  777           buf.append("\n");
  778           buf.append("NthDayOfWeek: ");
  779           buf.append(nthdayOfWeek);
  780           buf.append("\n");
  781           buf.append("lastdayOfMonth: ");
  782           buf.append(lastdayOfMonth);
  783           buf.append("\n");
  784           buf.append("years: ");
  785           buf.append(getExpressionSetSummary(years));
  786           buf.append("\n");
  787   
  788           return buf.toString();
  789       }
  790   
  791       protected String getExpressionSetSummary(java.util.Set set) {
  792   
  793           if (set.contains(NO_SPEC)) {
  794               return "?";
  795           }
  796           if (set.contains(ALL_SPEC)) {
  797               return "*";
  798           }
  799   
  800           StringBuffer buf = new StringBuffer();
  801   
  802           Iterator itr = set.iterator();
  803           boolean first = true;
  804           while (itr.hasNext()) {
  805               Integer iVal = (Integer) itr.next();
  806               String val = iVal.toString();
  807               if (!first) {
  808                   buf.append(",");
  809               }
  810               buf.append(val);
  811               first = false;
  812           }
  813   
  814           return buf.toString();
  815       }
  816   
  817       protected String getExpressionSetSummary(java.util.ArrayList list) {
  818   
  819           if (list.contains(NO_SPEC)) {
  820               return "?";
  821           }
  822           if (list.contains(ALL_SPEC)) {
  823               return "*";
  824           }
  825   
  826           StringBuffer buf = new StringBuffer();
  827   
  828           Iterator itr = list.iterator();
  829           boolean first = true;
  830           while (itr.hasNext()) {
  831               Integer iVal = (Integer) itr.next();
  832               String val = iVal.toString();
  833               if (!first) {
  834                   buf.append(",");
  835               }
  836               buf.append(val);
  837               first = false;
  838           }
  839   
  840           return buf.toString();
  841       }
  842   
  843       protected int skipWhiteSpace(int i, String s) {
  844           for (; i < s.length() && (s.charAt(i) == ' ' || s.charAt(i) == '\t'); i++) {
  845               ;
  846           }
  847   
  848           return i;
  849       }
  850   
  851       protected int findNextWhiteSpace(int i, String s) {
  852           for (; i < s.length() && (s.charAt(i) != ' ' || s.charAt(i) != '\t'); i++) {
  853               ;
  854           }
  855   
  856           return i;
  857       }
  858   
  859       protected void addToSet(int val, int end, int incr, int type)
  860           throws ParseException {
  861           
  862           TreeSet set = getSet(type);
  863   
  864           if (type == SECOND || type == MINUTE) {
  865               if ((val < 0 || val > 59 || end > 59) && (val != ALL_SPEC_INT)) {
  866                   throw new ParseException(
  867                           "Minute and Second values must be between 0 and 59",
  868                           -1);
  869               }
  870           } else if (type == HOUR) {
  871               if ((val < 0 || val > 23 || end > 23) && (val != ALL_SPEC_INT)) {
  872                   throw new ParseException(
  873                           "Hour values must be between 0 and 23", -1);
  874               }
  875           } else if (type == DAY_OF_MONTH) {
  876               if ((val < 1 || val > 31 || end > 31) && (val != ALL_SPEC_INT) 
  877                       && (val != NO_SPEC_INT)) {
  878                   throw new ParseException(
  879                           "Day of month values must be between 1 and 31", -1);
  880               }
  881           } else if (type == MONTH) {
  882               if ((val < 1 || val > 12 || end > 12) && (val != ALL_SPEC_INT)) {
  883                   throw new ParseException(
  884                           "Month values must be between 1 and 12", -1);
  885               }
  886           } else if (type == DAY_OF_WEEK) {
  887               if ((val == 0 || val > 7 || end > 7) && (val != ALL_SPEC_INT)
  888                       && (val != NO_SPEC_INT)) {
  889                   throw new ParseException(
  890                           "Day-of-Week values must be between 1 and 7", -1);
  891               }
  892           }
  893   
  894           if ((incr == 0 || incr == -1) && val != ALL_SPEC_INT) {
  895               if (val != -1) {
  896                   set.add(new Integer(val));
  897               } else {
  898                   set.add(NO_SPEC);
  899               }
  900               
  901               return;
  902           }
  903   
  904           int startAt = val;
  905           int stopAt = end;
  906   
  907           if (val == ALL_SPEC_INT && incr <= 0) {
  908               incr = 1;
  909               set.add(ALL_SPEC); // put in a marker, but also fill values
  910           }
  911   
  912           if (type == SECOND || type == MINUTE) {
  913               if (stopAt == -1) {
  914                   stopAt = 59;
  915               }
  916               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  917                   startAt = 0;
  918               }
  919           } else if (type == HOUR) {
  920               if (stopAt == -1) {
  921                   stopAt = 23;
  922               }
  923               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  924                   startAt = 0;
  925               }
  926           } else if (type == DAY_OF_MONTH) {
  927               if (stopAt == -1) {
  928                   stopAt = 31;
  929               }
  930               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  931                   startAt = 1;
  932               }
  933           } else if (type == MONTH) {
  934               if (stopAt == -1) {
  935                   stopAt = 12;
  936               }
  937               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  938                   startAt = 1;
  939               }
  940           } else if (type == DAY_OF_WEEK) {
  941               if (stopAt == -1) {
  942                   stopAt = 7;
  943               }
  944               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  945                   startAt = 1;
  946               }
  947           } else if (type == YEAR) {
  948               if (stopAt == -1) {
  949                   stopAt = 2099;
  950               }
  951               if (startAt == -1 || startAt == ALL_SPEC_INT) {
  952                   startAt = 1970;
  953               }
  954           }
  955   
  956           for (int i = startAt; i <= stopAt; i += incr) {
  957               set.add(new Integer(i));
  958           }
  959       }
  960   
  961       protected TreeSet getSet(int type) {
  962           switch (type) {
  963               case SECOND:
  964                   return seconds;
  965               case MINUTE:
  966                   return minutes;
  967               case HOUR:
  968                   return hours;
  969               case DAY_OF_MONTH:
  970                   return daysOfMonth;
  971               case MONTH:
  972                   return months;
  973               case DAY_OF_WEEK:
  974                   return daysOfWeek;
  975               case YEAR:
  976                   return years;
  977               default:
  978                   return null;
  979           }
  980       }
  981   
  982       protected ValueSet getValue(int v, String s, int i) {
  983           char c = s.charAt(i);
  984           String s1 = String.valueOf(v);
  985           while (c >= '0' && c <= '9') {
  986               s1 += c;
  987               i++;
  988               if (i >= s.length()) {
  989                   break;
  990               }
  991               c = s.charAt(i);
  992           }
  993           ValueSet val = new ValueSet();
  994           
  995           val.pos = (i < s.length()) ? i : i + 1;
  996           val.value = Integer.parseInt(s1);
  997           return val;
  998       }
  999   
 1000       protected int getNumericValue(String s, int i) {
 1001           int endOfVal = findNextWhiteSpace(i, s);
 1002           String val = s.substring(i, endOfVal);
 1003           return Integer.parseInt(val);
 1004       }
 1005   
 1006       protected int getMonthNumber(String s) {
 1007           Integer integer = (Integer) monthMap.get(s);
 1008   
 1009           if (integer == null) {
 1010               return -1;
 1011           }
 1012   
 1013           return integer.intValue();
 1014       }
 1015   
 1016       protected int getDayOfWeekNumber(String s) {
 1017           Integer integer = (Integer) dayMap.get(s);
 1018   
 1019           if (integer == null) {
 1020               return -1;
 1021           }
 1022   
 1023           return integer.intValue();
 1024       }
 1025   
 1026       ////////////////////////////////////////////////////////////////////////////
 1027       //
 1028       // Computation Functions
 1029       //
 1030       ////////////////////////////////////////////////////////////////////////////
 1031   
 1032       protected Date getTimeAfter(Date afterTime) {
 1033   
 1034           Calendar cl = Calendar.getInstance(getTimeZone());
 1035   
 1036           // move ahead one second, since we're computing the time *after* the
 1037           // given time
 1038           afterTime = new Date(afterTime.getTime() + 1000);
 1039           // CronTrigger does not deal with milliseconds
 1040           cl.setTime(afterTime);
 1041           cl.set(Calendar.MILLISECOND, 0);
 1042   
 1043           boolean gotOne = false;
 1044           // loop until we've computed the next time, or we've past the endTime
 1045           while (!gotOne) {
 1046   
 1047               //if (endTime != null && cl.getTime().after(endTime)) return null;
 1048               if(cl.get(Calendar.YEAR) > 2999) // prevent endless loop...
 1049                   return null;
 1050   
 1051               SortedSet st = null;
 1052               int t = 0;
 1053   
 1054               int sec = cl.get(Calendar.SECOND);
 1055               int min = cl.get(Calendar.MINUTE);
 1056   
 1057               // get second.................................................
 1058               st = seconds.tailSet(new Integer(sec));
 1059               if (st != null && st.size() != 0) {
 1060                   sec = ((Integer) st.first()).intValue();
 1061               } else {
 1062                   sec = ((Integer) seconds.first()).intValue();
 1063                   min++;
 1064                   cl.set(Calendar.MINUTE, min);
 1065               }
 1066               cl.set(Calendar.SECOND, sec);
 1067   
 1068               min = cl.get(Calendar.MINUTE);
 1069               int hr = cl.get(Calendar.HOUR_OF_DAY);
 1070               t = -1;
 1071   
 1072               // get minute.................................................
 1073               st = minutes.tailSet(new Integer(min));
 1074               if (st != null && st.size() != 0) {
 1075                   t = min;
 1076                   min = ((Integer) st.first()).intValue();
 1077               } else {
 1078                   min = ((Integer) minutes.first()).intValue();
 1079                   hr++;
 1080               }
 1081               if (min != t) {
 1082                   cl.set(Calendar.SECOND, 0);
 1083                   cl.set(Calendar.MINUTE, min);
 1084                   setCalendarHour(cl, hr);
 1085                   continue;
 1086               }
 1087               cl.set(Calendar.MINUTE, min);
 1088   
 1089               hr = cl.get(Calendar.HOUR_OF_DAY);
 1090               int day = cl.get(Calendar.DAY_OF_MONTH);
 1091               t = -1;
 1092   
 1093               // get hour...................................................
 1094               st = hours.tailSet(new Integer(hr));
 1095               if (st != null && st.size() != 0) {
 1096                   t = hr;
 1097                   hr = ((Integer) st.first()).intValue();
 1098               } else {
 1099                   hr = ((Integer) hours.first()).intValue();
 1100                   day++;
 1101               }
 1102               if (hr != t) {
 1103                   cl.set(Calendar.SECOND, 0);
 1104                   cl.set(Calendar.MINUTE, 0);
 1105                   cl.set(Calendar.DAY_OF_MONTH, day);
 1106                   setCalendarHour(cl, hr);
 1107                   continue;
 1108               }
 1109               cl.set(Calendar.HOUR_OF_DAY, hr);
 1110   
 1111               day = cl.get(Calendar.DAY_OF_MONTH);
 1112               int mon = cl.get(Calendar.MONTH) + 1;
 1113               // '+ 1' because calendar is 0-based for this field, and we are
 1114               // 1-based
 1115               t = -1;
 1116               int tmon = mon;
 1117               
 1118               // get day...................................................
 1119               boolean dayOfMSpec = !daysOfMonth.contains(NO_SPEC);
 1120               boolean dayOfWSpec = !daysOfWeek.contains(NO_SPEC);
 1121               if (dayOfMSpec && !dayOfWSpec) { // get day by day of month rule
 1122                   st = daysOfMonth.tailSet(new Integer(day));
 1123                   if (lastdayOfMonth) {
 1124                       if(!nearestWeekday) {
 1125                           t = day;
 1126                           day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1127                       } else {
 1128                           t = day;
 1129                           day = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1130                           
 1131                           java.util.Calendar tcal = java.util.Calendar.getInstance();
 1132                           tcal.set(Calendar.SECOND, 0);
 1133                           tcal.set(Calendar.MINUTE, 0);
 1134                           tcal.set(Calendar.HOUR_OF_DAY, 0);
 1135                           tcal.set(Calendar.DAY_OF_MONTH, day);
 1136                           tcal.set(Calendar.MONTH, mon - 1);
 1137                           tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
 1138                           
 1139                           int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1140                           int dow = tcal.get(Calendar.DAY_OF_WEEK);
 1141   
 1142                           if(dow == Calendar.SATURDAY && day == 1) {
 1143                               day += 2;
 1144                           } else if(dow == Calendar.SATURDAY) {
 1145                               day -= 1;
 1146                           } else if(dow == Calendar.SUNDAY && day == ldom) { 
 1147                               day -= 2;
 1148                           } else if(dow == Calendar.SUNDAY) { 
 1149                               day += 1;
 1150                           }
 1151                       
 1152                           tcal.set(Calendar.SECOND, sec);
 1153                           tcal.set(Calendar.MINUTE, min);
 1154                           tcal.set(Calendar.HOUR_OF_DAY, hr);
 1155                           tcal.set(Calendar.DAY_OF_MONTH, day);
 1156                           tcal.set(Calendar.MONTH, mon - 1);
 1157                           Date nTime = tcal.getTime();
 1158                           if(nTime.before(afterTime)) {
 1159                               day = 1;
 1160                               mon++;
 1161                           }
 1162                       }
 1163                   } else if(nearestWeekday) {
 1164                       t = day;
 1165                       day = ((Integer) daysOfMonth.first()).intValue();
 1166   
 1167                       java.util.Calendar tcal = java.util.Calendar.getInstance();
 1168                       tcal.set(Calendar.SECOND, 0);
 1169                       tcal.set(Calendar.MINUTE, 0);
 1170                       tcal.set(Calendar.HOUR_OF_DAY, 0);
 1171                       tcal.set(Calendar.DAY_OF_MONTH, day);
 1172                       tcal.set(Calendar.MONTH, mon - 1);
 1173                       tcal.set(Calendar.YEAR, cl.get(Calendar.YEAR));
 1174                       
 1175                       int ldom = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1176                       int dow = tcal.get(Calendar.DAY_OF_WEEK);
 1177   
 1178                       if(dow == Calendar.SATURDAY && day == 1) {
 1179                           day += 2;
 1180                       } else if(dow == Calendar.SATURDAY) {
 1181                           day -= 1;
 1182                       } else if(dow == Calendar.SUNDAY && day == ldom) { 
 1183                           day -= 2;
 1184                       } else if(dow == Calendar.SUNDAY) { 
 1185                           day += 1;
 1186                       }
 1187                           
 1188                   
 1189                       tcal.set(Calendar.SECOND, sec);
 1190                       tcal.set(Calendar.MINUTE, min);
 1191                       tcal.set(Calendar.HOUR_OF_DAY, hr);
 1192                       tcal.set(Calendar.DAY_OF_MONTH, day);
 1193                       tcal.set(Calendar.MONTH, mon - 1);
 1194                       Date nTime = tcal.getTime();
 1195                       if(nTime.before(afterTime)) {
 1196                           day = ((Integer) daysOfMonth.first()).intValue();;
 1197                           mon++;
 1198                       }
 1199                   } else if (st != null && st.size() != 0) {
 1200                       t = day;
 1201                       day = ((Integer) st.first()).intValue();
 1202                   } else {
 1203                       day = ((Integer) daysOfMonth.first()).intValue();
 1204                       mon++;
 1205                   }
 1206                   
 1207                   if (day != t || mon != tmon) {
 1208                       cl.set(Calendar.SECOND, 0);
 1209                       cl.set(Calendar.MINUTE, 0);
 1210                       cl.set(Calendar.HOUR_OF_DAY, 0);
 1211                       cl.set(Calendar.DAY_OF_MONTH, day);
 1212                       cl.set(Calendar.MONTH, mon - 1);
 1213                       // '- 1' because calendar is 0-based for this field, and we
 1214                       // are 1-based
 1215                       continue;
 1216                   }
 1217               } else if (dayOfWSpec && !dayOfMSpec) { // get day by day of week rule
 1218                   if (lastdayOfWeek) { // are we looking for the last XXX day of
 1219                       // the month?
 1220                       int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
 1221                       // d-o-w
 1222                       int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
 1223                       int daysToAdd = 0;
 1224                       if (cDow < dow) {
 1225                           daysToAdd = dow - cDow;
 1226                       }
 1227                       if (cDow > dow) {
 1228                           daysToAdd = dow + (7 - cDow);
 1229                       }
 1230   
 1231                       int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1232   
 1233                       if (day + daysToAdd > lDay) { // did we already miss the
 1234                           // last one?
 1235                           cl.set(Calendar.SECOND, 0);
 1236                           cl.set(Calendar.MINUTE, 0);
 1237                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1238                           cl.set(Calendar.DAY_OF_MONTH, 1);
 1239                           cl.set(Calendar.MONTH, mon);
 1240                           // no '- 1' here because we are promoting the month
 1241                           continue;
 1242                       }
 1243   
 1244                       // find date of last occurance of this day in this month...
 1245                       while ((day + daysToAdd + 7) <= lDay) {
 1246                           daysToAdd += 7;
 1247                       }
 1248   
 1249                       day += daysToAdd;
 1250   
 1251                       if (daysToAdd > 0) {
 1252                           cl.set(Calendar.SECOND, 0);
 1253                           cl.set(Calendar.MINUTE, 0);
 1254                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1255                           cl.set(Calendar.DAY_OF_MONTH, day);
 1256                           cl.set(Calendar.MONTH, mon - 1);
 1257                           // '- 1' here because we are not promoting the month
 1258                           continue;
 1259                       }
 1260   
 1261                   } else if (nthdayOfWeek != 0) {
 1262                       // are we looking for the Nth XXX day in the month?
 1263                       int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
 1264                       // d-o-w
 1265                       int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
 1266                       int daysToAdd = 0;
 1267                       if (cDow < dow) {
 1268                           daysToAdd = dow - cDow;
 1269                       } else if (cDow > dow) {
 1270                           daysToAdd = dow + (7 - cDow);
 1271                       }
 1272   
 1273                       boolean dayShifted = false;
 1274                       if (daysToAdd > 0) {
 1275                           dayShifted = true;
 1276                       }
 1277   
 1278                       day += daysToAdd;
 1279                       int weekOfMonth = day / 7;
 1280                       if (day % 7 > 0) {
 1281                           weekOfMonth++;
 1282                       }
 1283   
 1284                       daysToAdd = (nthdayOfWeek - weekOfMonth) * 7;
 1285                       day += daysToAdd;
 1286                       if (daysToAdd < 0
 1287                               || day > getLastDayOfMonth(mon, cl
 1288                                       .get(Calendar.YEAR))) {
 1289                           cl.set(Calendar.SECOND, 0);
 1290                           cl.set(Calendar.MINUTE, 0);
 1291                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1292                           cl.set(Calendar.DAY_OF_MONTH, 1);
 1293                           cl.set(Calendar.MONTH, mon);
 1294                           // no '- 1' here because we are promoting the month
 1295                           continue;
 1296                       } else if (daysToAdd > 0 || dayShifted) {
 1297                           cl.set(Calendar.SECOND, 0);
 1298                           cl.set(Calendar.MINUTE, 0);
 1299                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1300                           cl.set(Calendar.DAY_OF_MONTH, day);
 1301                           cl.set(Calendar.MONTH, mon - 1);
 1302                           // '- 1' here because we are NOT promoting the month
 1303                           continue;
 1304                       }
 1305                   } else {
 1306                       int cDow = cl.get(Calendar.DAY_OF_WEEK); // current d-o-w
 1307                       int dow = ((Integer) daysOfWeek.first()).intValue(); // desired
 1308                       // d-o-w
 1309                       st = daysOfWeek.tailSet(new Integer(cDow));
 1310                       if (st != null && st.size() > 0) {
 1311                           dow = ((Integer) st.first()).intValue();
 1312                       }
 1313   
 1314                       int daysToAdd = 0;
 1315                       if (cDow < dow) {
 1316                           daysToAdd = dow - cDow;
 1317                       }
 1318                       if (cDow > dow) {
 1319                           daysToAdd = dow + (7 - cDow);
 1320                       }
 1321   
 1322                       int lDay = getLastDayOfMonth(mon, cl.get(Calendar.YEAR));
 1323   
 1324                       if (day + daysToAdd > lDay) { // will we pass the end of
 1325                           // the month?
 1326                           cl.set(Calendar.SECOND, 0);
 1327                           cl.set(Calendar.MINUTE, 0);
 1328                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1329                           cl.set(Calendar.DAY_OF_MONTH, 1);
 1330                           cl.set(Calendar.MONTH, mon);
 1331                           // no '- 1' here because we are promoting the month
 1332                           continue;
 1333                       } else if (daysToAdd > 0) { // are we swithing days?
 1334                           cl.set(Calendar.SECOND, 0);
 1335                           cl.set(Calendar.MINUTE, 0);
 1336                           cl.set(Calendar.HOUR_OF_DAY, 0);
 1337                           cl.set(Calendar.DAY_OF_MONTH, day + daysToAdd);
 1338                           cl.set(Calendar.MONTH, mon - 1);
 1339                           // '- 1' because calendar is 0-based for this field,
 1340                           // and we are 1-based
 1341                           continue;
 1342                       }
 1343                   }
 1344               } else { // dayOfWSpec && !dayOfMSpec
 1345                   throw new UnsupportedOperationException(
 1346                           "Support for specifying both a day-of-week AND a day-of-month parameter is not implemented.");
 1347                   // TODO:
 1348               }
 1349               cl.set(Calendar.DAY_OF_MONTH, day);
 1350   
 1351               mon = cl.get(Calendar.MONTH) + 1;
 1352               // '+ 1' because calendar is 0-based for this field, and we are
 1353               // 1-based
 1354               int year = cl.get(Calendar.YEAR);
 1355               t = -1;
 1356   
 1357               // test for expressions that never generate a valid fire date,
 1358               // but keep looping...
 1359               if (year > 2099) {
 1360                   return null;
 1361               }
 1362   
 1363               // get month...................................................
 1364               st = months.tailSet(new Integer(mon));
 1365               if (st != null && st.size() != 0) {
 1366                   t = mon;
 1367                   mon = ((Integer) st.first()).intValue();
 1368               } else {
 1369                   mon = ((Integer) months.first()).intValue();
 1370                   year++;
 1371               }
 1372               if (mon != t) {
 1373                   cl.set(Calendar.SECOND, 0);
 1374                   cl.set(Calendar.MINUTE, 0);
 1375                   cl.set(Calendar.HOUR_OF_DAY, 0);
 1376                   cl.set(Calendar.DAY_OF_MONTH, 1);
 1377                   cl.set(Calendar.MONTH, mon - 1);
 1378                   // '- 1' because calendar is 0-based for this field, and we are
 1379                   // 1-based
 1380                   cl.set(Calendar.YEAR, year);
 1381                   continue;
 1382               }
 1383               cl.set(Calendar.MONTH, mon - 1);
 1384               // '- 1' because calendar is 0-based for this field, and we are
 1385               // 1-based
 1386   
 1387               year = cl.get(Calendar.YEAR);
 1388               t = -1;
 1389   
 1390               // get year...................................................
 1391               st = years.tailSet(new Integer(year));
 1392               if (st != null && st.size() != 0) {
 1393                   t = year;
 1394                   year = ((Integer) st.first()).intValue();
 1395               } else {
 1396                   return null; // ran out of years...
 1397               }
 1398   
 1399               if (year != t) {
 1400                   cl.set(Calendar.SECOND, 0);
 1401                   cl.set(Calendar.MINUTE, 0);
 1402                   cl.set(Calendar.HOUR_OF_DAY, 0);
 1403                   cl.set(Calendar.DAY_OF_MONTH, 1);
 1404                   cl.set(Calendar.MONTH, 0);
 1405                   // '- 1' because calendar is 0-based for this field, and we are
 1406                   // 1-based
 1407                   cl.set(Calendar.YEAR, year);
 1408                   continue;
 1409               }
 1410               cl.set(Calendar.YEAR, year);
 1411   
 1412               gotOne = true;
 1413           } // while( !done )
 1414   
 1415           return cl.getTime();
 1416       }
 1417   
 1418       /**
 1419        * Advance the calendar to the particular hour paying particular attention
 1420        * to daylight saving problems.
 1421        * 
 1422        * @param cal
 1423        * @param hour
 1424        */
 1425       protected void setCalendarHour(Calendar cal, int hour) {
 1426           cal.set(java.util.Calendar.HOUR_OF_DAY, hour);
 1427           if (cal.get(java.util.Calendar.HOUR_OF_DAY) != hour && hour != 24) {
 1428               cal.set(java.util.Calendar.HOUR_OF_DAY, hour + 1);
 1429           }
 1430       }
 1431   
 1432       /**
 1433        * NOT YET IMPLEMENTED: Returns the time before the given time
 1434        * that the <code>CronExpression</code> matches.
 1435        */ 
 1436       protected Date getTimeBefore(Date endTime) { 
 1437           // TODO: implement QUARTZ-423
 1438           return null;
 1439       }
 1440   
 1441       /**
 1442        * NOT YET IMPLEMENTED: Returns the final time that the 
 1443        * <code>CronExpression</code> will match.
 1444        */
 1445       public Date getFinalFireTime() {
 1446           // TODO: implement QUARTZ-423
 1447           return null;
 1448       }
 1449       
 1450       protected boolean isLeapYear(int year) {
 1451           return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
 1452       }
 1453   
 1454       protected int getLastDayOfMonth(int monthNum, int year) {
 1455   
 1456           switch (monthNum) {
 1457               case 1:
 1458                   return 31;
 1459               case 2:
 1460                   return (isLeapYear(year)) ? 29 : 28;
 1461               case 3:
 1462                   return 31;
 1463               case 4:
 1464                   return 30;
 1465               case 5:
 1466                   return 31;
 1467               case 6:
 1468                   return 30;
 1469               case 7:
 1470                   return 31;
 1471               case 8:
 1472                   return 31;
 1473               case 9:
 1474                   return 30;
 1475               case 10:
 1476                   return 31;
 1477               case 11:
 1478                   return 30;
 1479               case 12:
 1480                   return 31;
 1481               default:
 1482                   throw new IllegalArgumentException("Illegal month number: "
 1483                           + monthNum);
 1484           }
 1485       }
 1486       
 1487   
 1488       private void readObject(java.io.ObjectInputStream stream)
 1489           throws java.io.IOException, ClassNotFoundException {
 1490           
 1491           stream.defaultReadObject();
 1492           try {
 1493               buildExpression(cronExpression);
 1494           } catch (Exception ignore) {
 1495           } // never happens
 1496       }    
 1497       
 1498       public Object clone() {
 1499           CronExpression copy = null;
 1500           try {
 1501               copy = new CronExpression(getCronExpression());
 1502               copy.setTimeZone(getTimeZone());
 1503           } catch (ParseException ex) { // never happens since the source is valid...
 1504               throw new IncompatibleClassChangeError("Not Cloneable.");
 1505           }
 1506           return copy;
 1507       }        
 1508   }
 1509   
 1510   class ValueSet {
 1511       public int value;
 1512   
 1513       public int pos;
 1514   }

Home » quartz-1.6.0 » org » quartz » [javadoc | source]