PageRenderTime 928ms CodeModel.GetById 151ms app.highlight 600ms RepoModel.GetById 161ms app.codeStats 1ms

/modules/pvm/src/test/java/org/jbpm/pvm/internal/jobexecutor/cron/CronExpression.java

https://bitbucket.org/visionest/jbpm4
Java | 1514 lines | 1072 code | 157 blank | 285 comment | 444 complexity | 9110d55c10ce6d1f0c7a6161ff7201c6 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1package org.jbpm.pvm.internal.jobexecutor.cron;
   2
   3import java.io.Serializable;
   4import java.text.ParseException;
   5import java.util.Calendar;
   6import java.util.Date;
   7import java.util.HashMap;
   8import java.util.Iterator;
   9import java.util.Locale;
  10import java.util.Map;
  11import java.util.SortedSet;
  12import java.util.StringTokenizer;
  13import java.util.TimeZone;
  14import 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 */
 172public 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    	  StringBuilder summary = new StringBuilder();
 753
 754        summary.append("seconds: ");
 755        summary.append(getExpressionSetSummary(seconds));
 756        summary.append("\n");
 757        summary.append("minutes: ");
 758        summary.append(getExpressionSetSummary(minutes));
 759        summary.append("\n");
 760        summary.append("hours: ");
 761        summary.append(getExpressionSetSummary(hours));
 762        summary.append("\n");
 763        summary.append("daysOfMonth: ");
 764        summary.append(getExpressionSetSummary(daysOfMonth));
 765        summary.append("\n");
 766        summary.append("months: ");
 767        summary.append(getExpressionSetSummary(months));
 768        summary.append("\n");
 769        summary.append("daysOfWeek: ");
 770        summary.append(getExpressionSetSummary(daysOfWeek));
 771        summary.append("\n");
 772        summary.append("lastdayOfWeek: ");
 773        summary.append(lastdayOfWeek);
 774        summary.append("\n");
 775        summary.append("nearestWeekday: ");
 776        summary.append(nearestWeekday);
 777        summary.append("\n");
 778        summary.append("NthDayOfWeek: ");
 779        summary.append(nthdayOfWeek);
 780        summary.append("\n");
 781        summary.append("lastdayOfMonth: ");
 782        summary.append(lastdayOfMonth);
 783        summary.append("\n");
 784        summary.append("years: ");
 785        summary.append(getExpressionSetSummary(years));
 786        summary.append("\n");
 787
 788        return summary.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        StringBuilder summary = new StringBuilder();
 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                summary.append(",");
 809            }
 810            summary.append(val);
 811            first = false;
 812        }
 813
 814        return summary.toString();
 815    }
 816
 817    protected String getExpressionSetSummary(java.util.List list) {
 818
 819        if (list.contains(NO_SPEC)) {
 820            return "?";
 821        }
 822        if (list.contains(ALL_SPEC)) {
 823            return "*";
 824        }
 825
 826        StringBuilder summary = new StringBuilder();
 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                summary.append(",");
 835            }
 836            summary.append(val);
 837            first = false;
 838        }
 839
 840        return summary.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(Ca

Large files files are truncated, but you can click here to view the full file