diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 1f5c5f9..45fb7ac 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -81,6 +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.server.BackupManager; import org.ultramine.server.ConfigurationHandler; import org.ultramine.server.WatchdogThread; @@ -1506,6 +1507,7 @@ public final long startTime = System.currentTimeMillis(); private final MultiWorld multiworld = new MultiWorld(this); private IPermissionManager permissionManager; + private final Scheduler scheduler = new Scheduler(); public MultiWorld getMultiWorld() { @@ -1527,6 +1529,11 @@ this.permissionManager = permissionManager; } + public Scheduler getScheduler() + { + return scheduler; + } + public File getWorldsDir() { return anvilFile; diff --git a/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java b/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java new file mode 100644 index 0000000..0bae8e5 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/ScheduledAsyncTask.java @@ -0,0 +1,18 @@ +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.cachedExecutor().execute(task); + } +} diff --git a/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java b/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java new file mode 100644 index 0000000..f578b76 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/ScheduledSyncTask.java @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..b698030 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/ScheduledTask.java @@ -0,0 +1,29 @@ +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 new file mode 100644 index 0000000..30a07bf --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/Scheduler.java @@ -0,0 +1,132 @@ +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() + { + scheduleSync("* * * * *", new Runnable(){public void run(){System.out.println(Thread.currentThread().getName());}}); + } + + 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 new file mode 100644 index 0000000..8dd82b2 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/AlwaysTrueValueMatcher.java @@ -0,0 +1,10 @@ +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 new file mode 100644 index 0000000..2073d29 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/DayOfMonthValueMatcher.java @@ -0,0 +1,41 @@ +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 new file mode 100644 index 0000000..f06ad05 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/IValueMatcher.java @@ -0,0 +1,20 @@ +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 new file mode 100644 index 0000000..310948e --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/IntSetValueMatcher.java @@ -0,0 +1,19 @@ +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 new file mode 100644 index 0000000..7adae6d --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/InvalidPatternException.java @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..5cf2219 --- /dev/null +++ b/src/main/java/org/ultramine/sceduler/pattern/SchedulingPattern.java @@ -0,0 +1,718 @@ +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/server/UltramineServerModContainer.java b/src/main/java/org/ultramine/server/UltramineServerModContainer.java index 18707fc..0a8b052 100644 --- a/src/main/java/org/ultramine/server/UltramineServerModContainer.java +++ b/src/main/java/org/ultramine/server/UltramineServerModContainer.java @@ -165,6 +165,7 @@ buttonCommand.load(e); itemBlocker.load(); MinecraftForge.EVENT_BUS.register(new WarpProtection()); + e.getServer().getScheduler().start(); } } @@ -190,7 +191,10 @@ ChunkProfiler.instance().setEnabled(false); if(e.getSide().isServer()) + { buttonCommand.unload(); + MinecraftServer.getServer().getScheduler().stop(); + } } @Subscribe