diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index ed9606e..ece8157 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -81,7 +81,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.ultramine.permission.IPermissionManager; -import org.ultramine.sceduler.Scheduler; +import org.ultramine.scheduler.Scheduler; import org.ultramine.server.BackupManager; import org.ultramine.server.ConfigurationHandler; import org.ultramine.server.WatchdogThread; diff --git a/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java b/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java deleted file mode 100644 index 6cd578d..0000000 --- a/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ultramine.sceduler; - -import org.ultramine.sceduler.pattern.SchedulingPattern; -import org.ultramine.server.util.GlobalExecutors; - -public class ScheduledAsyncTask extends ScheduledTask -{ - ScheduledAsyncTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) - { - super(sceduler, pattern, task); - } - - @Override - void launch() - { - GlobalExecutors.cachedIO().execute(task); - } -} diff --git a/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java b/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java deleted file mode 100644 index f578b76..0000000 --- a/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.ultramine.sceduler; - -import org.ultramine.sceduler.pattern.SchedulingPattern; - -public class ScheduledSyncTask extends ScheduledTask -{ - ScheduledSyncTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) - { - super(sceduler, pattern, task); - } - - @Override - void launch() - { - sceduler.addToSyncQueue(task); - } -} \ No newline at end of file diff --git a/src/main/java/org/ultramine/sceduler/ScheduledTask.java b/src/main/java/org/ultramine/sceduler/ScheduledTask.java deleted file mode 100644 index b698030..0000000 --- a/src/main/java/org/ultramine/sceduler/ScheduledTask.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.ultramine.sceduler; - -import org.ultramine.sceduler.pattern.SchedulingPattern; - -public abstract class ScheduledTask -{ - protected final Scheduler sceduler; - protected final SchedulingPattern pattern; - protected final Runnable task; - - protected ScheduledTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) - { - this.sceduler = sceduler; - this.pattern = pattern; - this.task = task; - } - - public boolean cancel() - { - return sceduler.cancelTask(this); - } - - public boolean canRun(long millis) - { - return pattern.match(millis); - } - - abstract void launch(); -} diff --git a/src/main/java/org/ultramine/sceduler/Scheduler.java b/src/main/java/org/ultramine/sceduler/Scheduler.java deleted file mode 100644 index cc1373c..0000000 --- a/src/main/java/org/ultramine/sceduler/Scheduler.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.ultramine.sceduler; - -import java.util.List; -import java.util.Queue; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.ultramine.sceduler.pattern.SchedulingPattern; - -import com.google.common.collect.Queues; - -import cpw.mods.fml.common.FMLCommonHandler; -import cpw.mods.fml.common.eventhandler.SubscribeEvent; -import cpw.mods.fml.common.gameevent.TickEvent; -import cpw.mods.fml.common.gameevent.TickEvent.Phase; - -public class Scheduler -{ - private final List tasks = new CopyOnWriteArrayList(); - private final Queue toSyncExec = Queues.newConcurrentLinkedQueue(); - private final SchedulerThread thread = new SchedulerThread(); - - private volatile boolean isRunning = true; - - public Scheduler() - { - - } - - public void start() - { - thread.setDaemon(true); - thread.start(); - FMLCommonHandler.instance().bus().register(this); - } - - public void stop() - { - isRunning = false; - thread.interrupt(); - tasks.clear(); - FMLCommonHandler.instance().bus().unregister(this); - } - - public ScheduledTask scheduleSync(String pattern, Runnable run) - { - ScheduledSyncTask task = new ScheduledSyncTask(this, new SchedulingPattern(pattern), run); - tasks.add(task); - return task; - } - - public ScheduledTask scheduleAsync(String pattern, Runnable run) - { - ScheduledAsyncTask task = new ScheduledAsyncTask(this, new SchedulingPattern(pattern), run); - tasks.add(task); - return task; - } - - boolean cancelTask(ScheduledTask task) - { - return tasks.remove(task); - } - - void addToSyncQueue(Runnable task) - { - toSyncExec.add(task); - } - - private void launchTasks(long millis) - { - for(ScheduledTask task : tasks) - { - if(task.canRun(millis)) - task.launch(); - } - } - - @SubscribeEvent - public void onTick(TickEvent.ServerTickEvent e) - { - if(e.phase == Phase.END) - { - for(Runnable task; (task = toSyncExec.poll()) != null;) - task.run(); - } - } - - private class SchedulerThread extends Thread - { - public SchedulerThread() - { - super("UM Scheduler thread"); - } - - private void safeSleep(long millis) throws InterruptedException - { - long done = 0; - do - { - long before = System.currentTimeMillis(); - sleep(millis - done); - long after = System.currentTimeMillis(); - done += (after - before); - } - while(done < millis); - } - - @Override - public void run() - { - long millis = System.currentTimeMillis(); - long nextMinute = ((millis / 60000) + 1) * 60000; - while(isRunning) - { - long sleepTime = (nextMinute - System.currentTimeMillis()); - if(sleepTime > 0) - { - try - { - safeSleep(sleepTime); - } - catch(InterruptedException e) - { - continue; - } - } - millis = System.currentTimeMillis(); - launchTasks(millis); - nextMinute = ((millis / 60000) + 1) * 60000; - } - } - } -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/AlwaysTrueValueMatcher.java b/src/main/java/org/ultramine/sceduler/pattern/AlwaysTrueValueMatcher.java deleted file mode 100644 index 8dd82b2..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/AlwaysTrueValueMatcher.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ultramine.sceduler.pattern; - -class AlwaysTrueValueMatcher implements IValueMatcher -{ - @Override - public boolean match(int value) - { - return true; - } -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/DayOfMonthValueMatcher.java b/src/main/java/org/ultramine/sceduler/pattern/DayOfMonthValueMatcher.java deleted file mode 100644 index 2073d29..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/DayOfMonthValueMatcher.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.ultramine.sceduler.pattern; - -import gnu.trove.set.TIntSet; - -/** - *

- * A ValueMatcher whose rules are in a set of integer values. When asked - * to validate a value, this ValueMatcher checks if it is in the set and, if - * not, checks whether the last-day-of-month setting applies. - *

- */ -class DayOfMonthValueMatcher extends IntSetValueMatcher -{ - private static final int[] lastDays = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - - public DayOfMonthValueMatcher(TIntSet integers) - { - super(integers); - } - - /** - * Returns true if the given value is included in the matcher list or the - * last-day-of-month setting applies. - */ - public boolean match(int value, int month, boolean isLeapYear) - { - return (super.match(value) || (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear))); - } - - public boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) - { - if(isLeapYear && month == 2) - { - return value == 29; - } - else - { - return value == lastDays[month - 1]; - } - } -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/IValueMatcher.java b/src/main/java/org/ultramine/sceduler/pattern/IValueMatcher.java deleted file mode 100644 index f06ad05..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/IValueMatcher.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.ultramine.sceduler.pattern; - -/** - *

- * This interface describes the ValueMatcher behavior. A ValueMatcher is an - * object that validate an integer value against a set of rules. - *

- */ -interface IValueMatcher -{ - /** - * Validate the given integer value against a set of rules. - * - * @param value - * The value. - * @return true if the given value matches the rules of the ValueMatcher, - * false otherwise. - */ - boolean match(int value); -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/IntSetValueMatcher.java b/src/main/java/org/ultramine/sceduler/pattern/IntSetValueMatcher.java deleted file mode 100644 index 310948e..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/IntSetValueMatcher.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.ultramine.sceduler.pattern; - -import gnu.trove.set.TIntSet; - -class IntSetValueMatcher implements IValueMatcher -{ - private final TIntSet values; - - public IntSetValueMatcher(TIntSet values) - { - this.values = values; - } - - @Override - public boolean match(int value) - { - return values.contains(value); - } -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/InvalidPatternException.java b/src/main/java/org/ultramine/sceduler/pattern/InvalidPatternException.java deleted file mode 100644 index 7adae6d..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/InvalidPatternException.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ultramine.sceduler.pattern; - -/** - *

- * This kind of exception is thrown if an invalid scheduling pattern is - * encountered by the scheduler. - *

- */ -public class InvalidPatternException extends RuntimeException -{ - private static final long serialVersionUID = 1L; - - InvalidPatternException() - { - } - - InvalidPatternException(String message) - { - super(message); - } -} diff --git a/src/main/java/org/ultramine/sceduler/pattern/SchedulingPattern.java b/src/main/java/org/ultramine/sceduler/pattern/SchedulingPattern.java deleted file mode 100644 index 5cf2219..0000000 --- a/src/main/java/org/ultramine/sceduler/pattern/SchedulingPattern.java +++ /dev/null @@ -1,718 +0,0 @@ -package org.ultramine.sceduler.pattern; - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.StringTokenizer; -import java.util.TimeZone; - -import org.apache.commons.lang3.StringUtils; - -import gnu.trove.list.TIntList; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.set.TIntSet; -import gnu.trove.set.hash.TIntHashSet; - -/** - *

- * A UNIX crontab-like pattern is a string split in five space separated parts. - * Each part is intented as: - *

- *
    - *
  1. Minutes sub-pattern. During which minutes of the hour - * should the task been launched? The values range is from 0 to 59.
  2. - *
  3. Hours sub-pattern. During which hours of the day should - * the task been launched? The values range is from 0 to 23.
  4. - *
  5. Days of month sub-pattern. During which days of the - * month should the task been launched? The values range is from 1 to 31. The - * special value L can be used to recognize the last day of month.
  6. - *
  7. Months sub-pattern. During which months of the year - * should the task been launched? The values range is from 1 (January) to 12 - * (December), otherwise this sub-pattern allows the aliases "jan", - * "feb", "mar", "apr", "may", - * "jun", "jul", "aug", "sep", - * "oct", "nov" and "dec".
  8. - *
  9. Days of week sub-pattern. During which days of the week - * should the task been launched? The values range is from 0 (Sunday) to 6 - * (Saturday), otherwise this sub-pattern allows the aliases "sun", - * "mon", "tue", "wed", "thu", - * "fri" and "sat".
  10. - *
- *

- * The star wildcard character is also admitted, indicating "every minute - * of the hour", "every hour of the day", "every day of the - * month", "every month of the year" and "every day of the - * week", according to the sub-pattern in which it is used. - *

- *

- * Once the scheduler is started, a task will be launched when the five parts in - * its scheduling pattern will be true at the same time. - *

- *

- * Some examples: - *

- *

- * 5 * * * *
- * This pattern causes a task to be launched once every hour, at the begin of - * the fifth minute (00:05, 01:05, 02:05 etc.). - *

- *

- * * * * * *
- * This pattern causes a task to be launched every minute. - *

- *

- * * 12 * * Mon
- * This pattern causes a task to be launched every minute during the 12th hour - * of Monday. - *

- *

- * * 12 16 * Mon
- * This pattern causes a task to be launched every minute during the 12th hour - * of Monday, 16th, but only if the day is the 16th of the month. - *

- *

- * Every sub-pattern can contain two or more comma separated values. - *

- *

- * 59 11 * * 1,2,3,4,5
- * This pattern causes a task to be launched at 11:59AM on Monday, Tuesday, - * Wednesday, Thursday and Friday. - *

- *

- * Values intervals are admitted and defined using the minus character. - *

- *

- * 59 11 * * 1-5
- * This pattern is equivalent to the previous one. - *

- *

- * The slash character can be used to identify step values within a range. It - * can be used both in the form */c and a-b/c. The - * subpattern is matched every c values of the range - * 0,maxvalue or a-b. - *

- *

- * */5 * * * *
- * This pattern causes a task to be launched every 5 minutes (0:00, 0:05, 0:10, - * 0:15 and so on). - *

- *

- * 3-18/5 * * * *
- * This pattern causes a task to be launched every 5 minutes starting from the - * third minute of the hour, up to the 18th (0:03, 0:08, 0:13, 0:18, 1:03, 1:08 - * and so on). - *

- *

- * */15 9-17 * * *
- * This pattern causes a task to be launched every 15 minutes between the 9th - * and 17th hour of the day (9:00, 9:15, 9:30, 9:45 and so on... note that the - * last execution will be at 17:45). - *

- *

- * All the fresh described syntax rules can be used together. - *

- *

- * * 12 10-16/2 * *
- * This pattern causes a task to be launched every minute during the 12th hour - * of the day, but only if the day is the 10th, the 12th, the 14th or the 16th - * of the month. - *

- *

- * * 12 1-15,17,20-25 * *
- * This pattern causes a task to be launched every minute during the 12th hour - * of the day, but the day of the month must be between the 1st and the 15th, - * the 20th and the 25, or at least it must be the 17th. - *

- *

- * Finally cron4j lets you combine more scheduling patterns into one, with the - * pipe character: - *

- *

- * 0 5 * * *|8 10 * * *|22 17 * * *
- * This pattern causes a task to be launched every day at 05:00, 10:08 and - * 17:22. - *

- */ -public class SchedulingPattern -{ - private static final IValueParser MINUTE_VALUE_PARSER = new MinuteValueParser(); - private static final IValueParser HOUR_VALUE_PARSER = new HourValueParser(); - private static final IValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser(); - private static final IValueParser MONTH_VALUE_PARSER = new MonthValueParser(); - private static final IValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser(); - - /** The pattern as a string. */ - private String asString; - - protected ArrayList valueMatchers = new ArrayList(); - - /** - * Builds a SchedulingPattern parsing it from a string. - * - * @param pattern - * The pattern as a crontab-like string. - * @throws InvalidPatternException - * If the supplied string is not a valid pattern. - */ - public SchedulingPattern(String pattern) throws InvalidPatternException - { - this.asString = pattern; - StringTokenizer st1 = new StringTokenizer(pattern, "|"); - if(st1.countTokens() < 1) - { - throw new InvalidPatternException("invalid pattern: \"" + pattern + "\""); - } - while(st1.hasMoreTokens()) - { - String localPattern = st1.nextToken(); - StringTokenizer st2 = new StringTokenizer(localPattern, " \t"); - if(st2.countTokens() != 5) - { - throw new InvalidPatternException("invalid pattern: \"" + localPattern + "\""); - } - ValueMatcherCollection matcher = new ValueMatcherCollection(); - try - { - matcher.minute = buildValueMatcher(st2.nextToken(), MINUTE_VALUE_PARSER); - } - catch(Exception e) - { - throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing minutes field: " + e.getMessage() + "."); - } - try - { - matcher.hour = buildValueMatcher(st2.nextToken(), HOUR_VALUE_PARSER); - } - catch(Exception e) - { - throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing hours field: " + e.getMessage() + "."); - } - try - { - matcher.dayOfMonth = buildValueMatcher(st2.nextToken(), DAY_OF_MONTH_VALUE_PARSER); - } - catch(Exception e) - { - throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of month field: " + e.getMessage() + "."); - } - try - { - matcher.month = buildValueMatcher(st2.nextToken(), MONTH_VALUE_PARSER); - } - catch(Exception e) - { - throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing months field: " + e.getMessage() + "."); - } - try - { - matcher.dayOfWeek = buildValueMatcher(st2.nextToken(), DAY_OF_WEEK_VALUE_PARSER); - } - catch(Exception e) - { - throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of week field: " + e.getMessage() + "."); - } - valueMatchers.add(matcher); - } - } - - /** - * A ValueMatcher utility builder. - * - * @param str - * The pattern part for the ValueMatcher creation. - * @param parser - * The parser used to parse the values. - * @return The requested ValueMatcher. - * @throws Exception - * If the supplied pattern part is not valid. - */ - private IValueMatcher buildValueMatcher(String str, IValueParser parser) throws Exception - { - if(str.length() == 1 && str.equals("*")) - { - return new AlwaysTrueValueMatcher(); - } - TIntSet values = new TIntHashSet(); - String[] parts = StringUtils.split(str, ','); - for(int i = 0; i < parts.length; i++) - { - String element = parts[i]; - TIntList local; - try - { - local = parseListElement(element, parser); - } - catch(Exception e) - { - throw new Exception("invalid field \"" + str + "\", invalid element \"" + element + "\", " + e.getMessage()); - } - values.addAll(local); - } - if(values.size() == 0) - { - throw new Exception("invalid field \"" + str + "\""); - } - if(parser == DAY_OF_MONTH_VALUE_PARSER) - { - return new DayOfMonthValueMatcher(values); - } - else - { - return new IntSetValueMatcher(values); - } - } - - /** - * Parses an element of a list of values of the pattern. - * - * @param str - * The element string. - * @param parser - * The parser used to parse the values. - * @return A list of integers representing the allowed values. - * @throws Exception - * If the supplied pattern part is not valid. - */ - private TIntList parseListElement(String str, IValueParser parser) throws Exception - { - String[] parts = StringUtils.split(str, '/'); - if(parts.length < 1 || parts.length > 2) - { - throw new Exception("syntax error"); - } - TIntList values; - try - { - values = parseRange(parts[0], parser); - } - catch(Exception e) - { - throw new Exception("invalid range, " + e.getMessage()); - } - if(parts.length == 2) - { - String dStr = parts[1]; - int div; - try - { - div = Integer.parseInt(dStr); - } - catch(NumberFormatException e) - { - throw new Exception("invalid divisor \"" + dStr + "\""); - } - if(div < 1) - { - throw new Exception("non positive divisor \"" + div + "\""); - } - TIntList values2 = new TIntArrayList(values.size()/div+1); - for(int i = 0; i < values.size(); i += div) - { - values2.add(values.get(i)); - } - return values2; - } - else - { - return values; - } - } - - /** - * Parses a range of values. - * - * @param str - * The range string. - * @param parser - * The parser used to parse the values. - * @return A list of integers representing the allowed values. - * @throws Exception - * If the supplied pattern part is not valid. - */ - private TIntList parseRange(String str, IValueParser parser) throws Exception - { - if(str.equals("*")) - { - int min = parser.getMinValue(); - int max = parser.getMaxValue(); - TIntList values = new TIntArrayList(); - for(int i = min; i <= max; i++) - { - values.add(i); - } - return values; - } - String[] parts = StringUtils.split(str, '-'); - if(parts.length < 1 || parts.length > 2) - { - throw new Exception("syntax error"); - } - String v1Str = parts[0]; - int v1; - try - { - v1 = parser.parse(v1Str); - } - catch(Exception e) - { - throw new Exception("invalid value \"" + v1Str + "\", " + e.getMessage()); - } - if(parts.length == 1) - { - TIntList values = new TIntArrayList(); - values.add(v1); - return values; - } - else - { - String v2Str = parts[1]; - int v2; - try - { - v2 = parser.parse(v2Str); - } - catch(Exception e) - { - throw new Exception("invalid value \"" + v2Str + "\", " + e.getMessage()); - } - TIntList values = new TIntArrayList(); - if(v1 < v2) - { - for(int i = v1; i <= v2; i++) - { - values.add(i); - } - } - else if(v1 > v2) - { - int min = parser.getMinValue(); - int max = parser.getMaxValue(); - for(int i = v1; i <= max; i++) - { - values.add(i); - } - for(int i = min; i <= v2; i++) - { - values.add(i); - } - } - else - { - // v1 == v2 - values.add(v1); - } - return values; - } - } - - /** - * This methods returns true if the given timestamp (expressed as a UNIX-era - * millis value) matches the pattern, according to the given time zone. - * - * @param timezone - * A time zone. - * @param millis - * The timestamp, as a UNIX-era millis value. - * @return true if the given timestamp matches the pattern. - */ - public boolean match(TimeZone timezone, long millis) - { - GregorianCalendar gc = new GregorianCalendar(); - gc.setTimeInMillis(millis); - if(timezone != null) - gc.setTimeZone(timezone); - int minute = gc.get(Calendar.MINUTE); - int hour = gc.get(Calendar.HOUR_OF_DAY); - int dayOfMonth = gc.get(Calendar.DAY_OF_MONTH); - int month = gc.get(Calendar.MONTH) + 1; - int dayOfWeek = gc.get(Calendar.DAY_OF_WEEK) - 1; - int year = gc.get(Calendar.YEAR); - for(int i = 0; i < valueMatchers.size(); i++) - { - ValueMatcherCollection matcher = valueMatchers.get(i); - boolean eval = matcher.minute.match(minute) - && matcher.hour.match(hour) && ((matcher.dayOfMonth instanceof DayOfMonthValueMatcher) - ? ((DayOfMonthValueMatcher) matcher.dayOfMonth).match(dayOfMonth, month, gc.isLeapYear(year)) : matcher.dayOfMonth.match(dayOfMonth)) - && matcher.month.match(month) && matcher.dayOfWeek.match(dayOfWeek); - if(eval) - return true; - } - return false; - } - - /** - * This methods returns true if the given timestamp (expressed as a UNIX-era - * millis value) matches the pattern, according to the system default time - * zone. - * - * @param millis - * The timestamp, as a UNIX-era millis value. - * @return true if the given timestamp matches the pattern. - */ - public boolean match(long millis) - { - return match(null, millis); - } - - /** - * Returns the pattern as a string. - * - * @return The pattern as a string. - */ - public String toString() - { - return asString; - } - - /** - * This utility method changes an alias to an int value. - * - * @param value - * The value. - * @param aliases - * The aliases list. - * @param offset - * The offset appplied to the aliases list indices. - * @return The parsed value. - * @throws Exception - * If the expressed values doesn't match any alias. - */ - private static int parseAlias(String value, String[] aliases, int offset) throws Exception - { - for(int i = 0; i < aliases.length; i++) - { - if(aliases[i].equalsIgnoreCase(value)) - { - return offset + i; - } - } - throw new Exception("invalid alias \"" + value + "\""); - } - - /** - * Definition for a value parser. - */ - private static interface IValueParser - { - /** - * Attempts to parse a value. - * - * @param value - * The value. - * @return The parsed value. - * @throws Exception - * If the value can't be parsed. - */ - int parse(String value) throws Exception; - - /** - * Returns the minimum value accepred by the parser. - * - * @return The minimum value accepred by the parser. - */ - int getMinValue(); - - /** - * Returns the maximum value accepred by the parser. - * - * @return The maximum value accepred by the parser. - */ - int getMaxValue(); - } - - /** - * A simple value parser. - */ - private static class SimpleValueParser implements IValueParser - { - /** - * The minimum allowed value. - */ - protected int minValue; - - /** - * The maximum allowed value. - */ - protected int maxValue; - - /** - * Builds the value parser. - * - * @param minValue - * The minimum allowed value. - * @param maxValue - * The maximum allowed value. - */ - public SimpleValueParser(int minValue, int maxValue) - { - this.minValue = minValue; - this.maxValue = maxValue; - } - - public int parse(String value) throws Exception - { - int i; - try - { - i = Integer.parseInt(value); - } - catch(NumberFormatException e) - { - throw new Exception("invalid integer value"); - } - if(i < minValue || i > maxValue) - { - throw new Exception("value out of range"); - } - return i; - } - - public int getMinValue() - { - return minValue; - } - - public int getMaxValue() - { - return maxValue; - } - } - - /** - * The minutes value parser. - */ - private static class MinuteValueParser extends SimpleValueParser - { - /** - * Builds the value parser. - */ - public MinuteValueParser() - { - super(0, 59); - } - } - - /** - * The hours value parser. - */ - private static class HourValueParser extends SimpleValueParser - { - /** - * Builds the value parser. - */ - public HourValueParser() - { - super(0, 23); - } - } - - /** - * The days of month value parser. - */ - private static class DayOfMonthValueParser extends SimpleValueParser - { - /** - * Builds the value parser. - */ - public DayOfMonthValueParser() - { - super(1, 31); - } - - /** - * Added to support last-day-of-month. - * - * @param value - * The value to be parsed - * @return the integer day of the month or 32 for last day of the month - * @throws Exception - * if the input value is invalid - */ - public int parse(String value) throws Exception - { - if(value.equalsIgnoreCase("L")) - { - return 32; - } - else - { - return super.parse(value); - } - } - } - - /** - * The value parser for the months field. - */ - private static class MonthValueParser extends SimpleValueParser - { - /** - * Months aliases. - */ - private static String[] ALIASES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; - - /** - * Builds the months value parser. - */ - public MonthValueParser() - { - super(1, 12); - } - - public int parse(String value) throws Exception - { - try - { - // try as a simple value - return super.parse(value); - } - catch(Exception e) - { - // try as an alias - return parseAlias(value, ALIASES, 1); - } - } - } - - /** - * The value parser for the months field. - */ - private static class DayOfWeekValueParser extends SimpleValueParser - { - /** - * Days of week aliases. - */ - private static String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; - - /** - * Builds the months value parser. - */ - public DayOfWeekValueParser() - { - super(0, 7); - } - - public int parse(String value) throws Exception - { - try - { - // try as a simple value - return super.parse(value) % 7; - } - catch(Exception e) - { - // try as an alias - return parseAlias(value, ALIASES, 0); - } - } - } - - private static class ValueMatcherCollection - { - IValueMatcher minute; - IValueMatcher hour; - IValueMatcher dayOfMonth; - IValueMatcher month; - IValueMatcher dayOfWeek; - } -} diff --git a/src/main/java/org/ultramine/scheduler/ScheduledAsyncTask.java b/src/main/java/org/ultramine/scheduler/ScheduledAsyncTask.java new file mode 100644 index 0000000..8d32dc2 --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/ScheduledAsyncTask.java @@ -0,0 +1,18 @@ +package org.ultramine.scheduler; + +import org.ultramine.scheduler.pattern.SchedulingPattern; +import org.ultramine.server.util.GlobalExecutors; + +public class ScheduledAsyncTask extends ScheduledTask +{ + ScheduledAsyncTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) + { + super(sceduler, pattern, task); + } + + @Override + void launch() + { + GlobalExecutors.cachedIO().execute(task); + } +} diff --git a/src/main/java/org/ultramine/scheduler/ScheduledSyncTask.java b/src/main/java/org/ultramine/scheduler/ScheduledSyncTask.java new file mode 100644 index 0000000..440c00e --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/ScheduledSyncTask.java @@ -0,0 +1,17 @@ +package org.ultramine.scheduler; + +import org.ultramine.scheduler.pattern.SchedulingPattern; + +public class ScheduledSyncTask extends ScheduledTask +{ + ScheduledSyncTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) + { + super(sceduler, pattern, task); + } + + @Override + void launch() + { + sceduler.addToSyncQueue(task); + } +} \ No newline at end of file diff --git a/src/main/java/org/ultramine/scheduler/ScheduledTask.java b/src/main/java/org/ultramine/scheduler/ScheduledTask.java new file mode 100644 index 0000000..8d190ae --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/ScheduledTask.java @@ -0,0 +1,29 @@ +package org.ultramine.scheduler; + +import org.ultramine.scheduler.pattern.SchedulingPattern; + +public abstract class ScheduledTask +{ + protected final Scheduler sceduler; + protected final SchedulingPattern pattern; + protected final Runnable task; + + protected ScheduledTask(Scheduler sceduler, SchedulingPattern pattern, Runnable task) + { + this.sceduler = sceduler; + this.pattern = pattern; + this.task = task; + } + + public boolean cancel() + { + return sceduler.cancelTask(this); + } + + public boolean canRun(long millis) + { + return pattern.match(millis); + } + + abstract void launch(); +} diff --git a/src/main/java/org/ultramine/scheduler/Scheduler.java b/src/main/java/org/ultramine/scheduler/Scheduler.java new file mode 100644 index 0000000..2374e01 --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/Scheduler.java @@ -0,0 +1,132 @@ +package org.ultramine.scheduler; + +import java.util.List; +import java.util.Queue; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.ultramine.scheduler.pattern.SchedulingPattern; + +import com.google.common.collect.Queues; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.common.gameevent.TickEvent.Phase; + +public class Scheduler +{ + private final List tasks = new CopyOnWriteArrayList(); + private final Queue toSyncExec = Queues.newConcurrentLinkedQueue(); + private final SchedulerThread thread = new SchedulerThread(); + + private volatile boolean isRunning = true; + + public Scheduler() + { + + } + + public void start() + { + thread.setDaemon(true); + thread.start(); + FMLCommonHandler.instance().bus().register(this); + } + + public void stop() + { + isRunning = false; + thread.interrupt(); + tasks.clear(); + FMLCommonHandler.instance().bus().unregister(this); + } + + public ScheduledTask scheduleSync(String pattern, Runnable run) + { + ScheduledSyncTask task = new ScheduledSyncTask(this, new SchedulingPattern(pattern), run); + tasks.add(task); + return task; + } + + public ScheduledTask scheduleAsync(String pattern, Runnable run) + { + ScheduledAsyncTask task = new ScheduledAsyncTask(this, new SchedulingPattern(pattern), run); + tasks.add(task); + return task; + } + + boolean cancelTask(ScheduledTask task) + { + return tasks.remove(task); + } + + void addToSyncQueue(Runnable task) + { + toSyncExec.add(task); + } + + private void launchTasks(long millis) + { + for(ScheduledTask task : tasks) + { + if(task.canRun(millis)) + task.launch(); + } + } + + @SubscribeEvent + public void onTick(TickEvent.ServerTickEvent e) + { + if(e.phase == Phase.END) + { + for(Runnable task; (task = toSyncExec.poll()) != null;) + task.run(); + } + } + + private class SchedulerThread extends Thread + { + public SchedulerThread() + { + super("UM Scheduler thread"); + } + + private void safeSleep(long millis) throws InterruptedException + { + long done = 0; + do + { + long before = System.currentTimeMillis(); + sleep(millis - done); + long after = System.currentTimeMillis(); + done += (after - before); + } + while(done < millis); + } + + @Override + public void run() + { + long millis = System.currentTimeMillis(); + long nextMinute = ((millis / 60000) + 1) * 60000; + while(isRunning) + { + long sleepTime = (nextMinute - System.currentTimeMillis()); + if(sleepTime > 0) + { + try + { + safeSleep(sleepTime); + } + catch(InterruptedException e) + { + continue; + } + } + millis = System.currentTimeMillis(); + launchTasks(millis); + nextMinute = ((millis / 60000) + 1) * 60000; + } + } + } +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/AlwaysTrueValueMatcher.java b/src/main/java/org/ultramine/scheduler/pattern/AlwaysTrueValueMatcher.java new file mode 100644 index 0000000..5efc7fa --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/AlwaysTrueValueMatcher.java @@ -0,0 +1,10 @@ +package org.ultramine.scheduler.pattern; + +class AlwaysTrueValueMatcher implements IValueMatcher +{ + @Override + public boolean match(int value) + { + return true; + } +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/DayOfMonthValueMatcher.java b/src/main/java/org/ultramine/scheduler/pattern/DayOfMonthValueMatcher.java new file mode 100644 index 0000000..5aa697c --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/DayOfMonthValueMatcher.java @@ -0,0 +1,41 @@ +package org.ultramine.scheduler.pattern; + +import gnu.trove.set.TIntSet; + +/** + *

+ * A ValueMatcher whose rules are in a set of integer values. When asked + * to validate a value, this ValueMatcher checks if it is in the set and, if + * not, checks whether the last-day-of-month setting applies. + *

+ */ +class DayOfMonthValueMatcher extends IntSetValueMatcher +{ + private static final int[] lastDays = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + public DayOfMonthValueMatcher(TIntSet integers) + { + super(integers); + } + + /** + * Returns true if the given value is included in the matcher list or the + * last-day-of-month setting applies. + */ + public boolean match(int value, int month, boolean isLeapYear) + { + return (super.match(value) || (value > 27 && match(32) && isLastDayOfMonth(value, month, isLeapYear))); + } + + public boolean isLastDayOfMonth(int value, int month, boolean isLeapYear) + { + if(isLeapYear && month == 2) + { + return value == 29; + } + else + { + return value == lastDays[month - 1]; + } + } +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/IValueMatcher.java b/src/main/java/org/ultramine/scheduler/pattern/IValueMatcher.java new file mode 100644 index 0000000..a60b9a9 --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/IValueMatcher.java @@ -0,0 +1,20 @@ +package org.ultramine.scheduler.pattern; + +/** + *

+ * This interface describes the ValueMatcher behavior. A ValueMatcher is an + * object that validate an integer value against a set of rules. + *

+ */ +interface IValueMatcher +{ + /** + * Validate the given integer value against a set of rules. + * + * @param value + * The value. + * @return true if the given value matches the rules of the ValueMatcher, + * false otherwise. + */ + boolean match(int value); +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/IntSetValueMatcher.java b/src/main/java/org/ultramine/scheduler/pattern/IntSetValueMatcher.java new file mode 100644 index 0000000..ed9035b --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/IntSetValueMatcher.java @@ -0,0 +1,19 @@ +package org.ultramine.scheduler.pattern; + +import gnu.trove.set.TIntSet; + +class IntSetValueMatcher implements IValueMatcher +{ + private final TIntSet values; + + public IntSetValueMatcher(TIntSet values) + { + this.values = values; + } + + @Override + public boolean match(int value) + { + return values.contains(value); + } +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/InvalidPatternException.java b/src/main/java/org/ultramine/scheduler/pattern/InvalidPatternException.java new file mode 100644 index 0000000..070f790 --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/InvalidPatternException.java @@ -0,0 +1,21 @@ +package org.ultramine.scheduler.pattern; + +/** + *

+ * This kind of exception is thrown if an invalid scheduling pattern is + * encountered by the scheduler. + *

+ */ +public class InvalidPatternException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + InvalidPatternException() + { + } + + InvalidPatternException(String message) + { + super(message); + } +} diff --git a/src/main/java/org/ultramine/scheduler/pattern/SchedulingPattern.java b/src/main/java/org/ultramine/scheduler/pattern/SchedulingPattern.java new file mode 100644 index 0000000..00bdccf --- /dev/null +++ b/src/main/java/org/ultramine/scheduler/pattern/SchedulingPattern.java @@ -0,0 +1,718 @@ +package org.ultramine.scheduler.pattern; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.StringTokenizer; +import java.util.TimeZone; + +import org.apache.commons.lang3.StringUtils; + +import gnu.trove.list.TIntList; +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.set.TIntSet; +import gnu.trove.set.hash.TIntHashSet; + +/** + *

+ * A UNIX crontab-like pattern is a string split in five space separated parts. + * Each part is intented as: + *

+ *
    + *
  1. Minutes sub-pattern. During which minutes of the hour + * should the task been launched? The values range is from 0 to 59.
  2. + *
  3. Hours sub-pattern. During which hours of the day should + * the task been launched? The values range is from 0 to 23.
  4. + *
  5. Days of month sub-pattern. During which days of the + * month should the task been launched? The values range is from 1 to 31. The + * special value L can be used to recognize the last day of month.
  6. + *
  7. Months sub-pattern. During which months of the year + * should the task been launched? The values range is from 1 (January) to 12 + * (December), otherwise this sub-pattern allows the aliases "jan", + * "feb", "mar", "apr", "may", + * "jun", "jul", "aug", "sep", + * "oct", "nov" and "dec".
  8. + *
  9. Days of week sub-pattern. During which days of the week + * should the task been launched? The values range is from 0 (Sunday) to 6 + * (Saturday), otherwise this sub-pattern allows the aliases "sun", + * "mon", "tue", "wed", "thu", + * "fri" and "sat".
  10. + *
+ *

+ * The star wildcard character is also admitted, indicating "every minute + * of the hour", "every hour of the day", "every day of the + * month", "every month of the year" and "every day of the + * week", according to the sub-pattern in which it is used. + *

+ *

+ * Once the scheduler is started, a task will be launched when the five parts in + * its scheduling pattern will be true at the same time. + *

+ *

+ * Some examples: + *

+ *

+ * 5 * * * *
+ * This pattern causes a task to be launched once every hour, at the begin of + * the fifth minute (00:05, 01:05, 02:05 etc.). + *

+ *

+ * * * * * *
+ * This pattern causes a task to be launched every minute. + *

+ *

+ * * 12 * * Mon
+ * This pattern causes a task to be launched every minute during the 12th hour + * of Monday. + *

+ *

+ * * 12 16 * Mon
+ * This pattern causes a task to be launched every minute during the 12th hour + * of Monday, 16th, but only if the day is the 16th of the month. + *

+ *

+ * Every sub-pattern can contain two or more comma separated values. + *

+ *

+ * 59 11 * * 1,2,3,4,5
+ * This pattern causes a task to be launched at 11:59AM on Monday, Tuesday, + * Wednesday, Thursday and Friday. + *

+ *

+ * Values intervals are admitted and defined using the minus character. + *

+ *

+ * 59 11 * * 1-5
+ * This pattern is equivalent to the previous one. + *

+ *

+ * The slash character can be used to identify step values within a range. It + * can be used both in the form */c and a-b/c. The + * subpattern is matched every c values of the range + * 0,maxvalue or a-b. + *

+ *

+ * */5 * * * *
+ * This pattern causes a task to be launched every 5 minutes (0:00, 0:05, 0:10, + * 0:15 and so on). + *

+ *

+ * 3-18/5 * * * *
+ * This pattern causes a task to be launched every 5 minutes starting from the + * third minute of the hour, up to the 18th (0:03, 0:08, 0:13, 0:18, 1:03, 1:08 + * and so on). + *

+ *

+ * */15 9-17 * * *
+ * This pattern causes a task to be launched every 15 minutes between the 9th + * and 17th hour of the day (9:00, 9:15, 9:30, 9:45 and so on... note that the + * last execution will be at 17:45). + *

+ *

+ * All the fresh described syntax rules can be used together. + *

+ *

+ * * 12 10-16/2 * *
+ * This pattern causes a task to be launched every minute during the 12th hour + * of the day, but only if the day is the 10th, the 12th, the 14th or the 16th + * of the month. + *

+ *

+ * * 12 1-15,17,20-25 * *
+ * This pattern causes a task to be launched every minute during the 12th hour + * of the day, but the day of the month must be between the 1st and the 15th, + * the 20th and the 25, or at least it must be the 17th. + *

+ *

+ * Finally cron4j lets you combine more scheduling patterns into one, with the + * pipe character: + *

+ *

+ * 0 5 * * *|8 10 * * *|22 17 * * *
+ * This pattern causes a task to be launched every day at 05:00, 10:08 and + * 17:22. + *

+ */ +public class SchedulingPattern +{ + private static final IValueParser MINUTE_VALUE_PARSER = new MinuteValueParser(); + private static final IValueParser HOUR_VALUE_PARSER = new HourValueParser(); + private static final IValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser(); + private static final IValueParser MONTH_VALUE_PARSER = new MonthValueParser(); + private static final IValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser(); + + /** The pattern as a string. */ + private String asString; + + protected ArrayList valueMatchers = new ArrayList(); + + /** + * Builds a SchedulingPattern parsing it from a string. + * + * @param pattern + * The pattern as a crontab-like string. + * @throws InvalidPatternException + * If the supplied string is not a valid pattern. + */ + public SchedulingPattern(String pattern) throws InvalidPatternException + { + this.asString = pattern; + StringTokenizer st1 = new StringTokenizer(pattern, "|"); + if(st1.countTokens() < 1) + { + throw new InvalidPatternException("invalid pattern: \"" + pattern + "\""); + } + while(st1.hasMoreTokens()) + { + String localPattern = st1.nextToken(); + StringTokenizer st2 = new StringTokenizer(localPattern, " \t"); + if(st2.countTokens() != 5) + { + throw new InvalidPatternException("invalid pattern: \"" + localPattern + "\""); + } + ValueMatcherCollection matcher = new ValueMatcherCollection(); + try + { + matcher.minute = buildValueMatcher(st2.nextToken(), MINUTE_VALUE_PARSER); + } + catch(Exception e) + { + throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing minutes field: " + e.getMessage() + "."); + } + try + { + matcher.hour = buildValueMatcher(st2.nextToken(), HOUR_VALUE_PARSER); + } + catch(Exception e) + { + throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing hours field: " + e.getMessage() + "."); + } + try + { + matcher.dayOfMonth = buildValueMatcher(st2.nextToken(), DAY_OF_MONTH_VALUE_PARSER); + } + catch(Exception e) + { + throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of month field: " + e.getMessage() + "."); + } + try + { + matcher.month = buildValueMatcher(st2.nextToken(), MONTH_VALUE_PARSER); + } + catch(Exception e) + { + throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing months field: " + e.getMessage() + "."); + } + try + { + matcher.dayOfWeek = buildValueMatcher(st2.nextToken(), DAY_OF_WEEK_VALUE_PARSER); + } + catch(Exception e) + { + throw new InvalidPatternException("invalid pattern \"" + localPattern + "\". Error parsing days of week field: " + e.getMessage() + "."); + } + valueMatchers.add(matcher); + } + } + + /** + * A ValueMatcher utility builder. + * + * @param str + * The pattern part for the ValueMatcher creation. + * @param parser + * The parser used to parse the values. + * @return The requested ValueMatcher. + * @throws Exception + * If the supplied pattern part is not valid. + */ + private IValueMatcher buildValueMatcher(String str, IValueParser parser) throws Exception + { + if(str.length() == 1 && str.equals("*")) + { + return new AlwaysTrueValueMatcher(); + } + TIntSet values = new TIntHashSet(); + String[] parts = StringUtils.split(str, ','); + for(int i = 0; i < parts.length; i++) + { + String element = parts[i]; + TIntList local; + try + { + local = parseListElement(element, parser); + } + catch(Exception e) + { + throw new Exception("invalid field \"" + str + "\", invalid element \"" + element + "\", " + e.getMessage()); + } + values.addAll(local); + } + if(values.size() == 0) + { + throw new Exception("invalid field \"" + str + "\""); + } + if(parser == DAY_OF_MONTH_VALUE_PARSER) + { + return new DayOfMonthValueMatcher(values); + } + else + { + return new IntSetValueMatcher(values); + } + } + + /** + * Parses an element of a list of values of the pattern. + * + * @param str + * The element string. + * @param parser + * The parser used to parse the values. + * @return A list of integers representing the allowed values. + * @throws Exception + * If the supplied pattern part is not valid. + */ + private TIntList parseListElement(String str, IValueParser parser) throws Exception + { + String[] parts = StringUtils.split(str, '/'); + if(parts.length < 1 || parts.length > 2) + { + throw new Exception("syntax error"); + } + TIntList values; + try + { + values = parseRange(parts[0], parser); + } + catch(Exception e) + { + throw new Exception("invalid range, " + e.getMessage()); + } + if(parts.length == 2) + { + String dStr = parts[1]; + int div; + try + { + div = Integer.parseInt(dStr); + } + catch(NumberFormatException e) + { + throw new Exception("invalid divisor \"" + dStr + "\""); + } + if(div < 1) + { + throw new Exception("non positive divisor \"" + div + "\""); + } + TIntList values2 = new TIntArrayList(values.size()/div+1); + for(int i = 0; i < values.size(); i += div) + { + values2.add(values.get(i)); + } + return values2; + } + else + { + return values; + } + } + + /** + * Parses a range of values. + * + * @param str + * The range string. + * @param parser + * The parser used to parse the values. + * @return A list of integers representing the allowed values. + * @throws Exception + * If the supplied pattern part is not valid. + */ + private TIntList parseRange(String str, IValueParser parser) throws Exception + { + if(str.equals("*")) + { + int min = parser.getMinValue(); + int max = parser.getMaxValue(); + TIntList values = new TIntArrayList(); + for(int i = min; i <= max; i++) + { + values.add(i); + } + return values; + } + String[] parts = StringUtils.split(str, '-'); + if(parts.length < 1 || parts.length > 2) + { + throw new Exception("syntax error"); + } + String v1Str = parts[0]; + int v1; + try + { + v1 = parser.parse(v1Str); + } + catch(Exception e) + { + throw new Exception("invalid value \"" + v1Str + "\", " + e.getMessage()); + } + if(parts.length == 1) + { + TIntList values = new TIntArrayList(); + values.add(v1); + return values; + } + else + { + String v2Str = parts[1]; + int v2; + try + { + v2 = parser.parse(v2Str); + } + catch(Exception e) + { + throw new Exception("invalid value \"" + v2Str + "\", " + e.getMessage()); + } + TIntList values = new TIntArrayList(); + if(v1 < v2) + { + for(int i = v1; i <= v2; i++) + { + values.add(i); + } + } + else if(v1 > v2) + { + int min = parser.getMinValue(); + int max = parser.getMaxValue(); + for(int i = v1; i <= max; i++) + { + values.add(i); + } + for(int i = min; i <= v2; i++) + { + values.add(i); + } + } + else + { + // v1 == v2 + values.add(v1); + } + return values; + } + } + + /** + * This methods returns true if the given timestamp (expressed as a UNIX-era + * millis value) matches the pattern, according to the given time zone. + * + * @param timezone + * A time zone. + * @param millis + * The timestamp, as a UNIX-era millis value. + * @return true if the given timestamp matches the pattern. + */ + public boolean match(TimeZone timezone, long millis) + { + GregorianCalendar gc = new GregorianCalendar(); + gc.setTimeInMillis(millis); + if(timezone != null) + gc.setTimeZone(timezone); + int minute = gc.get(Calendar.MINUTE); + int hour = gc.get(Calendar.HOUR_OF_DAY); + int dayOfMonth = gc.get(Calendar.DAY_OF_MONTH); + int month = gc.get(Calendar.MONTH) + 1; + int dayOfWeek = gc.get(Calendar.DAY_OF_WEEK) - 1; + int year = gc.get(Calendar.YEAR); + for(int i = 0; i < valueMatchers.size(); i++) + { + ValueMatcherCollection matcher = valueMatchers.get(i); + boolean eval = matcher.minute.match(minute) + && matcher.hour.match(hour) && ((matcher.dayOfMonth instanceof DayOfMonthValueMatcher) + ? ((DayOfMonthValueMatcher) matcher.dayOfMonth).match(dayOfMonth, month, gc.isLeapYear(year)) : matcher.dayOfMonth.match(dayOfMonth)) + && matcher.month.match(month) && matcher.dayOfWeek.match(dayOfWeek); + if(eval) + return true; + } + return false; + } + + /** + * This methods returns true if the given timestamp (expressed as a UNIX-era + * millis value) matches the pattern, according to the system default time + * zone. + * + * @param millis + * The timestamp, as a UNIX-era millis value. + * @return true if the given timestamp matches the pattern. + */ + public boolean match(long millis) + { + return match(null, millis); + } + + /** + * Returns the pattern as a string. + * + * @return The pattern as a string. + */ + public String toString() + { + return asString; + } + + /** + * This utility method changes an alias to an int value. + * + * @param value + * The value. + * @param aliases + * The aliases list. + * @param offset + * The offset appplied to the aliases list indices. + * @return The parsed value. + * @throws Exception + * If the expressed values doesn't match any alias. + */ + private static int parseAlias(String value, String[] aliases, int offset) throws Exception + { + for(int i = 0; i < aliases.length; i++) + { + if(aliases[i].equalsIgnoreCase(value)) + { + return offset + i; + } + } + throw new Exception("invalid alias \"" + value + "\""); + } + + /** + * Definition for a value parser. + */ + private static interface IValueParser + { + /** + * Attempts to parse a value. + * + * @param value + * The value. + * @return The parsed value. + * @throws Exception + * If the value can't be parsed. + */ + int parse(String value) throws Exception; + + /** + * Returns the minimum value accepred by the parser. + * + * @return The minimum value accepred by the parser. + */ + int getMinValue(); + + /** + * Returns the maximum value accepred by the parser. + * + * @return The maximum value accepred by the parser. + */ + int getMaxValue(); + } + + /** + * A simple value parser. + */ + private static class SimpleValueParser implements IValueParser + { + /** + * The minimum allowed value. + */ + protected int minValue; + + /** + * The maximum allowed value. + */ + protected int maxValue; + + /** + * Builds the value parser. + * + * @param minValue + * The minimum allowed value. + * @param maxValue + * The maximum allowed value. + */ + public SimpleValueParser(int minValue, int maxValue) + { + this.minValue = minValue; + this.maxValue = maxValue; + } + + public int parse(String value) throws Exception + { + int i; + try + { + i = Integer.parseInt(value); + } + catch(NumberFormatException e) + { + throw new Exception("invalid integer value"); + } + if(i < minValue || i > maxValue) + { + throw new Exception("value out of range"); + } + return i; + } + + public int getMinValue() + { + return minValue; + } + + public int getMaxValue() + { + return maxValue; + } + } + + /** + * The minutes value parser. + */ + private static class MinuteValueParser extends SimpleValueParser + { + /** + * Builds the value parser. + */ + public MinuteValueParser() + { + super(0, 59); + } + } + + /** + * The hours value parser. + */ + private static class HourValueParser extends SimpleValueParser + { + /** + * Builds the value parser. + */ + public HourValueParser() + { + super(0, 23); + } + } + + /** + * The days of month value parser. + */ + private static class DayOfMonthValueParser extends SimpleValueParser + { + /** + * Builds the value parser. + */ + public DayOfMonthValueParser() + { + super(1, 31); + } + + /** + * Added to support last-day-of-month. + * + * @param value + * The value to be parsed + * @return the integer day of the month or 32 for last day of the month + * @throws Exception + * if the input value is invalid + */ + public int parse(String value) throws Exception + { + if(value.equalsIgnoreCase("L")) + { + return 32; + } + else + { + return super.parse(value); + } + } + } + + /** + * The value parser for the months field. + */ + private static class MonthValueParser extends SimpleValueParser + { + /** + * Months aliases. + */ + private static String[] ALIASES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; + + /** + * Builds the months value parser. + */ + public MonthValueParser() + { + super(1, 12); + } + + public int parse(String value) throws Exception + { + try + { + // try as a simple value + return super.parse(value); + } + catch(Exception e) + { + // try as an alias + return parseAlias(value, ALIASES, 1); + } + } + } + + /** + * The value parser for the months field. + */ + private static class DayOfWeekValueParser extends SimpleValueParser + { + /** + * Days of week aliases. + */ + private static String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; + + /** + * Builds the months value parser. + */ + public DayOfWeekValueParser() + { + super(0, 7); + } + + public int parse(String value) throws Exception + { + try + { + // try as a simple value + return super.parse(value) % 7; + } + catch(Exception e) + { + // try as an alias + return parseAlias(value, ALIASES, 0); + } + } + } + + private static class ValueMatcherCollection + { + IValueMatcher minute; + IValueMatcher hour; + IValueMatcher dayOfMonth; + IValueMatcher month; + IValueMatcher dayOfWeek; + } +}