package com.pugh.sockso.music.scheduling;
import com.pugh.sockso.Constants;
import com.pugh.sockso.Properties;
import java.util.Date;
import java.util.Calendar;
import org.apache.log4j.Logger;
/**
* This scheduler accepts a cron style specification for when it should run
*
* Example:
*
* scheduler.cron.tab 20 * * * *
*
* Will run at 20 minutes past every hour.
*
*/
public class CronScheduler implements Scheduler {
private static final Logger log = Logger.getLogger( CronScheduler.class );
private static final String DEFAULT_TAB = "*/30 * * * *";
private final Properties p;
/**
* Constructorm creates a new scheduler which runs via a cron spec
*
* @param indexer
* @param p
*
*/
public CronScheduler( final Properties p ) {
this.p = p;
}
/**
* Returns a boolean indicating if the indiexer should be run on the date
*
* @param date
*
* @return
*
*/
public boolean shouldRunAt( final Date date ) {
try {
final String spec = p.get( Constants.SCHED_CRON_TAB, DEFAULT_TAB );
final String[] patterns = getPatterns( spec );
final Calendar cal = Calendar.getInstance();
cal.setTime( date );
return
// normal cron rules
(
matchesPattern( cal.get(Calendar.MINUTE), patterns[0] ) // minute
&& matchesPattern( cal.get(Calendar.HOUR), patterns[1] ) // hour
&& matchesPattern( cal.get(Calendar.DAY_OF_MONTH), patterns[2] ) // day of month
&& matchesPattern( cal.get(Calendar.MONTH) + 1, getMonth(patterns[3]) ) // month
&& matchesPattern( cal.get(Calendar.DAY_OF_WEEK) - 1, getDayOfWeek(patterns[4]) ) // day of week
)
||
// exception: if both "day of month" and "day of week" are restricted
// (not "*"), then either the "day of month" field (3) or the "day of
// week" field (5) must match the current day (even though the other
// of the two fields need not match the current day).
// http://en.wikipedia.org/wiki/Cron#crontab_syntax
(
!patterns[2].equals("*") && !patterns[4].equals("*") &&
( matchesPattern( cal.get(Calendar.DAY_OF_MONTH), patterns[2] )
|| matchesPattern( cal.get(Calendar.DAY_OF_WEEK) - 1, getDayOfWeek(patterns[4]) ) )
);
}
catch ( final Exception e ) {
e.printStackTrace();
return false;
}
}
/**
* Returns true if we have a match for the specified part of the spec
*
* @param toMatch the number to match (eg minute, hour, day)
* @param pattern eg. "*", "1,2,3"
*
* @return
*
*/
private boolean matchesPattern( final int toMatch, final String pattern ) {
// every unit: *
if ( pattern.equals("*") ) {
return true;
}
// every period of units: */10
else if ( pattern.contains("/") ) {
final int divisor = Integer.parseInt( pattern.substring( 2 ) );
return toMatch % divisor == 0;
}
// between certain units: 1-6
else if ( pattern.contains("-") ) {
final int dashIndex = pattern.indexOf( "-" );
final int from = Integer.parseInt( pattern.substring(0,dashIndex) );
final int to = Integer.parseInt( pattern.substring(dashIndex+1) );
for ( int i=from; i<=to; i++ ) {
if ( toMatch == i ) {
return true;
}
}
}
// on certain units: 1,2,3
else if ( pattern.contains(",") ) {
final String[] units = pattern.split( "," );
for ( final String unit : units ) {
if ( toMatch == Integer.parseInt(unit) ) {
return true;
}
}
}
// on a unit: 1
else if ( toMatch == Integer.parseInt(pattern) ) {
return true;
}
// no match
return false;
}
/**
* Given a specification for a month translates it to numbers. For example
* it could be "jan", which will become 1.
*
* @param month
*
* @return
*
*/
private String getMonth( final String month ) {
final String replacements[] = {
"jan", "1",
"feb", "2",
"mar", "3",
"apr", "4",
"may", "5",
"jun", "6",
"jul", "7",
"aug", "8",
"sep", "9",
"oct", "10",
"nov", "11",
"dec", "12"
};
return replaceWords( month, replacements );
}
/**
* Given a specification for a day of the week translates it to numbers.
* For example it could be "mon", which will become 1.
*
* @param dayOfWeek
*
* @return
*
*/
private String getDayOfWeek( final String dayOfWeek ) {
final String replacements[] = {
"sun", "0",
"mon", "1",
"tue", "2",
"wed", "3",
"thu", "4",
"fri", "5",
"sat", "6"
};
return replaceWords( dayOfWeek, replacements )
.replaceAll( "7", "0" ); // sunday can be 0 or 7
}
/**
* Replaces words in the phrase with the value of the next item (ie. they
* should be in pairs in the array)
*
* @param phrase
* @param replacements
*
* @return
*
*/
private String replaceWords( final String phrase, final String[] replacements ) {
String newPhrase = phrase;
for ( int i=0; i<replacements.length; i+=2 ) {
newPhrase = newPhrase.replaceAll(
replacements[ i ],
replacements[ i + 1 ]
);
}
return newPhrase;
}
/**
* Returns an array of patterns from the cron spec. The spec can optionally
* contain any of the special @name words (eg. @yearly)
*
* @param spec
*
* @return
*
*/
private String[] getPatterns( final String spec ) {
final String[] replacements = {
"@yearly", "0 0 1 1 *",
"@annually", "0 0 1 1 *",
"@monthly", "0 0 1 * *",
"@weekly", "0 0 * * 0",
"@daily", "0 0 * * *",
"@midnight", "0 0 * * *",
"@hourly", "0 * * * *",
};
return replaceWords( spec, replacements ).split( " " );
}
}