Clover coverage report - Ledge Components - SNAPSHOT
Coverage timestamp: Fri Nov 17 2006 05:13:20 CET
file stats: LOC: 530   Methods: 14
NCLOC: 372   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
CronSchedule.java 8.7% 21.1% 78.6% 20.4%
coverage coverage
 1    //
 2    //Copyright (c) 2003, Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
 3    //All rights reserved.
 4    //
 5    //Redistribution and use in source and binary forms, with or without modification,
 6    //are permitted provided that the following conditions are met:
 7    //
 8    //* Redistributions of source code must retain the above copyright notice,
 9    //this list of conditions and the following disclaimer.
 10    //* Redistributions in binary form must reproduce the above copyright notice,
 11    //this list of conditions and the following disclaimer in the documentation
 12    //and/or other materials provided with the distribution.
 13    //* Neither the name of the Caltha - Gajda, Krzewski, Mach, Potempski Sp.J.
 14    //nor the names of its contributors may be used to endorse or promote products
 15    //derived from this software without specific prior written permission.
 16    //
 17    //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 18    //AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 19    //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 20    //IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 21    //INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 22    //BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 23    //OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 24    //WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 25    //ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 26    //POSSIBILITY OF SUCH DAMAGE.
 27    //
 28   
 29    package org.objectledge.scheduler;
 30   
 31    import java.io.StringReader;
 32    import java.util.Calendar;
 33    import java.util.Date;
 34    import java.util.GregorianCalendar;
 35   
 36    import org.objectledge.i18n.I18n;
 37    import org.objectledge.scheduler.cron.CronParser;
 38    import org.objectledge.scheduler.cron.ParseException;
 39    import org.objectledge.scheduler.cron.Token;
 40    import org.objectledge.scheduler.cron.ValueOutOfRangeException;
 41   
 42    /**
 43    * A schedule that follows the configuration syntax of Unix cron utility.
 44    *
 45    * <p>The configuration grammar:</p>
 46    * <pre>
 47    * schedule_config := ( ( minute hour day month weekday ) | special )
 48    * special := ( "@reboot" | "@yearly" | "@annually" | "@monthly" | "@weekly" |
 49    * "@daily" | "@midnight" | "@hourly" )
 50    * minute := value_spec
 51    * hour := value_spec
 52    * day := value_spec
 53    * month := ( value_spec | month_name )
 54    * weekday := ( value_spec | weekday_name )
 55    * month_name := ( "jan" | "feb" | "mar" | "apr" | "may" | "jun" |
 56    * "jul" | "aug" | "sep" | "oct" | "nov" | "dec" )
 57    * weekday_name := ( "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun" )
 58    * value_spec := ( "*" | list_of_values )
 59    * list_of_values := ( value_or_range ( "," value_or_range )* )
 60    * value_or_range := ( number | range )
 61    * range := number "-" number [ "/" number ]
 62    * number := LEXICAL( [0-9]+ )
 63    * </pre>
 64    *
 65    * <p>Configuration syntax is compatible with Paul Vixie's <a
 66    * href="ftp://ftp.vix.com/pub/vixie/cron-3.0">cron</a>, version 3.0.</p>
 67    */
 68    public class CronSchedule
 69    implements Schedule
 70    {
 71    // constants /////////////////////////////////////////////////////////////
 72   
 73    /** index of minute field. */
 74    private static final int MINUTE = 0;
 75   
 76    /** index of hour field. */
 77    private static final int HOUR_OF_DAY = 1;
 78   
 79    /** index of day field. */
 80    private static final int DAY_OF_MONTH = 2;
 81   
 82    /** index of month field. */
 83    private static final int MONTH = 3;
 84   
 85    /** index of weekday field. */
 86    private static final int DAY_OF_WEEK = 4;
 87   
 88    /** The schedule type. */
 89    public static final String TYPE = "cron";
 90   
 91    // instance variables ////////////////////////////////////////////////////
 92   
 93    /** The textual configuration form. */
 94    private String config;
 95   
 96    /** The parserd schedule data. */
 97    private int[][] schedule;
 98   
 99    /** I18n component */
 100    private I18n i18n;
 101   
 102    /**
 103    * Constructor.
 104    *
 105    * @param i18n the i18n component.
 106    */
 107  736 public CronSchedule(I18n i18n)
 108    {
 109  736 this.i18n = i18n;
 110    }
 111   
 112    /**
 113    * {@inheritDoc}
 114    */
 115  736 public void init(AbstractScheduler scheduler, String config)
 116    throws InvalidScheduleException
 117    {
 118  736 setConfig(config);
 119    }
 120   
 121    /**
 122    * {@inheritDoc}
 123    */
 124  46 public String getType()
 125    {
 126  46 return TYPE;
 127    }
 128   
 129    /**
 130    * {@inheritDoc}
 131    */
 132  46 public String getConfig()
 133    {
 134  46 return config;
 135    }
 136   
 137    /**
 138    * {@inheritDoc}
 139    */
 140  736 public void setConfig(String config)
 141    throws InvalidScheduleException
 142    {
 143    //TODO Add Polling here...
 144  736 StringReader sr = new StringReader("");
 145  736 CronParser parser = new CronParser(sr);
 146  736 try
 147    {
 148  736 this.schedule = parser.parse(config);
 149  644 this.config = config;
 150    }
 151    catch(ParseException e)
 152    {
 153  0 throw new InvalidScheduleException(localizeParseException(e), e);
 154    }
 155    catch(ValueOutOfRangeException e)
 156    {
 157  0 throw new InvalidScheduleException(localizeValueOutOrRangeException(e), e);
 158    }
 159    //TODO recycle parser
 160    }
 161   
 162    /**
 163    * {@inheritDoc}
 164    */
 165  460 public boolean atStartup()
 166    {
 167  460 return (schedule.length == 0);
 168    }
 169   
 170    /**
 171    * {@inheritDoc}
 172    */
 173  517 public Date getNextRunTime(Date currentTime, Date lastRunTime)
 174    {
 175  517 if(schedule.length == 0)
 176    {
 177  0 return null;
 178    }
 179    else
 180    {
 181  517 GregorianCalendar nextRun = new GregorianCalendar();
 182  517 nextRun.setTime(currentTime);
 183    // skip to next full minute
 184  517 nextRun.set(Calendar.MILLISECOND, 0);
 185  517 nextRun.set(Calendar.SECOND, 0);
 186  517 nextRun.add(Calendar.MINUTE, 1);
 187   
 188  517 computeDay(nextRun);
 189  517 computeHour(nextRun);
 190  517 computeMinute(nextRun);
 191   
 192  517 return nextRun.getTime();
 193    }
 194    }
 195   
 196    // implementation ////////////////////////////////////////////////////////
 197   
 198  517 private void computeMonth(Calendar nextRunDOM)
 199    {
 200  517 int i;
 201  517 if(schedule[MONTH].length != 0)
 202    {
 203  0 loop: for(i=0; i<schedule[MONTH].length; i++)
 204    {
 205  0 if(schedule[MONTH][i] >= nextRunDOM.get(Calendar.MONTH))
 206    {
 207  0 break loop;
 208    }
 209    }
 210  0 if(i == schedule[MONTH].length)
 211    {
 212  0 nextRunDOM.add(Calendar.YEAR, 1);
 213  0 nextRunDOM.set(Calendar.MONTH, schedule[MONTH][0]);
 214  0 nextRunDOM.set(Calendar.DAY_OF_MONTH, 0);
 215  0 nextRunDOM.set(Calendar.HOUR_OF_DAY, 0);
 216  0 nextRunDOM.set(Calendar.MINUTE, 0);
 217    }
 218    else
 219    {
 220  0 if(schedule[MONTH][i] > nextRunDOM.get(Calendar.MONTH))
 221    {
 222  0 nextRunDOM.set(Calendar.MONTH, schedule[MONTH][i]);
 223  0 nextRunDOM.set(Calendar.DAY_OF_MONTH, 0);
 224  0 nextRunDOM.set(Calendar.HOUR_OF_DAY, 0);
 225  0 nextRunDOM.set(Calendar.MINUTE, 0);
 226    }
 227    }
 228    }
 229    }
 230   
 231  517 private void computeDay(Calendar nextRun)
 232    {
 233  517 int i;
 234  517 GregorianCalendar nextRunDOM = new GregorianCalendar();
 235  517 nextRunDOM.setTime(nextRun.getTime());
 236   
 237  517 computeMonth(nextRunDOM);
 238   
 239    // day of month
 240  517 if(schedule[DAY_OF_MONTH].length != 0)
 241    {
 242  0 loop: for(i=0; i<schedule[DAY_OF_MONTH].length; i++)
 243    {
 244  0 if(schedule[DAY_OF_MONTH][i] >= nextRunDOM.get(Calendar.DAY_OF_MONTH))
 245    {
 246  0 break loop;
 247    }
 248    }
 249  0 if(i == schedule[DAY_OF_MONTH].length)
 250    {
 251  0 nextRunDOM.add(Calendar.MONTH, 1);
 252  0 computeMonth(nextRunDOM);
 253    // FIXME day > month length
 254  0 nextRunDOM.set(Calendar.DAY_OF_MONTH, schedule[DAY_OF_MONTH][0]);
 255  0 nextRunDOM.set(Calendar.HOUR_OF_DAY, 0);
 256  0 nextRunDOM.set(Calendar.MINUTE, 0);
 257    }
 258    else
 259    {
 260  0 if(schedule[DAY_OF_MONTH][i] > nextRunDOM.get(Calendar.DAY_OF_MONTH))
 261    {
 262  0 nextRunDOM.set(Calendar.DAY_OF_MONTH, schedule[DAY_OF_MONTH][i]);
 263    // FIXME day > month length
 264  0 nextRunDOM.set(Calendar.HOUR_OF_DAY, 0);
 265  0 nextRunDOM.set(Calendar.MINUTE, 0);
 266    }
 267    }
 268    }
 269   
 270  517 GregorianCalendar nextRunDOW = new GregorianCalendar();
 271  517 nextRunDOW.setTime(nextRun.getTime());
 272    // day of week
 273  517 if(schedule[DAY_OF_WEEK].length != 0)
 274    {
 275  0 loop: for(i=0; i<schedule[DAY_OF_WEEK].length; i++)
 276    {
 277  0 if(schedule[DAY_OF_WEEK][i] >= nextRunDOW.get(Calendar.DAY_OF_WEEK))
 278    {
 279  0 break loop;
 280    }
 281    }
 282  0 if(i == schedule[DAY_OF_WEEK].length)
 283    {
 284  0 nextRunDOW.add(Calendar.WEEK_OF_YEAR, 1);
 285  0 nextRunDOW.set(Calendar.DAY_OF_WEEK, schedule[DAY_OF_WEEK][0]);
 286  0 nextRunDOW.set(Calendar.HOUR_OF_DAY, 0);
 287  0 nextRunDOW.set(Calendar.MINUTE, 0);
 288    }
 289    else
 290    {
 291  0 if(schedule[DAY_OF_WEEK][i] > nextRunDOW.get(Calendar.DAY_OF_WEEK))
 292    {
 293  0 if(nextRunDOW.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY &&
 294    nextRunDOW.getFirstDayOfWeek() != Calendar.SUNDAY)
 295    {
 296  0 nextRunDOW.add(Calendar.WEEK_OF_YEAR, 1);
 297    }
 298  0 nextRunDOW.set(Calendar.DAY_OF_WEEK, schedule[DAY_OF_WEEK][i]);
 299  0 nextRunDOW.set(Calendar.HOUR_OF_DAY, 0);
 300  0 nextRunDOW.set(Calendar.MINUTE, 0);
 301    }
 302    }
 303    }
 304   
 305  517 if(schedule[DAY_OF_WEEK].length == 0)
 306    {
 307  517 nextRun.setTime(nextRunDOM.getTime());
 308    }
 309    else
 310    {
 311  0 if(schedule[DAY_OF_MONTH].length == 0 && schedule[MONTH].length == 0)
 312    {
 313  0 nextRun.setTime(nextRunDOW.getTime());
 314    }
 315    else
 316    {
 317  0 if(nextRunDOM.getTime().compareTo(nextRunDOW.getTime()) < 0)
 318    {
 319  0 nextRun.setTime(nextRunDOM.getTime());
 320    }
 321    else
 322    {
 323  0 nextRun.setTime(nextRunDOW.getTime());
 324    }
 325    }
 326    }
 327    }
 328   
 329  517 private void computeHour(Calendar nextRun)
 330    {
 331  517 int i;
 332  517 if(schedule[HOUR_OF_DAY].length != 0)
 333    {
 334  0 loop: for(i=0; i<schedule[HOUR_OF_DAY].length; i++)
 335    {
 336  0 if(schedule[HOUR_OF_DAY][i] >= nextRun.get(Calendar.HOUR_OF_DAY))
 337    {
 338  0 break loop;
 339    }
 340    }
 341  0 if(i == schedule[HOUR_OF_DAY].length)
 342    {
 343  0 nextRun.add(Calendar.DAY_OF_MONTH, 1);
 344  0 computeDay(nextRun);
 345  0 nextRun.set(Calendar.HOUR_OF_DAY, schedule[HOUR_OF_DAY][0]);
 346  0 nextRun.set(Calendar.MINUTE, 0);
 347    }
 348    else
 349    {
 350  0 if(schedule[HOUR_OF_DAY][i] > nextRun.get(Calendar.HOUR_OF_DAY))
 351    {
 352  0 nextRun.set(Calendar.MINUTE, 0);
 353  0 nextRun.set(Calendar.HOUR_OF_DAY, schedule[HOUR_OF_DAY][i]);
 354    }
 355    }
 356    }
 357    }
 358   
 359  517 private void computeMinute(Calendar nextRun)
 360    {
 361  517 int i;
 362  517 if(schedule[MINUTE].length != 0)
 363    {
 364  0 loop: for(i=0; i<schedule[MINUTE].length; i++)
 365    {
 366  0 if(schedule[MINUTE][i] >= nextRun.get(Calendar.MINUTE))
 367    {
 368  0 break loop;
 369    }
 370    }
 371  0 if(i == schedule[MINUTE].length)
 372    {
 373  0 nextRun.add(Calendar.HOUR_OF_DAY, 1);
 374  0 computeHour(nextRun);
 375  0 nextRun.set(Calendar.MINUTE, schedule[MINUTE][0]);
 376    }
 377    else
 378    {
 379  0 if(schedule[MINUTE][i] > nextRun.get(Calendar.MINUTE))
 380    {
 381  0 nextRun.set(Calendar.MINUTE, schedule[MINUTE][i]);
 382    }
 383    }
 384    }
 385    }
 386   
 387    /**
 388    * The end of line string for this machine.
 389    */
 390    protected String eol = System.getProperty("line.separator", "\n");
 391   
 392    /**
 393    * Create a localized message out of ParseException object.
 394    *
 395    * @param ex the exception.
 396    */
 397  0 private String localizeParseException(ParseException ex)
 398    {
 399  0 String expected = "";
 400  0 int maxSize = 0;
 401  0 for (int i = 0; i < ex.expectedTokenSequences.length; i++)
 402    {
 403  0 if (maxSize < ex.expectedTokenSequences[i].length)
 404    {
 405  0 maxSize = ex.expectedTokenSequences[i].length;
 406    }
 407  0 for (int j = 0; j < ex.expectedTokenSequences[i].length; j++)
 408    {
 409  0 expected += ex.tokenImage[ex.expectedTokenSequences[i][j]] + " ";
 410    }
 411  0 if (ex.expectedTokenSequences[i][ex.expectedTokenSequences[i].length - 1] != 0)
 412    {
 413  0 expected += "...";
 414    }
 415  0 expected += eol + " ";
 416    }
 417   
 418  0 String encountered = "";
 419  0 Token tok = ex.currentToken.next;
 420  0 for (int i = 0; i < maxSize; i++)
 421    {
 422  0 if (i != 0)
 423    {
 424  0 encountered += " ";
 425    }
 426  0 if (tok.kind == 0)
 427    {
 428  0 encountered += ex.tokenImage[0];
 429  0 break;
 430    }
 431  0 encountered += addEscapes(tok.image);
 432  0 tok = tok.next;
 433    }
 434   
 435  0 String[] strings = new String[] {
 436    encountered,
 437    Integer.toString(ex.currentToken.next.beginColumn),
 438    expected
 439    };
 440   
 441  0 String pattern;
 442  0 if(ex.expectedTokenSequences.length == 1)
 443    {
 444  0 pattern = "ledge.scheduler.cron.parseOne";
 445    }
 446    else
 447    {
 448  0 pattern = "ledge.scheduler.cron.parseMany";
 449    }
 450  0 return i18n.get(i18n.getDefaultLocale(), pattern, strings);
 451    }
 452   
 453    /**
 454    * Create a localized message out of ParseException object.
 455    *
 456    * @param ex the exception.
 457    */
 458  0 private String localizeValueOutOrRangeException(ValueOutOfRangeException ex)
 459    {
 460  0 String[] strings = new String[] {
 461    Integer.toString(ex.token.beginColumn),
 462    Integer.toString(ex.value),
 463    Integer.toString(ex.min),
 464    Integer.toString(ex.max)
 465    };
 466  0 String pattern = "ledge.scheduler.cron.outOfRange";
 467  0 return i18n.get(i18n.getDefaultLocale(), pattern, strings);
 468    }
 469   
 470    /**
 471    * Used to convert raw characters to their escaped version
 472    * when these raw version cannot be used as part of an ASCII
 473    * string literal.
 474    *
 475    * <p>This method was copied from JavaCC generated code.</p>
 476    *
 477    * @author JavaCC team
 478    * @param str input string.
 479    * @return string with escaped sepcial characters.
 480    */
 481  0 protected String addEscapes(String str)
 482    {
 483  0 StringBuilder retval = new StringBuilder();
 484  0 char ch;
 485