From 09dafa9dce78294cb3d70f9eaac4d17d3f078a15 Mon Sep 17 00:00:00 2001
From: MobiusDev <8391001+MobiusDevelopment@users.noreply.github.com>
Date: Tue, 8 Aug 2017 12:19:36 +0000
Subject: [PATCH] Complete set of classes for cron4j.
---
.../util/cron4j/AlwaysTrueValueMatcher.java | 22 +-
.../gameserver/util/cron4j/CronParser.java | 606 +++++++++++++++
.../util/cron4j/DayOfMonthValueMatcher.java | 22 +-
.../util/cron4j/FileTaskCollector.java | 104 +++
.../gameserver/util/cron4j/GUIDGenerator.java | 244 ++++++
.../util/cron4j/IntArrayValueMatcher.java | 22 +-
.../util/cron4j/InvalidPatternException.java | 22 +-
.../util/cron4j/LauncherThread.java | 100 +++
.../util/cron4j/MemoryTaskCollector.java | 152 ++++
.../gameserver/util/cron4j/Predictor.java | 22 +-
.../gameserver/util/cron4j/ProcessTask.java | 420 ++++++++++
.../gameserver/util/cron4j/RunnableTask.java | 76 ++
.../gameserver/util/cron4j/Scheduler.java | 728 ++++++++++++++++++
.../util/cron4j/SchedulerListener.java | 48 ++
.../util/cron4j/SchedulingPattern.java | 22 +-
.../cron4j/SchedulingPatternValidator.java | 52 ++
.../util/cron4j/StaticMethodTask.java | 107 +++
.../gameserver/util/cron4j/StreamBridge.java | 213 +++++
.../gameserver/util/cron4j/Task.java | 144 ++++
.../gameserver/util/cron4j/TaskCollector.java | 37 +
.../util/cron4j/TaskExecutionContext.java | 67 ++
.../gameserver/util/cron4j/TaskExecutor.java | 612 +++++++++++++++
.../util/cron4j/TaskExecutorListener.java | 67 ++
.../gameserver/util/cron4j/TaskTable.java | 102 +++
.../gameserver/util/cron4j/TimerThread.java | 117 +++
.../gameserver/util/cron4j/ValueMatcher.java | 22 +-
.../util/cron4j/AlwaysTrueValueMatcher.java | 22 +-
.../gameserver/util/cron4j/CronParser.java | 606 +++++++++++++++
.../util/cron4j/DayOfMonthValueMatcher.java | 22 +-
.../util/cron4j/FileTaskCollector.java | 104 +++
.../gameserver/util/cron4j/GUIDGenerator.java | 244 ++++++
.../util/cron4j/IntArrayValueMatcher.java | 22 +-
.../util/cron4j/InvalidPatternException.java | 22 +-
.../util/cron4j/LauncherThread.java | 100 +++
.../util/cron4j/MemoryTaskCollector.java | 152 ++++
.../gameserver/util/cron4j/Predictor.java | 22 +-
.../gameserver/util/cron4j/ProcessTask.java | 420 ++++++++++
.../gameserver/util/cron4j/RunnableTask.java | 76 ++
.../gameserver/util/cron4j/Scheduler.java | 728 ++++++++++++++++++
.../util/cron4j/SchedulerListener.java | 48 ++
.../util/cron4j/SchedulingPattern.java | 22 +-
.../cron4j/SchedulingPatternValidator.java | 52 ++
.../util/cron4j/StaticMethodTask.java | 107 +++
.../gameserver/util/cron4j/StreamBridge.java | 213 +++++
.../gameserver/util/cron4j/Task.java | 144 ++++
.../gameserver/util/cron4j/TaskCollector.java | 37 +
.../util/cron4j/TaskExecutionContext.java | 67 ++
.../gameserver/util/cron4j/TaskExecutor.java | 612 +++++++++++++++
.../util/cron4j/TaskExecutorListener.java | 67 ++
.../gameserver/util/cron4j/TaskTable.java | 102 +++
.../gameserver/util/cron4j/TimerThread.java | 117 +++
.../gameserver/util/cron4j/ValueMatcher.java | 22 +-
.../util/cron4j/AlwaysTrueValueMatcher.java | 22 +-
.../gameserver/util/cron4j/CronParser.java | 606 +++++++++++++++
.../util/cron4j/DayOfMonthValueMatcher.java | 22 +-
.../util/cron4j/FileTaskCollector.java | 104 +++
.../gameserver/util/cron4j/GUIDGenerator.java | 244 ++++++
.../util/cron4j/IntArrayValueMatcher.java | 22 +-
.../util/cron4j/InvalidPatternException.java | 22 +-
.../util/cron4j/LauncherThread.java | 100 +++
.../util/cron4j/MemoryTaskCollector.java | 152 ++++
.../gameserver/util/cron4j/Predictor.java | 22 +-
.../gameserver/util/cron4j/ProcessTask.java | 420 ++++++++++
.../gameserver/util/cron4j/RunnableTask.java | 76 ++
.../gameserver/util/cron4j/Scheduler.java | 728 ++++++++++++++++++
.../util/cron4j/SchedulerListener.java | 48 ++
.../util/cron4j/SchedulingPattern.java | 22 +-
.../cron4j/SchedulingPatternValidator.java | 52 ++
.../util/cron4j/StaticMethodTask.java | 107 +++
.../gameserver/util/cron4j/StreamBridge.java | 213 +++++
.../gameserver/util/cron4j/Task.java | 144 ++++
.../gameserver/util/cron4j/TaskCollector.java | 37 +
.../util/cron4j/TaskExecutionContext.java | 67 ++
.../gameserver/util/cron4j/TaskExecutor.java | 612 +++++++++++++++
.../util/cron4j/TaskExecutorListener.java | 67 ++
.../gameserver/util/cron4j/TaskTable.java | 102 +++
.../gameserver/util/cron4j/TimerThread.java | 117 +++
.../gameserver/util/cron4j/ValueMatcher.java | 22 +-
78 files changed, 12240 insertions(+), 210 deletions(-)
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/CronParser.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/FileTaskCollector.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/GUIDGenerator.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/LauncherThread.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/MemoryTaskCollector.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/ProcessTask.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPatternValidator.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/StreamBridge.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Task.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutorListener.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskTable.java
create mode 100644 L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TimerThread.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/CronParser.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/FileTaskCollector.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/GUIDGenerator.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/LauncherThread.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/MemoryTaskCollector.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/ProcessTask.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPatternValidator.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/StreamBridge.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Task.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutorListener.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskTable.java
create mode 100644 L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TimerThread.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/CronParser.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/FileTaskCollector.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/GUIDGenerator.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/LauncherThread.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/MemoryTaskCollector.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/ProcessTask.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPatternValidator.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/StreamBridge.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Task.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutorListener.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskTable.java
create mode 100644 L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TimerThread.java
diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/AlwaysTrueValueMatcher.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/AlwaysTrueValueMatcher.java
index a2a358fda8..6a88ca02fe 100644
--- a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/AlwaysTrueValueMatcher.java
+++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/AlwaysTrueValueMatcher.java
@@ -1,18 +1,20 @@
/*
- * This file is part of the L2J Mobius project.
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
*
* This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see
+ * A parser for crontab-like formatted files and streams. + *
+ *+ * If you want to schedule a list of tasks declared in a crontab-like file you don't need the CronParser, since you can do it by adding the file to the scheduler, with the {@link Scheduler#scheduleFile(File)} method. + *
+ *+ * Consider to use the CronParser if the {@link Scheduler#scheduleFile(File)} method is not enough for you. In example, you may need to fetch the task list from a remote source which is not representable as a {@link File} object (a document on a remote server, a DBMS result set and so on). To solve + * the problem you can implement your own {@link TaskCollector}, getting the advantage of the CronParser to parse easily any crontab-like content. + *
+ *+ * You can parse a whole file/stream, but you can also parse a single line. + *
+ *+ * A line can be empty, can contain a comment or it can be a scheduling line. + *
+ *+ * A line containing no characters or a line with only space characters is considered an empty line. + *
+ *+ * A line whose first non-space character is a number sign (#) is considered a comment. + *
+ *+ * Empty lines and comment lines are ignored by the parser. + *
+ *+ * Any other kind of line is parsed as a scheduling line. + *
+ *+ * A valid scheduling line respects the following structure: + *
+ * + *+ * scheduling-pattern [options] command [args] + *+ *
+ * After the scheduling pattern item, other tokens in each line are space separated or delimited with double quotation marks ("). + *
+ *+ * Double quotation marks delimited items can take advantage of the following escape sequences: + *
+ *+ * The options token collection can include one or more of the following elements: + *
+ *+ * It is also possible to schedule the invocation of a method of a Java class in the scope of the parser ClassLoader. The method has to be static and it must accept an array of strings as its sole argument. To invoke a method of this kind the syntax is: + *
+ * + *+ * scheduling-pattern java:className#methodName [args] + *+ *
+ * The #methodName part can be omitted: in this case the main(String[]) method will be assumed. + *
+ *+ * Please note that static methods are invoked within the scheduler same JVM, without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options can't be applied. + *
+ *+ * Invalid scheduling lines are discarded without blocking the parsing procedure, but an error message is printed in the application standard error channel. + *
+ *+ * Valid examples: + *
+ * + *+ * 0 5 * * * sol.exe + * 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55 + * 0,30 4 * * * "OUT:C:\Documents and Settings\Carlo\ping.txt" ping 10.9.43.55 + * 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat "Nightly Build" + * 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2 + *+ * + * @author Carlo Pelliccia + * @since 2.0 + */ +public class CronParser +{ + /** + * Instantiation prohibited. + */ + private CronParser() + { + } + + /** + *
+ * Builds a task list reading it from a file. + *
+ *+ * The file is treated as UTF-8. If your source file is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source file are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param file The file. + * @return The task table parsed from the file. + * @throws IOException I/O error. + */ + public static TaskTable parse(File file) throws IOException + { + InputStream stream = null; + try + { + stream = new FileInputStream(file); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an URL. + *
+ *+ * Contents fetched from the URL are treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the retrieved document are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param url The URL. + * @return The task table parsed from the contents fetched from the given URL. + * @throws IOException I/O error. + */ + public static TaskTable parse(URL url) throws IOException + { + InputStream stream = null; + try + { + stream = url.openStream(); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an input stream. + *
+ *+ * The stream is treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source stream are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param stream The input stream. + * @return The task table parsed from the stream contents. + * @throws IOException I/O error. + */ + public static TaskTable parse(InputStream stream) throws IOException + { + return parse(new InputStreamReader(stream, "UTF-8")); + } + + /** + *+ * Builds a task list reading it from a reader. + *
+ *+ * Syntax and semantics errors in the source reader are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param reader The reader. + * @return The task table parsed from the contents in the reader. + * @throws IOException I/O error. + */ + public static TaskTable parse(Reader reader) throws IOException + { + TaskTable table = new TaskTable(); + BufferedReader bufferedReader = new BufferedReader(reader); + try + { + String line; + while ((line = bufferedReader.readLine()) != null) + { + try + { + parseLine(table, line); + } + catch (Exception e) + { + e.printStackTrace(); + continue; + } + } + } + finally + { + reader.close(); + } + return table; + } + + /** + * Parses a crontab-like line. + * @param table The table on which the parsed task will be stored, by side-effect. + * @param line The crontab-like line. + * @throws Exception The supplied line doesn't represent a valid task line. + */ + public static void parseLine(TaskTable table, String line) throws Exception + { + line = line.trim(); + if ((line.length() == 0) || (line.charAt(0) == '#')) + { + return; + } + // Detecting the pattern. + int size = line.length(); + String pattern = null; + for (int i = size; i >= 0; i--) + { + String aux = line.substring(0, i); + if (SchedulingPattern.validate(aux)) + { + pattern = aux; + break; + } + } + if (pattern == null) + { + throw new Exception("Invalid cron line: " + line); + } + line = line.substring(pattern.length()); + size = line.length(); + // Splitting the line + ArrayList+ * A {@link TaskCollector} implementation, reading the task list from a group of files. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class FileTaskCollector implements TaskCollector +{ + /** + * File list. + */ + private final ArrayList+ * A {@link TaskCollector} implementation managing a task list in memory. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class MemoryTaskCollector implements TaskCollector +{ + /** + * Size. + */ + private final int size = 0; + + /** + * The inner scheduling pattern list. + */ + private final ArrayList+ * A built-in {@link Task} implementation which can be used to run an external process. + *
+ * @author Carlo Pelliccia + * @since 2.1 + */ +public class ProcessTask extends Task +{ + /** + * The command to launch. + */ + private String[] command; + + /** + * Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + private String[] envs; + + /** + * Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + private File directory; + + /** + * Standard input file (optional). + */ + private File stdinFile = null; + + /** + * Standard output file (optional). + */ + private File stdoutFile = null; + + /** + * Standard error file (optional). + */ + private File stderrFile = null; + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + * @param directory Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public ProcessTask(String[] command, String[] envs, File directory) + { + this.command = command; + this.envs = envs; + this.directory = directory; + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + public ProcessTask(String[] command, String[] envs) + { + this(command, envs, null); + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + */ + public ProcessTask(String[] command) + { + this(command, null, null); + } + + /** + * Creates the task. + * @param command The command to launch. + */ + public ProcessTask(String command) + { + this(new String[] + { + command + }, null, null); + } + + /** + * Returns true. + */ + @Override + public boolean canBeStopped() + { + return true; + } + + /** + * Returns the command executed by this task. + * @return The command executed by this task. + */ + public String[] getCommand() + { + return command; + } + + /** + * Sets the command executed by this task. + * @param command The command executed by this task. + */ + public void setCommand(String[] command) + { + this.command = command; + } + + /** + * Returns the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @return The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public String[] getEnvs() + { + return envs; + } + + /** + * Sets the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @param envs The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public void setEnvs(String[] envs) + { + this.envs = envs; + } + + /** + * Resturns the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @return The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public File getDirectory() + { + return directory; + } + + /** + * Sets the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @param directory The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public void setDirectory(File directory) + { + this.directory = directory; + } + + /** + * Returns the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @return The standard input file (optional). + */ + public File getStdinFile() + { + return stdinFile; + } + + /** + * Sets the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @param stdinFile The standard input file (optional). + */ + public void setStdinFile(File stdinFile) + { + this.stdinFile = stdinFile; + } + + /** + * Sets the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @param stdoutFile The standard output file (optional). + */ + public void setStdoutFile(File stdoutFile) + { + this.stdoutFile = stdoutFile; + } + + /** + * Returns the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @return The standard output file (optional). + */ + public File getStdoutFile() + { + return stdoutFile; + } + + /** + * Sets the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @param stderrFile The standard error file (optional). + */ + public void setStderrFile(File stderrFile) + { + this.stderrFile = stderrFile; + } + + /** + * Returns the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @return The standard error file (optional). + */ + public File getStderrFile() + { + return stderrFile; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}. Runs the given command as a separate process and waits for its end. + */ + @Override + public void execute(TaskExecutionContext context) throws RuntimeException + { + Process p; + try + { + p = exec(); + } + catch (IOException e) + { + throw new RuntimeException(toString() + " cannot be started", e); + } + InputStream in = buildInputStream(stdinFile); + OutputStream out = buildOutputStream(stdoutFile); + OutputStream err = buildOutputStream(stderrFile); + if (in != null) + { + StreamBridge b = new StreamBridge(in, p.getOutputStream()); + b.start(); + } + if (out != null) + { + StreamBridge b = new StreamBridge(p.getInputStream(), out); + b.start(); + } + if (err != null) + { + StreamBridge b = new StreamBridge(p.getErrorStream(), err); + b.start(); + } + int r; + try + { + r = p.waitFor(); + } + catch (InterruptedException e) + { + throw new RuntimeException(toString() + " has been interrupted"); + } + finally + { + if (in != null) + { + try + { + in.close(); + } + catch (Throwable e) + { + } + } + if (out != null) + { + try + { + out.close(); + } + catch (Throwable e) + { + } + } + if (err != null) + { + try + { + err.close(); + } + catch (Throwable e) + { + } + } + p.destroy(); + } + if (r != 0) + { + throw new RuntimeException(toString() + " returns with error code " + r); + } + } + + /** + * Executes the command. + * @return The launched Process. + * @throws IOException If an I/O error occurs. + */ + private Process exec() throws IOException + { + Runtime rt = Runtime.getRuntime(); + Process p; + try + { + // java 1.3+ + p = rt.exec(command, envs, directory); + } + catch (NoSuchMethodError e) + { + // java 1.2 + p = rt.exec(command, envs); + } + return p; + } + + /** + * Prepares an {@link InputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private InputStream buildInputStream(File file) + { + if (file != null) + { + try + { + return new FileInputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prepares an {@link OutputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private OutputStream buildOutputStream(File file) + { + if (file != null) + { + try + { + return new FileOutputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prints in the returned string the elements contained in the given string array. + * @param arr The array. + * @return A string representing the supplied array contents. + */ + private static String listStrings(String[] arr) + { + if (arr == null) + { + return "null"; + } + StringBuffer b = new StringBuffer(); + b.append('['); + for (int i = 0; i < arr.length; i++) + { + if (i > 0) + { + b.append(", "); + } + b.append(arr[i]); + } + b.append(']'); + return b.toString(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("cmd="); + b.append(ProcessTask.listStrings(command)); + b.append(", env="); + b.append(ProcessTask.listStrings(envs)); + b.append(", "); + b.append("dir="); + b.append(directory); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java new file mode 100644 index 0000000000..0fe313c855 --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java @@ -0,0 +1,76 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A {@link Task} implementation acting as a wrapper around a {@link Runnable} object. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class RunnableTask extends Task +{ + /** + * The wrapped runnable object. + */ + private final Runnable runnable; + + /** + * Builds the task. + * @param runnable The wrapped Runnable object. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public RunnableTask(Runnable runnable) throws InvalidPatternException + { + this.runnable = runnable; + } + + /** + * Returns the wrapped Runnable object. + * @return The wrapped Runnable object. + */ + public Runnable getRunnable() + { + return runnable; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}, launching the {@link Runnable#run()} method on the wrapped object. + */ + @Override + public void execute(TaskExecutionContext context) + { + runnable.run(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("runnable="); + b.append(runnable); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java new file mode 100644 index 0000000000..d284db56ac --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java @@ -0,0 +1,728 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * The cron4j scheduler. + *
+ * @author Carlo Pelliccia + */ +public class Scheduler +{ + /** + * A GUID for this scheduler. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The time zone applied by the scheduler. + */ + private TimeZone timezone = null; + + /** + * The daemon flag. If true the scheduler and its spawned threads acts like daemons. + */ + private boolean daemon = false; + + /** + * The state flag. If true the scheduler is started and running, otherwise it is paused and no task is launched. + */ + private boolean started = false; + + /** + * Registered {@link TaskCollector}s list. + */ + private final ArrayList+ * Sets the time zone applied by the scheduler. + *
+ *+ * Current system time is adapted to the supplied time zone before comparing it with registered scheduling patterns. The result is that any supplied scheduling pattern is treated according to the specified time zone. In example, suppose: + *
+ *+ * The scheduler, before comparing system time with patterns, translates 10:00 from GMT+1 to GMT+3. It means that 10:00 becomes 12:00. The resulted time is then used by the scheduler to activate tasks. So, in the given configuration at the given moment, any task scheduled as 0 12 * * * + * will be executed, while any 0 10 * * * will not. + *
+ * @param timezone The time zone applied by the scheduler. + */ + public void setTimeZone(TimeZone timezone) + { + this.timezone = timezone; + } + + /** + * Returns the time zone applied by the scheduler. + * @return The time zone applied by the scheduler. + */ + public TimeZone getTimeZone() + { + return timezone != null ? timezone : TimeZone.getDefault(); + } + + /** + * Tests whether this scheduler is a daemon scheduler. + * @return true if this scheduler is a daemon scheduler; false otherwise. + */ + public boolean isDaemon() + { + return daemon; + } + + /** + * Marks this scheduler daemon flag. When a scheduler is marked as a daemon scheduler it spawns only daemon threads. The Java Virtual Machine exits when the only threads running are all daemon threads. This method must be called before the scheduler is started. + * @param on If true, the scheduler will spawn only daemon threads. + * @throws IllegalStateException If the scheduler is started. + */ + public void setDaemon(boolean on) throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + this.daemon = on; + } + } + + /** + * Tests if this scheduler is started. + * @return true if the scheduler is started, false if it is stopped. + */ + public boolean isStarted() + { + synchronized (lock) + { + return started; + } + } + + /** + * Adds a {@link File} instance to the scheduler. Every minute the file will be parsed. The scheduler will execute any declared task whose scheduling pattern matches the current system time. See {@link CronParser} documentation for informations about the file contents syntax. + * @param file The {@link File} instance. + */ + public void scheduleFile(File file) + { + fileTaskCollector.addFile(file); + } + + /** + * Removes a {@link File} instance previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @param file The {@link File} instance. + */ + public void descheduleFile(File file) + { + fileTaskCollector.removeFile(file); + } + + /** + * Returns an array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @return An array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + */ + public File[] getScheduledFiles() + { + return fileTaskCollector.getFiles(); + } + + /** + * Adds a custom {@link TaskCollector} instance to the scheduler. The supplied object, once added to the scheduler, will be query every minute for its task list. The scheduler will execute any of the returned tasks whose scheduling pattern matches the current system time. + * @param collector The custom {@link TaskCollector} instance. + */ + public void addTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.add(collector); + } + } + + /** + * Removes a previously registered custom {@link TaskCollector} instance. + * @param collector The custom {@link TaskCollector} instance. + */ + public void removeTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.remove(collector); + } + } + + /** + * Returns an array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + * @return An array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + */ + public TaskCollector[] getTaskCollectors() + { + synchronized (collectors) + { + // Discard the first 2 elements in the list. + int size = collectors.size() - 2; + TaskCollector[] ret = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + ret[i] = collectors.get(i + 2); + } + return ret; + } + } + + /** + * Adds a {@link SchedulerListener} to the scheduler. A {@link SchedulerListener} is notified every time a task is launching, has succeeded or has failed. + * @param listener The listener. + */ + public void addSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.add(listener); + } + } + + /** + * Removes a {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @param listener The listener. + */ + public void removeSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.remove(listener); + } + } + + /** + * Returns an array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @return An array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + */ + public SchedulerListener[] getSchedulerListeners() + { + synchronized (listeners) + { + int size = listeners.size(); + SchedulerListener[] ret = new SchedulerListener[size]; + for (int i = 0; i < size; i++) + { + ret[i] = listeners.get(i); + } + return ret; + } + } + + /** + * Returns an array containing any currently executing task, in the form of {@link TaskExecutor} objects. Each running task is executed by a different thread. A {@link TaskExecutor} object allows the control of the running task. The inner {@link Task} representation could be retrieved, the + * status of the task could be detected and the thread could be interrupted using any standard {@link Thread} method ( {@link Thread#interrupt()}, {@link Thread#isAlive() etc}. + * @return An array containing any currently executing task, in the form of {@link TaskExecutor} objects. + */ + public TaskExecutor[] getExecutingTasks() + { + synchronized (executors) + { + int size = executors.size(); + TaskExecutor[] ret = new TaskExecutor[size]; + for (int i = 0; i < size; i++) + { + ret[i] = executors.get(i); + } + return ret; + } + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public String schedule(String schedulingPattern, Runnable task) throws InvalidPatternException + { + return schedule(schedulingPattern, new RunnableTask(task)); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @since 2.0 + */ + public String schedule(String schedulingPattern, Task task) throws InvalidPatternException + { + return schedule(new SchedulingPattern(schedulingPattern), task); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @since 2.0 + */ + public String schedule(SchedulingPattern schedulingPattern, Task task) + { + return memoryTaskCollector.add(schedulingPattern, task); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @deprecated Use {@link Scheduler#reschedule(String, String)}. + */ + @Deprecated + public void reschedule(Object id, String schedulingPattern) throws InvalidPatternException + { + reschedule((String) id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public void reschedule(String id, String schedulingPattern) throws InvalidPatternException + { + reschedule(id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @since 2.0 + */ + public void reschedule(String id, SchedulingPattern schedulingPattern) + { + memoryTaskCollector.update(id, schedulingPattern); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + * @deprecated Use {@link Scheduler#deschedule(String)}. + */ + @Deprecated + public void deschedule(Object id) + { + deschedule((String) id); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + */ + public void deschedule(String id) + { + memoryTaskCollector.remove(id); + } + + /** + * This method retrieves a previously scheduled task. + * @param id The task ID. + * @return The requested task, or null if the task was not found. + * @since 2.0 + */ + public Task getTask(String id) + { + return memoryTaskCollector.getTask(id); + } + + /** + * This method retrieves a previously scheduled task scheduling pattern. + * @param id The task ID. + * @return The requested scheduling pattern, or null if the task was not found. + * @since 2.0 + */ + public SchedulingPattern getSchedulingPattern(String id) + { + return memoryTaskCollector.getSchedulingPattern(id); + } + + /** + * This method retrieves the Runnable object of a previously scheduled task. + * @param id The task ID. + * @return The Runnable object of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getTask(String)}. + */ + @Deprecated + public Runnable getTaskRunnable(Object id) + { + Task task = getTask((String) id); + if (task instanceof RunnableTask) + { + RunnableTask rt = (RunnableTask) task; + return rt.getRunnable(); + } + return null; + } + + /** + * This method retrieves the scheduling pattern of a previously scheduled task. + * @param id The task ID. + * @return The scheduling pattern of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getSchedulingPattern(String)}. + */ + @Deprecated + public String getTaskSchedulingPattern(Object id) + { + return getSchedulingPattern((String) id).toString(); + } + + /** + * Executes immediately a task, without scheduling it. + * @param task The task. + * @return The {@link TaskExecutor} executing the given task. + * @throws IllegalStateException If the scheduler is not started. + */ + public TaskExecutor launch(Task task) + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + return spawnExecutor(task); + } + } + + /** + * This method starts the scheduler. When the scheduled is started the supplied tasks are executed at the given moment. + * @throws IllegalStateException Thrown if this scheduler is already started. + */ + public void start() throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + // Initializes required lists. + launchers = new ArrayList<>(); + executors = new ArrayList<>(); + // Starts the timer thread. + timer = new TimerThread(this); + timer.setDaemon(daemon); + timer.start(); + // Change the state of the scheduler. + started = true; + } + } + + /** + * This method stops the scheduler execution. Before returning, it waits the end of all the running tasks previously launched. Once the scheduler has been stopped it can be started again with a start() call. + * @throws IllegalStateException Thrown if this scheduler is not started. + */ + public void stop() throws IllegalStateException + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + // Interrupts the timer and waits for its death. + timer.interrupt(); + tillThreadDies(timer); + timer = null; + // Interrupts any running launcher and waits for its death. + for (;;) + { + LauncherThread launcher = null; + synchronized (launchers) + { + if (launchers.size() == 0) + { + break; + } + launcher = launchers.remove(0); + } + launcher.interrupt(); + tillThreadDies(launcher); + } + launchers = null; + // Interrupts any running executor and waits for its death. + // Before exiting wait for all the active tasks end. + for (;;) + { + TaskExecutor executor = null; + synchronized (executors) + { + if (executors.size() == 0) + { + break; + } + executor = executors.remove(0); + } + if (executor.canBeStopped()) + { + executor.stop(); + } + tillExecutorDies(executor); + } + executors = null; + // Change the state of the object. + started = false; + } + } + + // -- PACKAGE RESERVED METHODS -------------------------------------------- + + /** + * Starts a launcher thread. + * @param referenceTimeInMillis Reference time in millis for the launcher. + * @return The spawned launcher. + */ + LauncherThread spawnLauncher(long referenceTimeInMillis) + { + TaskCollector[] nowCollectors; + synchronized (collectors) + { + int size = collectors.size(); + nowCollectors = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + nowCollectors[i] = collectors.get(i); + } + } + LauncherThread l = new LauncherThread(this, nowCollectors, referenceTimeInMillis); + synchronized (launchers) + { + launchers.add(l); + } + l.setDaemon(daemon); + l.start(); + return l; + } + + /** + * Starts the given task within a task executor. + * @param task The task. + * @return The spawned task executor. + */ + TaskExecutor spawnExecutor(Task task) + { + TaskExecutor e = new TaskExecutor(this, task); + synchronized (executors) + { + executors.add(e); + } + e.start(daemon); + return e; + } + + /** + * This method is called by a launcher thread to notify that the execution is completed. + * @param launcher The launcher which has completed its task. + */ + void notifyLauncherCompleted(LauncherThread launcher) + { + synchronized (launchers) + { + launchers.remove(launcher); + } + } + + /** + * This method is called by a task executor to notify that the execution is completed. + * @param executor The executor which has completed its task. + */ + void notifyExecutorCompleted(TaskExecutor executor) + { + synchronized (executors) + { + executors.remove(executor); + } + } + + /** + * Notifies every registered listener that a task is going to be launched. + * @param executor The task executor. + */ + void notifyTaskLaunching(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskLaunching(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has successfully completed. + * @param executor The task executor. + */ + void notifyTaskSucceeded(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskSucceeded(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has failed due to an uncaught exception. + * @param executor The task executor. + * @param exception The exception. + */ + void notifyTaskFailed(TaskExecutor executor, Throwable exception) + { + synchronized (listeners) + { + int size = listeners.size(); + if (size > 0) + { + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskFailed(executor, exception); + } + } + else + { + // Logs on console if no one has been notified about it. + exception.printStackTrace(); + } + } + } + + // -- PRIVATE METHODS ----------------------------------------------------- + + /** + * It waits until the given thread is dead. It is similar to {@link Thread#join()}, but this one avoids {@link InterruptedException} instances. + * @param thread The thread. + */ + private void tillThreadDies(Thread thread) + { + boolean dead = false; + do + { + try + { + thread.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + + /** + * It waits until the given task executor is dead. It is similar to {@link TaskExecutor#join()}, but this one avoids {@link InterruptedException} instances. + * @param executor The task executor. + */ + private void tillExecutorDies(TaskExecutor executor) + { + boolean dead = false; + do + { + try + { + executor.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java new file mode 100644 index 0000000000..2f186eb42f --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java @@ -0,0 +1,48 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Implement this interface and register your instance with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method to receive notifications about scheduled task executions. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface SchedulerListener +{ + /** + * This one is called by the scheduler when a task execution is starting. + * @param executor The task executor. + */ + public void taskLaunching(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has been successfully completed. + * @param executor The task executor. + */ + public void taskSucceeded(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has failed. + * @param executor The task executor. + * @param exception The exception representing the failure notification. + */ + public void taskFailed(TaskExecutor executor, Throwable exception); +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java index d4c8eaaa86..ac993fb52b 100644 --- a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see+ * A scheduling patterns validator. + *
+ *+ * The class lets you validate a scheduling pattern before/without using it with a {@link Scheduler} instance. Simply call: + *
+ * + *+ * boolean valid = SchedulingPatternValidator.validate(thePattern); + *+ *
+ * It is useful in validating user-entered patterns. + *
+ * @author Carlo Pelliccia + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ +@Deprecated +public class SchedulingPatternValidator +{ + /** + * Validates a string as a scheduling pattern. + * @param schedulingPattern The pattern to validate. + * @return true if the given string represents a valid scheduling pattern; false otherwise. + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ + @Deprecated + public static boolean validate(String schedulingPattern) + { + return SchedulingPattern.validate(schedulingPattern); + } +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java new file mode 100644 index 0000000000..bbb04bf752 --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java @@ -0,0 +1,107 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A package-reserved utility class. It spawns a secondary thread in which the supplied {@link InputStream} instance is read, and the incoming contents are written in the supplied {@link OutputStream}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class StreamBridge +{ + /** + * Used to trace alive instances. + */ + static ArrayListmillis
milliseconds for this thread to die. A timeout of 0
means to wait forever.
+ * @param millis the time to wait in milliseconds.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis) throws InterruptedException
+ {
+ thread.join(millis);
+ }
+
+ /**
+ * @param millis the time to wait in milliseconds.
+ * @param nanos 0-999999 additional nanoseconds to wait.
+ * @throws IllegalArgumentException if the value of millis is negative the value of nanos is not in the range 0-999999.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis, int nanos) throws IllegalArgumentException, InterruptedException
+ {
+ thread.join(millis, nanos);
+ }
+
+ /**
+ * Tests if this bridge is alive. A job is alive if it has been started and has not yet completed.
+ * @return true
if this thread is alive; false
otherwise.
+ */
+ public boolean isAlive()
+ {
+ return thread.isAlive();
+ }
+
+ /**
+ * Contains the routine doing the job in the secondary thread.
+ */
+ private class Runner implements Runnable
+ {
+ public Runner()
+ {
+ }
+
+ @Override
+ public void run()
+ {
+ boolean skipout = false;
+ for (;;)
+ {
+ int b;
+ try
+ {
+ b = in.read();
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ break;
+ }
+ if (b == -1)
+ {
+ break;
+ }
+ if (!skipout)
+ {
+ try
+ {
+ out.write(b);
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ skipout = true;
+ }
+ }
+ }
+ try
+ {
+ out.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ try
+ {
+ in.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ synchronized (traced)
+ {
+ traced.remove(myself);
+ }
+ }
+ }
+}
diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Task.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Task.java
new file mode 100644
index 0000000000..e46211e0f3
--- /dev/null
+++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/Task.java
@@ -0,0 +1,144 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see + * Abstract base representation of a cron4j task. + *
+ *+ * Developers can extends this abstract class to build their own tasks. + *
+ *+ * Extending Task means, above all, implementing the {@link Task#execute(TaskExecutionContext)} method. Within this method the task must perform its operation. If the execute() method returns regularly then the execution is considered to be successfully completed. If execute() dies + * throwing a {@link RuntimeException} then the task execution is considered to be failed. The supplied parameter, which is a {@link TaskExecutionContext} instance, helps the developer in integrating his task with the scheduler executor. Through the context the developer can check if the execution + * has been paused or stopped, and he can also push back some status informations by calling {@link TaskExecutionContext#setCompleteness(double)} and {@link TaskExecutionContext#setStatusMessage(String)}. + *
+ *+ * If the custom task supports pausing, stopping and/or tracking, that should be notified by overriding {@link Task#canBePaused()}, {@link Task#canBeStopped()}, {@link Task#supportsCompletenessTracking()} and/or {@link Task#supportsStatusTracking()}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public abstract class Task +{ + /** + * The ID for this task. Also used as an instance synchronization lock. + */ + private final Object id = GUIDGenerator.generate(); + + /** + * Empty constructor, does nothing. + */ + public Task() + { + } + + /** + * It returns the ID for this task. + * @return The ID for this task. + */ + Object getId() + { + return id; + } + + /** + *+ * Checks whether this task supports pause requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that pause requests are really handled. This can be done calling regularly the + * {@link TaskExecutionContext#pauseIfRequested()} method during the task execution. + *
+ * @return true if this task can be paused; false otherwise. + */ + public boolean canBePaused() + { + return false; + } + + /** + *+ * Checks whether this task supports stop requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that stop requests are really handled. This can be done checking regularly the + * {@link TaskExecutionContext#isStopped()} method during the task execution. + *
+ * @return true if this task can be stopped; false otherwise. + */ + public boolean canBeStopped() + { + return false; + } + + /** + *+ * Tests whether this task supports status tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setStatusMessage(String)} method during the task execution. + *
+ * @return true if this task, during its execution, provides status message regularly. + */ + public boolean supportsStatusTracking() + { + return false; + } + + /** + *+ * Tests whether this task supports completeness tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setCompleteness(double)} method during the task execution. + *
+ * @return true if this task, during its execution, provides a completeness value regularly. + */ + public boolean supportsCompletenessTracking() + { + return false; + } + + /** + *+ * This method is called to require a task execution, and should contain the core routine of any scheduled task. + *
+ *+ * If the execute() method ends regularly the scheduler will consider the execution successfully completed, and this will be communicated to any {@link SchedulerListener} interested in it. If the execute() method dies throwing a {@link RuntimeException} the scheduler will + * consider it as a failure notification. Any {@link SchedulerListener} will be notified about the occurred exception. + *
+ * @param context The execution context. + * @throws RuntimeException Task execution has somehow failed. + */ + public abstract void execute(TaskExecutionContext context) throws RuntimeException; +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java new file mode 100644 index 0000000000..4cb2f9fa2f --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java @@ -0,0 +1,37 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * This interface describes a task collector. Task collectors can be registered in a {@link Scheduler} instance with the {@link Scheduler#addTaskCollector(TaskCollector)} method. Any registered task collector is queried by the scheduler once a minute. The developer has to implement the + * {@link TaskCollector#getTasks()} method, returning a {@link TaskTable} whose elements has been collected with a custom logic. In example the list can be extracted from a database. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskCollector +{ + /** + * Once the instance has been registered on a {@link Scheduler} instance, with the {@link Scheduler#addTaskCollector(TaskCollector)} method, this method will be queried once a minute. It should return a custom {@link TaskTable} object. The scheduler instance will automatically iterate over the + * returned table elements, executing any task whose scheduling pattern is matching the current system time. + * @return The task table that will be automatically injected in the scheduler. + */ + public TaskTable getTasks(); +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java new file mode 100644 index 0000000000..8e0d1a8c05 --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java @@ -0,0 +1,67 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A TaskExecutionContext object provides support methods for the execution of a task. An instance of this class is always passed to the task when its {@link Task#execute(TaskExecutionContext)} method is called. The task, while executing, can use the received context to exchange informations with + * its own executor. If the task declares to supports pausing, stopping, completeness tracking and/or status tracking, it has to use its context methods to perform any declared operation (checks pause and stop requests, sends back tracking informations). + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskExecutionContext +{ + /** + * Returns the scheduler. + * @return The scheduler. + */ + public Scheduler getScheduler(); + + /** + * Returns the task executor. + * @return The task executor. + */ + public TaskExecutor getTaskExecutor(); + + /** + * Sets the current status tracking message, that has to be something about what the task is doing at the moment. + * @param message A message representing the current execution status. Null messages will be blanked. + */ + public void setStatusMessage(String message); + + /** + * Sets the completeness tracking value, that has to be between 0 and 1. + * @param completeness A completeness value, between 0 and 1. Values out of range will be ignored. + */ + public void setCompleteness(double completeness); + + /** + * If the task execution has been paused, stops until the operation is resumed. It can also returns because of a stop operation without any previous resuming. Due to this the task developer should always check the {@link TaskExecutionContext#isStopped()} value after any + * pauseIfRequested() call. Note that a task execution can be paused only if the task {@link Task#canBePaused()} method returns true. + */ + public void pauseIfRequested(); + + /** + * Checks whether the task execution has been demanded to be stopped. If the returned value is true, the task developer must shut down gracefully its task execution, as soon as possible. Note that a task execution can be stopped only if the task {@link Task#canBePaused()} method returns + * true. + * @return true if the current task execution has been demanded to be stopped; false otherwise. + */ + public boolean isStopped(); +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java new file mode 100644 index 0000000000..4911f2826c --- /dev/null +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java @@ -0,0 +1,612 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Represents a task executor, which is something similar to threads. + *
+ *+ * Each time a task is launched, a new executor is spawned, executing and watching the task + *
+ *+ * Alive task executors can be retrieved with the {@link Scheduler#getExecutingTasks()} method, and they expose method to control the ongoing execution. + *
+ * @see Scheduler#getExecutingTasks() + * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskExecutor +{ + /** + * The scheduler whose this executor belongs to. + */ + final Scheduler scheduler; + + /** + * The executed task. + */ + final Task task; + + /** + * A task execution context. + */ + final MyContext context; + + /** + * A unique ID for this executor (used also as a lock object). + */ + private final String guid = GUIDGenerator.generate(); + + /** + * An alternative to this (inner classes need it). + */ + final TaskExecutor myself = this; + + /** + * A list of {@link TaskExecutorListener} instances. + */ + private final ArrayList+ * A table coupling tasks with scheduling patterns. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskTable +{ + /** + * Table size. + */ + private int size = 0; + + /** + * Pattern list. + */ + private final ArrayList+ * TimeThreads are used by {@link Scheduler} instances. A TimerThread spends most of the time sleeping. It wakes up every minute and it requests to the scheduler the spawning of a {@link LauncherThread}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class TimerThread extends Thread +{ + /** + * A GUID for this object. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The owner scheduler. + */ + private Scheduler scheduler; + + /** + * Builds the timer thread. + * @param scheduler The owner scheduler. + */ + public TimerThread(Scheduler scheduler) + { + this.scheduler = scheduler; + // Thread name. + String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::timer[" + guid + "]"; + setName(name); + } + + /** + * Returns the GUID for this object. + * @return The GUID for this object. + */ + public Object getGuid() + { + return guid; + } + + /** + * It has been reported that the {@link Thread#sleep(long)} method sometimes exits before the requested time has passed. This one offers an alternative that sometimes could sleep a few millis more than requested, but never less. + * @param millis The length of time to sleep in milliseconds. + * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown. + * @see Thread#sleep(long) + */ + 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); + } + + /** + * Overrides {@link Thread#run()}. + */ + @Override + public void run() + { + // What time is it? + long millis = System.currentTimeMillis(); + // Calculating next minute. + long nextMinute = ((millis / 60000) + 1) * 60000; + // Work until the scheduler is started. + for (;;) + { + // Coffee break 'till next minute comes! + long sleepTime = (nextMinute - System.currentTimeMillis()); + if (sleepTime > 0) + { + try + { + safeSleep(sleepTime); + } + catch (InterruptedException e) + { + // Must exit! + break; + } + } + // What time is it? + millis = System.currentTimeMillis(); + // Launching the launching thread! + scheduler.spawnLauncher(millis); + // Calculating next minute. + nextMinute = ((millis / 60000) + 1) * 60000; + } + // Discard scheduler reference. + scheduler = null; + } +} diff --git a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java index 8e7b79e0d3..c5c1a0e5ec 100644 --- a/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java +++ b/L2J_Mobius_Classic/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see+ * A parser for crontab-like formatted files and streams. + *
+ *+ * If you want to schedule a list of tasks declared in a crontab-like file you don't need the CronParser, since you can do it by adding the file to the scheduler, with the {@link Scheduler#scheduleFile(File)} method. + *
+ *+ * Consider to use the CronParser if the {@link Scheduler#scheduleFile(File)} method is not enough for you. In example, you may need to fetch the task list from a remote source which is not representable as a {@link File} object (a document on a remote server, a DBMS result set and so on). To solve + * the problem you can implement your own {@link TaskCollector}, getting the advantage of the CronParser to parse easily any crontab-like content. + *
+ *+ * You can parse a whole file/stream, but you can also parse a single line. + *
+ *+ * A line can be empty, can contain a comment or it can be a scheduling line. + *
+ *+ * A line containing no characters or a line with only space characters is considered an empty line. + *
+ *+ * A line whose first non-space character is a number sign (#) is considered a comment. + *
+ *+ * Empty lines and comment lines are ignored by the parser. + *
+ *+ * Any other kind of line is parsed as a scheduling line. + *
+ *+ * A valid scheduling line respects the following structure: + *
+ * + *+ * scheduling-pattern [options] command [args] + *+ *
+ * After the scheduling pattern item, other tokens in each line are space separated or delimited with double quotation marks ("). + *
+ *+ * Double quotation marks delimited items can take advantage of the following escape sequences: + *
+ *+ * The options token collection can include one or more of the following elements: + *
+ *+ * It is also possible to schedule the invocation of a method of a Java class in the scope of the parser ClassLoader. The method has to be static and it must accept an array of strings as its sole argument. To invoke a method of this kind the syntax is: + *
+ * + *+ * scheduling-pattern java:className#methodName [args] + *+ *
+ * The #methodName part can be omitted: in this case the main(String[]) method will be assumed. + *
+ *+ * Please note that static methods are invoked within the scheduler same JVM, without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options can't be applied. + *
+ *+ * Invalid scheduling lines are discarded without blocking the parsing procedure, but an error message is printed in the application standard error channel. + *
+ *+ * Valid examples: + *
+ * + *+ * 0 5 * * * sol.exe + * 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55 + * 0,30 4 * * * "OUT:C:\Documents and Settings\Carlo\ping.txt" ping 10.9.43.55 + * 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat "Nightly Build" + * 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2 + *+ * + * @author Carlo Pelliccia + * @since 2.0 + */ +public class CronParser +{ + /** + * Instantiation prohibited. + */ + private CronParser() + { + } + + /** + *
+ * Builds a task list reading it from a file. + *
+ *+ * The file is treated as UTF-8. If your source file is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source file are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param file The file. + * @return The task table parsed from the file. + * @throws IOException I/O error. + */ + public static TaskTable parse(File file) throws IOException + { + InputStream stream = null; + try + { + stream = new FileInputStream(file); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an URL. + *
+ *+ * Contents fetched from the URL are treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the retrieved document are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param url The URL. + * @return The task table parsed from the contents fetched from the given URL. + * @throws IOException I/O error. + */ + public static TaskTable parse(URL url) throws IOException + { + InputStream stream = null; + try + { + stream = url.openStream(); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an input stream. + *
+ *+ * The stream is treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source stream are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param stream The input stream. + * @return The task table parsed from the stream contents. + * @throws IOException I/O error. + */ + public static TaskTable parse(InputStream stream) throws IOException + { + return parse(new InputStreamReader(stream, "UTF-8")); + } + + /** + *+ * Builds a task list reading it from a reader. + *
+ *+ * Syntax and semantics errors in the source reader are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param reader The reader. + * @return The task table parsed from the contents in the reader. + * @throws IOException I/O error. + */ + public static TaskTable parse(Reader reader) throws IOException + { + TaskTable table = new TaskTable(); + BufferedReader bufferedReader = new BufferedReader(reader); + try + { + String line; + while ((line = bufferedReader.readLine()) != null) + { + try + { + parseLine(table, line); + } + catch (Exception e) + { + e.printStackTrace(); + continue; + } + } + } + finally + { + reader.close(); + } + return table; + } + + /** + * Parses a crontab-like line. + * @param table The table on which the parsed task will be stored, by side-effect. + * @param line The crontab-like line. + * @throws Exception The supplied line doesn't represent a valid task line. + */ + public static void parseLine(TaskTable table, String line) throws Exception + { + line = line.trim(); + if ((line.length() == 0) || (line.charAt(0) == '#')) + { + return; + } + // Detecting the pattern. + int size = line.length(); + String pattern = null; + for (int i = size; i >= 0; i--) + { + String aux = line.substring(0, i); + if (SchedulingPattern.validate(aux)) + { + pattern = aux; + break; + } + } + if (pattern == null) + { + throw new Exception("Invalid cron line: " + line); + } + line = line.substring(pattern.length()); + size = line.length(); + // Splitting the line + ArrayList+ * A {@link TaskCollector} implementation, reading the task list from a group of files. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class FileTaskCollector implements TaskCollector +{ + /** + * File list. + */ + private final ArrayList+ * A {@link TaskCollector} implementation managing a task list in memory. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class MemoryTaskCollector implements TaskCollector +{ + /** + * Size. + */ + private final int size = 0; + + /** + * The inner scheduling pattern list. + */ + private final ArrayList+ * A built-in {@link Task} implementation which can be used to run an external process. + *
+ * @author Carlo Pelliccia + * @since 2.1 + */ +public class ProcessTask extends Task +{ + /** + * The command to launch. + */ + private String[] command; + + /** + * Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + private String[] envs; + + /** + * Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + private File directory; + + /** + * Standard input file (optional). + */ + private File stdinFile = null; + + /** + * Standard output file (optional). + */ + private File stdoutFile = null; + + /** + * Standard error file (optional). + */ + private File stderrFile = null; + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + * @param directory Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public ProcessTask(String[] command, String[] envs, File directory) + { + this.command = command; + this.envs = envs; + this.directory = directory; + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + public ProcessTask(String[] command, String[] envs) + { + this(command, envs, null); + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + */ + public ProcessTask(String[] command) + { + this(command, null, null); + } + + /** + * Creates the task. + * @param command The command to launch. + */ + public ProcessTask(String command) + { + this(new String[] + { + command + }, null, null); + } + + /** + * Returns true. + */ + @Override + public boolean canBeStopped() + { + return true; + } + + /** + * Returns the command executed by this task. + * @return The command executed by this task. + */ + public String[] getCommand() + { + return command; + } + + /** + * Sets the command executed by this task. + * @param command The command executed by this task. + */ + public void setCommand(String[] command) + { + this.command = command; + } + + /** + * Returns the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @return The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public String[] getEnvs() + { + return envs; + } + + /** + * Sets the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @param envs The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public void setEnvs(String[] envs) + { + this.envs = envs; + } + + /** + * Resturns the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @return The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public File getDirectory() + { + return directory; + } + + /** + * Sets the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @param directory The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public void setDirectory(File directory) + { + this.directory = directory; + } + + /** + * Returns the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @return The standard input file (optional). + */ + public File getStdinFile() + { + return stdinFile; + } + + /** + * Sets the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @param stdinFile The standard input file (optional). + */ + public void setStdinFile(File stdinFile) + { + this.stdinFile = stdinFile; + } + + /** + * Sets the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @param stdoutFile The standard output file (optional). + */ + public void setStdoutFile(File stdoutFile) + { + this.stdoutFile = stdoutFile; + } + + /** + * Returns the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @return The standard output file (optional). + */ + public File getStdoutFile() + { + return stdoutFile; + } + + /** + * Sets the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @param stderrFile The standard error file (optional). + */ + public void setStderrFile(File stderrFile) + { + this.stderrFile = stderrFile; + } + + /** + * Returns the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @return The standard error file (optional). + */ + public File getStderrFile() + { + return stderrFile; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}. Runs the given command as a separate process and waits for its end. + */ + @Override + public void execute(TaskExecutionContext context) throws RuntimeException + { + Process p; + try + { + p = exec(); + } + catch (IOException e) + { + throw new RuntimeException(toString() + " cannot be started", e); + } + InputStream in = buildInputStream(stdinFile); + OutputStream out = buildOutputStream(stdoutFile); + OutputStream err = buildOutputStream(stderrFile); + if (in != null) + { + StreamBridge b = new StreamBridge(in, p.getOutputStream()); + b.start(); + } + if (out != null) + { + StreamBridge b = new StreamBridge(p.getInputStream(), out); + b.start(); + } + if (err != null) + { + StreamBridge b = new StreamBridge(p.getErrorStream(), err); + b.start(); + } + int r; + try + { + r = p.waitFor(); + } + catch (InterruptedException e) + { + throw new RuntimeException(toString() + " has been interrupted"); + } + finally + { + if (in != null) + { + try + { + in.close(); + } + catch (Throwable e) + { + } + } + if (out != null) + { + try + { + out.close(); + } + catch (Throwable e) + { + } + } + if (err != null) + { + try + { + err.close(); + } + catch (Throwable e) + { + } + } + p.destroy(); + } + if (r != 0) + { + throw new RuntimeException(toString() + " returns with error code " + r); + } + } + + /** + * Executes the command. + * @return The launched Process. + * @throws IOException If an I/O error occurs. + */ + private Process exec() throws IOException + { + Runtime rt = Runtime.getRuntime(); + Process p; + try + { + // java 1.3+ + p = rt.exec(command, envs, directory); + } + catch (NoSuchMethodError e) + { + // java 1.2 + p = rt.exec(command, envs); + } + return p; + } + + /** + * Prepares an {@link InputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private InputStream buildInputStream(File file) + { + if (file != null) + { + try + { + return new FileInputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prepares an {@link OutputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private OutputStream buildOutputStream(File file) + { + if (file != null) + { + try + { + return new FileOutputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prints in the returned string the elements contained in the given string array. + * @param arr The array. + * @return A string representing the supplied array contents. + */ + private static String listStrings(String[] arr) + { + if (arr == null) + { + return "null"; + } + StringBuffer b = new StringBuffer(); + b.append('['); + for (int i = 0; i < arr.length; i++) + { + if (i > 0) + { + b.append(", "); + } + b.append(arr[i]); + } + b.append(']'); + return b.toString(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("cmd="); + b.append(ProcessTask.listStrings(command)); + b.append(", env="); + b.append(ProcessTask.listStrings(envs)); + b.append(", "); + b.append("dir="); + b.append(directory); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java new file mode 100644 index 0000000000..0fe313c855 --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java @@ -0,0 +1,76 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A {@link Task} implementation acting as a wrapper around a {@link Runnable} object. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class RunnableTask extends Task +{ + /** + * The wrapped runnable object. + */ + private final Runnable runnable; + + /** + * Builds the task. + * @param runnable The wrapped Runnable object. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public RunnableTask(Runnable runnable) throws InvalidPatternException + { + this.runnable = runnable; + } + + /** + * Returns the wrapped Runnable object. + * @return The wrapped Runnable object. + */ + public Runnable getRunnable() + { + return runnable; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}, launching the {@link Runnable#run()} method on the wrapped object. + */ + @Override + public void execute(TaskExecutionContext context) + { + runnable.run(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("runnable="); + b.append(runnable); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java new file mode 100644 index 0000000000..d284db56ac --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java @@ -0,0 +1,728 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * The cron4j scheduler. + *
+ * @author Carlo Pelliccia + */ +public class Scheduler +{ + /** + * A GUID for this scheduler. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The time zone applied by the scheduler. + */ + private TimeZone timezone = null; + + /** + * The daemon flag. If true the scheduler and its spawned threads acts like daemons. + */ + private boolean daemon = false; + + /** + * The state flag. If true the scheduler is started and running, otherwise it is paused and no task is launched. + */ + private boolean started = false; + + /** + * Registered {@link TaskCollector}s list. + */ + private final ArrayList+ * Sets the time zone applied by the scheduler. + *
+ *+ * Current system time is adapted to the supplied time zone before comparing it with registered scheduling patterns. The result is that any supplied scheduling pattern is treated according to the specified time zone. In example, suppose: + *
+ *+ * The scheduler, before comparing system time with patterns, translates 10:00 from GMT+1 to GMT+3. It means that 10:00 becomes 12:00. The resulted time is then used by the scheduler to activate tasks. So, in the given configuration at the given moment, any task scheduled as 0 12 * * * + * will be executed, while any 0 10 * * * will not. + *
+ * @param timezone The time zone applied by the scheduler. + */ + public void setTimeZone(TimeZone timezone) + { + this.timezone = timezone; + } + + /** + * Returns the time zone applied by the scheduler. + * @return The time zone applied by the scheduler. + */ + public TimeZone getTimeZone() + { + return timezone != null ? timezone : TimeZone.getDefault(); + } + + /** + * Tests whether this scheduler is a daemon scheduler. + * @return true if this scheduler is a daemon scheduler; false otherwise. + */ + public boolean isDaemon() + { + return daemon; + } + + /** + * Marks this scheduler daemon flag. When a scheduler is marked as a daemon scheduler it spawns only daemon threads. The Java Virtual Machine exits when the only threads running are all daemon threads. This method must be called before the scheduler is started. + * @param on If true, the scheduler will spawn only daemon threads. + * @throws IllegalStateException If the scheduler is started. + */ + public void setDaemon(boolean on) throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + this.daemon = on; + } + } + + /** + * Tests if this scheduler is started. + * @return true if the scheduler is started, false if it is stopped. + */ + public boolean isStarted() + { + synchronized (lock) + { + return started; + } + } + + /** + * Adds a {@link File} instance to the scheduler. Every minute the file will be parsed. The scheduler will execute any declared task whose scheduling pattern matches the current system time. See {@link CronParser} documentation for informations about the file contents syntax. + * @param file The {@link File} instance. + */ + public void scheduleFile(File file) + { + fileTaskCollector.addFile(file); + } + + /** + * Removes a {@link File} instance previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @param file The {@link File} instance. + */ + public void descheduleFile(File file) + { + fileTaskCollector.removeFile(file); + } + + /** + * Returns an array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @return An array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + */ + public File[] getScheduledFiles() + { + return fileTaskCollector.getFiles(); + } + + /** + * Adds a custom {@link TaskCollector} instance to the scheduler. The supplied object, once added to the scheduler, will be query every minute for its task list. The scheduler will execute any of the returned tasks whose scheduling pattern matches the current system time. + * @param collector The custom {@link TaskCollector} instance. + */ + public void addTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.add(collector); + } + } + + /** + * Removes a previously registered custom {@link TaskCollector} instance. + * @param collector The custom {@link TaskCollector} instance. + */ + public void removeTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.remove(collector); + } + } + + /** + * Returns an array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + * @return An array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + */ + public TaskCollector[] getTaskCollectors() + { + synchronized (collectors) + { + // Discard the first 2 elements in the list. + int size = collectors.size() - 2; + TaskCollector[] ret = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + ret[i] = collectors.get(i + 2); + } + return ret; + } + } + + /** + * Adds a {@link SchedulerListener} to the scheduler. A {@link SchedulerListener} is notified every time a task is launching, has succeeded or has failed. + * @param listener The listener. + */ + public void addSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.add(listener); + } + } + + /** + * Removes a {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @param listener The listener. + */ + public void removeSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.remove(listener); + } + } + + /** + * Returns an array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @return An array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + */ + public SchedulerListener[] getSchedulerListeners() + { + synchronized (listeners) + { + int size = listeners.size(); + SchedulerListener[] ret = new SchedulerListener[size]; + for (int i = 0; i < size; i++) + { + ret[i] = listeners.get(i); + } + return ret; + } + } + + /** + * Returns an array containing any currently executing task, in the form of {@link TaskExecutor} objects. Each running task is executed by a different thread. A {@link TaskExecutor} object allows the control of the running task. The inner {@link Task} representation could be retrieved, the + * status of the task could be detected and the thread could be interrupted using any standard {@link Thread} method ( {@link Thread#interrupt()}, {@link Thread#isAlive() etc}. + * @return An array containing any currently executing task, in the form of {@link TaskExecutor} objects. + */ + public TaskExecutor[] getExecutingTasks() + { + synchronized (executors) + { + int size = executors.size(); + TaskExecutor[] ret = new TaskExecutor[size]; + for (int i = 0; i < size; i++) + { + ret[i] = executors.get(i); + } + return ret; + } + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public String schedule(String schedulingPattern, Runnable task) throws InvalidPatternException + { + return schedule(schedulingPattern, new RunnableTask(task)); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @since 2.0 + */ + public String schedule(String schedulingPattern, Task task) throws InvalidPatternException + { + return schedule(new SchedulingPattern(schedulingPattern), task); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @since 2.0 + */ + public String schedule(SchedulingPattern schedulingPattern, Task task) + { + return memoryTaskCollector.add(schedulingPattern, task); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @deprecated Use {@link Scheduler#reschedule(String, String)}. + */ + @Deprecated + public void reschedule(Object id, String schedulingPattern) throws InvalidPatternException + { + reschedule((String) id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public void reschedule(String id, String schedulingPattern) throws InvalidPatternException + { + reschedule(id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @since 2.0 + */ + public void reschedule(String id, SchedulingPattern schedulingPattern) + { + memoryTaskCollector.update(id, schedulingPattern); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + * @deprecated Use {@link Scheduler#deschedule(String)}. + */ + @Deprecated + public void deschedule(Object id) + { + deschedule((String) id); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + */ + public void deschedule(String id) + { + memoryTaskCollector.remove(id); + } + + /** + * This method retrieves a previously scheduled task. + * @param id The task ID. + * @return The requested task, or null if the task was not found. + * @since 2.0 + */ + public Task getTask(String id) + { + return memoryTaskCollector.getTask(id); + } + + /** + * This method retrieves a previously scheduled task scheduling pattern. + * @param id The task ID. + * @return The requested scheduling pattern, or null if the task was not found. + * @since 2.0 + */ + public SchedulingPattern getSchedulingPattern(String id) + { + return memoryTaskCollector.getSchedulingPattern(id); + } + + /** + * This method retrieves the Runnable object of a previously scheduled task. + * @param id The task ID. + * @return The Runnable object of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getTask(String)}. + */ + @Deprecated + public Runnable getTaskRunnable(Object id) + { + Task task = getTask((String) id); + if (task instanceof RunnableTask) + { + RunnableTask rt = (RunnableTask) task; + return rt.getRunnable(); + } + return null; + } + + /** + * This method retrieves the scheduling pattern of a previously scheduled task. + * @param id The task ID. + * @return The scheduling pattern of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getSchedulingPattern(String)}. + */ + @Deprecated + public String getTaskSchedulingPattern(Object id) + { + return getSchedulingPattern((String) id).toString(); + } + + /** + * Executes immediately a task, without scheduling it. + * @param task The task. + * @return The {@link TaskExecutor} executing the given task. + * @throws IllegalStateException If the scheduler is not started. + */ + public TaskExecutor launch(Task task) + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + return spawnExecutor(task); + } + } + + /** + * This method starts the scheduler. When the scheduled is started the supplied tasks are executed at the given moment. + * @throws IllegalStateException Thrown if this scheduler is already started. + */ + public void start() throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + // Initializes required lists. + launchers = new ArrayList<>(); + executors = new ArrayList<>(); + // Starts the timer thread. + timer = new TimerThread(this); + timer.setDaemon(daemon); + timer.start(); + // Change the state of the scheduler. + started = true; + } + } + + /** + * This method stops the scheduler execution. Before returning, it waits the end of all the running tasks previously launched. Once the scheduler has been stopped it can be started again with a start() call. + * @throws IllegalStateException Thrown if this scheduler is not started. + */ + public void stop() throws IllegalStateException + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + // Interrupts the timer and waits for its death. + timer.interrupt(); + tillThreadDies(timer); + timer = null; + // Interrupts any running launcher and waits for its death. + for (;;) + { + LauncherThread launcher = null; + synchronized (launchers) + { + if (launchers.size() == 0) + { + break; + } + launcher = launchers.remove(0); + } + launcher.interrupt(); + tillThreadDies(launcher); + } + launchers = null; + // Interrupts any running executor and waits for its death. + // Before exiting wait for all the active tasks end. + for (;;) + { + TaskExecutor executor = null; + synchronized (executors) + { + if (executors.size() == 0) + { + break; + } + executor = executors.remove(0); + } + if (executor.canBeStopped()) + { + executor.stop(); + } + tillExecutorDies(executor); + } + executors = null; + // Change the state of the object. + started = false; + } + } + + // -- PACKAGE RESERVED METHODS -------------------------------------------- + + /** + * Starts a launcher thread. + * @param referenceTimeInMillis Reference time in millis for the launcher. + * @return The spawned launcher. + */ + LauncherThread spawnLauncher(long referenceTimeInMillis) + { + TaskCollector[] nowCollectors; + synchronized (collectors) + { + int size = collectors.size(); + nowCollectors = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + nowCollectors[i] = collectors.get(i); + } + } + LauncherThread l = new LauncherThread(this, nowCollectors, referenceTimeInMillis); + synchronized (launchers) + { + launchers.add(l); + } + l.setDaemon(daemon); + l.start(); + return l; + } + + /** + * Starts the given task within a task executor. + * @param task The task. + * @return The spawned task executor. + */ + TaskExecutor spawnExecutor(Task task) + { + TaskExecutor e = new TaskExecutor(this, task); + synchronized (executors) + { + executors.add(e); + } + e.start(daemon); + return e; + } + + /** + * This method is called by a launcher thread to notify that the execution is completed. + * @param launcher The launcher which has completed its task. + */ + void notifyLauncherCompleted(LauncherThread launcher) + { + synchronized (launchers) + { + launchers.remove(launcher); + } + } + + /** + * This method is called by a task executor to notify that the execution is completed. + * @param executor The executor which has completed its task. + */ + void notifyExecutorCompleted(TaskExecutor executor) + { + synchronized (executors) + { + executors.remove(executor); + } + } + + /** + * Notifies every registered listener that a task is going to be launched. + * @param executor The task executor. + */ + void notifyTaskLaunching(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskLaunching(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has successfully completed. + * @param executor The task executor. + */ + void notifyTaskSucceeded(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskSucceeded(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has failed due to an uncaught exception. + * @param executor The task executor. + * @param exception The exception. + */ + void notifyTaskFailed(TaskExecutor executor, Throwable exception) + { + synchronized (listeners) + { + int size = listeners.size(); + if (size > 0) + { + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskFailed(executor, exception); + } + } + else + { + // Logs on console if no one has been notified about it. + exception.printStackTrace(); + } + } + } + + // -- PRIVATE METHODS ----------------------------------------------------- + + /** + * It waits until the given thread is dead. It is similar to {@link Thread#join()}, but this one avoids {@link InterruptedException} instances. + * @param thread The thread. + */ + private void tillThreadDies(Thread thread) + { + boolean dead = false; + do + { + try + { + thread.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + + /** + * It waits until the given task executor is dead. It is similar to {@link TaskExecutor#join()}, but this one avoids {@link InterruptedException} instances. + * @param executor The task executor. + */ + private void tillExecutorDies(TaskExecutor executor) + { + boolean dead = false; + do + { + try + { + executor.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java new file mode 100644 index 0000000000..2f186eb42f --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java @@ -0,0 +1,48 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Implement this interface and register your instance with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method to receive notifications about scheduled task executions. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface SchedulerListener +{ + /** + * This one is called by the scheduler when a task execution is starting. + * @param executor The task executor. + */ + public void taskLaunching(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has been successfully completed. + * @param executor The task executor. + */ + public void taskSucceeded(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has failed. + * @param executor The task executor. + * @param exception The exception representing the failure notification. + */ + public void taskFailed(TaskExecutor executor, Throwable exception); +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java index d4c8eaaa86..ac993fb52b 100644 --- a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see+ * A scheduling patterns validator. + *
+ *+ * The class lets you validate a scheduling pattern before/without using it with a {@link Scheduler} instance. Simply call: + *
+ * + *+ * boolean valid = SchedulingPatternValidator.validate(thePattern); + *+ *
+ * It is useful in validating user-entered patterns. + *
+ * @author Carlo Pelliccia + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ +@Deprecated +public class SchedulingPatternValidator +{ + /** + * Validates a string as a scheduling pattern. + * @param schedulingPattern The pattern to validate. + * @return true if the given string represents a valid scheduling pattern; false otherwise. + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ + @Deprecated + public static boolean validate(String schedulingPattern) + { + return SchedulingPattern.validate(schedulingPattern); + } +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java new file mode 100644 index 0000000000..bbb04bf752 --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java @@ -0,0 +1,107 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A package-reserved utility class. It spawns a secondary thread in which the supplied {@link InputStream} instance is read, and the incoming contents are written in the supplied {@link OutputStream}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class StreamBridge +{ + /** + * Used to trace alive instances. + */ + static ArrayListmillis
milliseconds for this thread to die. A timeout of 0
means to wait forever.
+ * @param millis the time to wait in milliseconds.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis) throws InterruptedException
+ {
+ thread.join(millis);
+ }
+
+ /**
+ * @param millis the time to wait in milliseconds.
+ * @param nanos 0-999999 additional nanoseconds to wait.
+ * @throws IllegalArgumentException if the value of millis is negative the value of nanos is not in the range 0-999999.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis, int nanos) throws IllegalArgumentException, InterruptedException
+ {
+ thread.join(millis, nanos);
+ }
+
+ /**
+ * Tests if this bridge is alive. A job is alive if it has been started and has not yet completed.
+ * @return true
if this thread is alive; false
otherwise.
+ */
+ public boolean isAlive()
+ {
+ return thread.isAlive();
+ }
+
+ /**
+ * Contains the routine doing the job in the secondary thread.
+ */
+ private class Runner implements Runnable
+ {
+ public Runner()
+ {
+ }
+
+ @Override
+ public void run()
+ {
+ boolean skipout = false;
+ for (;;)
+ {
+ int b;
+ try
+ {
+ b = in.read();
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ break;
+ }
+ if (b == -1)
+ {
+ break;
+ }
+ if (!skipout)
+ {
+ try
+ {
+ out.write(b);
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ skipout = true;
+ }
+ }
+ }
+ try
+ {
+ out.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ try
+ {
+ in.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ synchronized (traced)
+ {
+ traced.remove(myself);
+ }
+ }
+ }
+}
diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Task.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Task.java
new file mode 100644
index 0000000000..e46211e0f3
--- /dev/null
+++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/Task.java
@@ -0,0 +1,144 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see + * Abstract base representation of a cron4j task. + *
+ *+ * Developers can extends this abstract class to build their own tasks. + *
+ *+ * Extending Task means, above all, implementing the {@link Task#execute(TaskExecutionContext)} method. Within this method the task must perform its operation. If the execute() method returns regularly then the execution is considered to be successfully completed. If execute() dies + * throwing a {@link RuntimeException} then the task execution is considered to be failed. The supplied parameter, which is a {@link TaskExecutionContext} instance, helps the developer in integrating his task with the scheduler executor. Through the context the developer can check if the execution + * has been paused or stopped, and he can also push back some status informations by calling {@link TaskExecutionContext#setCompleteness(double)} and {@link TaskExecutionContext#setStatusMessage(String)}. + *
+ *+ * If the custom task supports pausing, stopping and/or tracking, that should be notified by overriding {@link Task#canBePaused()}, {@link Task#canBeStopped()}, {@link Task#supportsCompletenessTracking()} and/or {@link Task#supportsStatusTracking()}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public abstract class Task +{ + /** + * The ID for this task. Also used as an instance synchronization lock. + */ + private final Object id = GUIDGenerator.generate(); + + /** + * Empty constructor, does nothing. + */ + public Task() + { + } + + /** + * It returns the ID for this task. + * @return The ID for this task. + */ + Object getId() + { + return id; + } + + /** + *+ * Checks whether this task supports pause requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that pause requests are really handled. This can be done calling regularly the + * {@link TaskExecutionContext#pauseIfRequested()} method during the task execution. + *
+ * @return true if this task can be paused; false otherwise. + */ + public boolean canBePaused() + { + return false; + } + + /** + *+ * Checks whether this task supports stop requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that stop requests are really handled. This can be done checking regularly the + * {@link TaskExecutionContext#isStopped()} method during the task execution. + *
+ * @return true if this task can be stopped; false otherwise. + */ + public boolean canBeStopped() + { + return false; + } + + /** + *+ * Tests whether this task supports status tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setStatusMessage(String)} method during the task execution. + *
+ * @return true if this task, during its execution, provides status message regularly. + */ + public boolean supportsStatusTracking() + { + return false; + } + + /** + *+ * Tests whether this task supports completeness tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setCompleteness(double)} method during the task execution. + *
+ * @return true if this task, during its execution, provides a completeness value regularly. + */ + public boolean supportsCompletenessTracking() + { + return false; + } + + /** + *+ * This method is called to require a task execution, and should contain the core routine of any scheduled task. + *
+ *+ * If the execute() method ends regularly the scheduler will consider the execution successfully completed, and this will be communicated to any {@link SchedulerListener} interested in it. If the execute() method dies throwing a {@link RuntimeException} the scheduler will + * consider it as a failure notification. Any {@link SchedulerListener} will be notified about the occurred exception. + *
+ * @param context The execution context. + * @throws RuntimeException Task execution has somehow failed. + */ + public abstract void execute(TaskExecutionContext context) throws RuntimeException; +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java new file mode 100644 index 0000000000..4cb2f9fa2f --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java @@ -0,0 +1,37 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * This interface describes a task collector. Task collectors can be registered in a {@link Scheduler} instance with the {@link Scheduler#addTaskCollector(TaskCollector)} method. Any registered task collector is queried by the scheduler once a minute. The developer has to implement the + * {@link TaskCollector#getTasks()} method, returning a {@link TaskTable} whose elements has been collected with a custom logic. In example the list can be extracted from a database. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskCollector +{ + /** + * Once the instance has been registered on a {@link Scheduler} instance, with the {@link Scheduler#addTaskCollector(TaskCollector)} method, this method will be queried once a minute. It should return a custom {@link TaskTable} object. The scheduler instance will automatically iterate over the + * returned table elements, executing any task whose scheduling pattern is matching the current system time. + * @return The task table that will be automatically injected in the scheduler. + */ + public TaskTable getTasks(); +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java new file mode 100644 index 0000000000..8e0d1a8c05 --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java @@ -0,0 +1,67 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A TaskExecutionContext object provides support methods for the execution of a task. An instance of this class is always passed to the task when its {@link Task#execute(TaskExecutionContext)} method is called. The task, while executing, can use the received context to exchange informations with + * its own executor. If the task declares to supports pausing, stopping, completeness tracking and/or status tracking, it has to use its context methods to perform any declared operation (checks pause and stop requests, sends back tracking informations). + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskExecutionContext +{ + /** + * Returns the scheduler. + * @return The scheduler. + */ + public Scheduler getScheduler(); + + /** + * Returns the task executor. + * @return The task executor. + */ + public TaskExecutor getTaskExecutor(); + + /** + * Sets the current status tracking message, that has to be something about what the task is doing at the moment. + * @param message A message representing the current execution status. Null messages will be blanked. + */ + public void setStatusMessage(String message); + + /** + * Sets the completeness tracking value, that has to be between 0 and 1. + * @param completeness A completeness value, between 0 and 1. Values out of range will be ignored. + */ + public void setCompleteness(double completeness); + + /** + * If the task execution has been paused, stops until the operation is resumed. It can also returns because of a stop operation without any previous resuming. Due to this the task developer should always check the {@link TaskExecutionContext#isStopped()} value after any + * pauseIfRequested() call. Note that a task execution can be paused only if the task {@link Task#canBePaused()} method returns true. + */ + public void pauseIfRequested(); + + /** + * Checks whether the task execution has been demanded to be stopped. If the returned value is true, the task developer must shut down gracefully its task execution, as soon as possible. Note that a task execution can be stopped only if the task {@link Task#canBePaused()} method returns + * true. + * @return true if the current task execution has been demanded to be stopped; false otherwise. + */ + public boolean isStopped(); +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java new file mode 100644 index 0000000000..4911f2826c --- /dev/null +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java @@ -0,0 +1,612 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Represents a task executor, which is something similar to threads. + *
+ *+ * Each time a task is launched, a new executor is spawned, executing and watching the task + *
+ *+ * Alive task executors can be retrieved with the {@link Scheduler#getExecutingTasks()} method, and they expose method to control the ongoing execution. + *
+ * @see Scheduler#getExecutingTasks() + * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskExecutor +{ + /** + * The scheduler whose this executor belongs to. + */ + final Scheduler scheduler; + + /** + * The executed task. + */ + final Task task; + + /** + * A task execution context. + */ + final MyContext context; + + /** + * A unique ID for this executor (used also as a lock object). + */ + private final String guid = GUIDGenerator.generate(); + + /** + * An alternative to this (inner classes need it). + */ + final TaskExecutor myself = this; + + /** + * A list of {@link TaskExecutorListener} instances. + */ + private final ArrayList+ * A table coupling tasks with scheduling patterns. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskTable +{ + /** + * Table size. + */ + private int size = 0; + + /** + * Pattern list. + */ + private final ArrayList+ * TimeThreads are used by {@link Scheduler} instances. A TimerThread spends most of the time sleeping. It wakes up every minute and it requests to the scheduler the spawning of a {@link LauncherThread}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class TimerThread extends Thread +{ + /** + * A GUID for this object. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The owner scheduler. + */ + private Scheduler scheduler; + + /** + * Builds the timer thread. + * @param scheduler The owner scheduler. + */ + public TimerThread(Scheduler scheduler) + { + this.scheduler = scheduler; + // Thread name. + String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::timer[" + guid + "]"; + setName(name); + } + + /** + * Returns the GUID for this object. + * @return The GUID for this object. + */ + public Object getGuid() + { + return guid; + } + + /** + * It has been reported that the {@link Thread#sleep(long)} method sometimes exits before the requested time has passed. This one offers an alternative that sometimes could sleep a few millis more than requested, but never less. + * @param millis The length of time to sleep in milliseconds. + * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown. + * @see Thread#sleep(long) + */ + 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); + } + + /** + * Overrides {@link Thread#run()}. + */ + @Override + public void run() + { + // What time is it? + long millis = System.currentTimeMillis(); + // Calculating next minute. + long nextMinute = ((millis / 60000) + 1) * 60000; + // Work until the scheduler is started. + for (;;) + { + // Coffee break 'till next minute comes! + long sleepTime = (nextMinute - System.currentTimeMillis()); + if (sleepTime > 0) + { + try + { + safeSleep(sleepTime); + } + catch (InterruptedException e) + { + // Must exit! + break; + } + } + // What time is it? + millis = System.currentTimeMillis(); + // Launching the launching thread! + scheduler.spawnLauncher(millis); + // Calculating next minute. + nextMinute = ((millis / 60000) + 1) * 60000; + } + // Discard scheduler reference. + scheduler = null; + } +} diff --git a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java index 8e7b79e0d3..c5c1a0e5ec 100644 --- a/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java +++ b/L2J_Mobius_Helios/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see+ * A parser for crontab-like formatted files and streams. + *
+ *+ * If you want to schedule a list of tasks declared in a crontab-like file you don't need the CronParser, since you can do it by adding the file to the scheduler, with the {@link Scheduler#scheduleFile(File)} method. + *
+ *+ * Consider to use the CronParser if the {@link Scheduler#scheduleFile(File)} method is not enough for you. In example, you may need to fetch the task list from a remote source which is not representable as a {@link File} object (a document on a remote server, a DBMS result set and so on). To solve + * the problem you can implement your own {@link TaskCollector}, getting the advantage of the CronParser to parse easily any crontab-like content. + *
+ *+ * You can parse a whole file/stream, but you can also parse a single line. + *
+ *+ * A line can be empty, can contain a comment or it can be a scheduling line. + *
+ *+ * A line containing no characters or a line with only space characters is considered an empty line. + *
+ *+ * A line whose first non-space character is a number sign (#) is considered a comment. + *
+ *+ * Empty lines and comment lines are ignored by the parser. + *
+ *+ * Any other kind of line is parsed as a scheduling line. + *
+ *+ * A valid scheduling line respects the following structure: + *
+ * + *+ * scheduling-pattern [options] command [args] + *+ *
+ * After the scheduling pattern item, other tokens in each line are space separated or delimited with double quotation marks ("). + *
+ *+ * Double quotation marks delimited items can take advantage of the following escape sequences: + *
+ *+ * The options token collection can include one or more of the following elements: + *
+ *+ * It is also possible to schedule the invocation of a method of a Java class in the scope of the parser ClassLoader. The method has to be static and it must accept an array of strings as its sole argument. To invoke a method of this kind the syntax is: + *
+ * + *+ * scheduling-pattern java:className#methodName [args] + *+ *
+ * The #methodName part can be omitted: in this case the main(String[]) method will be assumed. + *
+ *+ * Please note that static methods are invoked within the scheduler same JVM, without spawning any external process. Thus IN, OUT, ERR, ENV and DIR options can't be applied. + *
+ *+ * Invalid scheduling lines are discarded without blocking the parsing procedure, but an error message is printed in the application standard error channel. + *
+ *+ * Valid examples: + *
+ * + *+ * 0 5 * * * sol.exe + * 0,30 * * * * OUT:C:\ping.txt ping 10.9.43.55 + * 0,30 4 * * * "OUT:C:\Documents and Settings\Carlo\ping.txt" ping 10.9.43.55 + * 0 3 * * * ENV:JAVA_HOME=C:\jdks\1.4.2_15 DIR:C:\myproject OUT:C:\myproject\build.log C:\myproject\build.bat "Nightly Build" + * 0 4 * * * java:mypackage.MyClass#startApplication myOption1 myOption2 + *+ * + * @author Carlo Pelliccia + * @since 2.0 + */ +public class CronParser +{ + /** + * Instantiation prohibited. + */ + private CronParser() + { + } + + /** + *
+ * Builds a task list reading it from a file. + *
+ *+ * The file is treated as UTF-8. If your source file is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source file are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param file The file. + * @return The task table parsed from the file. + * @throws IOException I/O error. + */ + public static TaskTable parse(File file) throws IOException + { + InputStream stream = null; + try + { + stream = new FileInputStream(file); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an URL. + *
+ *+ * Contents fetched from the URL are treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the retrieved document are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param url The URL. + * @return The task table parsed from the contents fetched from the given URL. + * @throws IOException I/O error. + */ + public static TaskTable parse(URL url) throws IOException + { + InputStream stream = null; + try + { + stream = url.openStream(); + return parse(stream); + } + finally + { + if (stream != null) + { + try + { + stream.close(); + } + catch (Throwable t) + { + } + } + } + } + + /** + *+ * Builds a task list reading it from an input stream. + *
+ *+ * The stream is treated as UTF-8. If your source is not UTF-8 encoded establish by yourself a {@link Reader} using the right charset and pass it to the {@link CronParser#parse(Reader)} method. + *
+ *+ * Syntax and semantics errors in the source stream are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param stream The input stream. + * @return The task table parsed from the stream contents. + * @throws IOException I/O error. + */ + public static TaskTable parse(InputStream stream) throws IOException + { + return parse(new InputStreamReader(stream, "UTF-8")); + } + + /** + *+ * Builds a task list reading it from a reader. + *
+ *+ * Syntax and semantics errors in the source reader are not blocking. Invalid lines are discarded, and they cause just a stack trace to be printed in the standard error channel as a notification. + *
+ * @param reader The reader. + * @return The task table parsed from the contents in the reader. + * @throws IOException I/O error. + */ + public static TaskTable parse(Reader reader) throws IOException + { + TaskTable table = new TaskTable(); + BufferedReader bufferedReader = new BufferedReader(reader); + try + { + String line; + while ((line = bufferedReader.readLine()) != null) + { + try + { + parseLine(table, line); + } + catch (Exception e) + { + e.printStackTrace(); + continue; + } + } + } + finally + { + reader.close(); + } + return table; + } + + /** + * Parses a crontab-like line. + * @param table The table on which the parsed task will be stored, by side-effect. + * @param line The crontab-like line. + * @throws Exception The supplied line doesn't represent a valid task line. + */ + public static void parseLine(TaskTable table, String line) throws Exception + { + line = line.trim(); + if ((line.length() == 0) || (line.charAt(0) == '#')) + { + return; + } + // Detecting the pattern. + int size = line.length(); + String pattern = null; + for (int i = size; i >= 0; i--) + { + String aux = line.substring(0, i); + if (SchedulingPattern.validate(aux)) + { + pattern = aux; + break; + } + } + if (pattern == null) + { + throw new Exception("Invalid cron line: " + line); + } + line = line.substring(pattern.length()); + size = line.length(); + // Splitting the line + ArrayList+ * A {@link TaskCollector} implementation, reading the task list from a group of files. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class FileTaskCollector implements TaskCollector +{ + /** + * File list. + */ + private final ArrayList+ * A {@link TaskCollector} implementation managing a task list in memory. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class MemoryTaskCollector implements TaskCollector +{ + /** + * Size. + */ + private final int size = 0; + + /** + * The inner scheduling pattern list. + */ + private final ArrayList+ * A built-in {@link Task} implementation which can be used to run an external process. + *
+ * @author Carlo Pelliccia + * @since 2.1 + */ +public class ProcessTask extends Task +{ + /** + * The command to launch. + */ + private String[] command; + + /** + * Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + private String[] envs; + + /** + * Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + private File directory; + + /** + * Standard input file (optional). + */ + private File stdinFile = null; + + /** + * Standard output file (optional). + */ + private File stdoutFile = null; + + /** + * Standard error file (optional). + */ + private File stderrFile = null; + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + * @param directory Working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public ProcessTask(String[] command, String[] envs, File directory) + { + this.command = command; + this.envs = envs; + this.directory = directory; + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + * @param envs Environment variables for the spawned process, in the form name=value. If null the process will inherit the current JVM environment variables. + */ + public ProcessTask(String[] command, String[] envs) + { + this(command, envs, null); + } + + /** + * Creates the task. + * @param command The command to launch and its arguments. + */ + public ProcessTask(String[] command) + { + this(command, null, null); + } + + /** + * Creates the task. + * @param command The command to launch. + */ + public ProcessTask(String command) + { + this(new String[] + { + command + }, null, null); + } + + /** + * Returns true. + */ + @Override + public boolean canBeStopped() + { + return true; + } + + /** + * Returns the command executed by this task. + * @return The command executed by this task. + */ + public String[] getCommand() + { + return command; + } + + /** + * Sets the command executed by this task. + * @param command The command executed by this task. + */ + public void setCommand(String[] command) + { + this.command = command; + } + + /** + * Returns the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @return The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public String[] getEnvs() + { + return envs; + } + + /** + * Sets the environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + * @param envs The environment variables, in the name=value form, used by the task to run its process. If null the process will inherit the current JVM environment variables. + */ + public void setEnvs(String[] envs) + { + this.envs = envs; + } + + /** + * Resturns the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @return The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public File getDirectory() + { + return directory; + } + + /** + * Sets the working directory for the spawned process. If null the process will inherit the current JVM working directory. + * @param directory The working directory for the spawned process. If null the process will inherit the current JVM working directory. + */ + public void setDirectory(File directory) + { + this.directory = directory; + } + + /** + * Returns the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @return The standard input file (optional). + */ + public File getStdinFile() + { + return stdinFile; + } + + /** + * Sets the standard input file (optional). If supplied, the standard input channel of the spawned process will be read from the given file. + * @param stdinFile The standard input file (optional). + */ + public void setStdinFile(File stdinFile) + { + this.stdinFile = stdinFile; + } + + /** + * Sets the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @param stdoutFile The standard output file (optional). + */ + public void setStdoutFile(File stdoutFile) + { + this.stdoutFile = stdoutFile; + } + + /** + * Returns the standard output file (optional). If supplied, the standard output channel of the spawned process will be written in the given file. + * @return The standard output file (optional). + */ + public File getStdoutFile() + { + return stdoutFile; + } + + /** + * Sets the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @param stderrFile The standard error file (optional). + */ + public void setStderrFile(File stderrFile) + { + this.stderrFile = stderrFile; + } + + /** + * Returns the standard error file (optional). If supplied, the standard error channel of the spawned process will be written in the given file. + * @return The standard error file (optional). + */ + public File getStderrFile() + { + return stderrFile; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}. Runs the given command as a separate process and waits for its end. + */ + @Override + public void execute(TaskExecutionContext context) throws RuntimeException + { + Process p; + try + { + p = exec(); + } + catch (IOException e) + { + throw new RuntimeException(toString() + " cannot be started", e); + } + InputStream in = buildInputStream(stdinFile); + OutputStream out = buildOutputStream(stdoutFile); + OutputStream err = buildOutputStream(stderrFile); + if (in != null) + { + StreamBridge b = new StreamBridge(in, p.getOutputStream()); + b.start(); + } + if (out != null) + { + StreamBridge b = new StreamBridge(p.getInputStream(), out); + b.start(); + } + if (err != null) + { + StreamBridge b = new StreamBridge(p.getErrorStream(), err); + b.start(); + } + int r; + try + { + r = p.waitFor(); + } + catch (InterruptedException e) + { + throw new RuntimeException(toString() + " has been interrupted"); + } + finally + { + if (in != null) + { + try + { + in.close(); + } + catch (Throwable e) + { + } + } + if (out != null) + { + try + { + out.close(); + } + catch (Throwable e) + { + } + } + if (err != null) + { + try + { + err.close(); + } + catch (Throwable e) + { + } + } + p.destroy(); + } + if (r != 0) + { + throw new RuntimeException(toString() + " returns with error code " + r); + } + } + + /** + * Executes the command. + * @return The launched Process. + * @throws IOException If an I/O error occurs. + */ + private Process exec() throws IOException + { + Runtime rt = Runtime.getRuntime(); + Process p; + try + { + // java 1.3+ + p = rt.exec(command, envs, directory); + } + catch (NoSuchMethodError e) + { + // java 1.2 + p = rt.exec(command, envs); + } + return p; + } + + /** + * Prepares an {@link InputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private InputStream buildInputStream(File file) + { + if (file != null) + { + try + { + return new FileInputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prepares an {@link OutputStream} on a file and returns it. + * @param file The file. + * @return The stream, or null if the file is not found. + */ + private OutputStream buildOutputStream(File file) + { + if (file != null) + { + try + { + return new FileOutputStream(file); + } + catch (FileNotFoundException e) + { + e.printStackTrace(); + return null; + } + } + return null; + } + + /** + * Prints in the returned string the elements contained in the given string array. + * @param arr The array. + * @return A string representing the supplied array contents. + */ + private static String listStrings(String[] arr) + { + if (arr == null) + { + return "null"; + } + StringBuffer b = new StringBuffer(); + b.append('['); + for (int i = 0; i < arr.length; i++) + { + if (i > 0) + { + b.append(", "); + } + b.append(arr[i]); + } + b.append(']'); + return b.toString(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("cmd="); + b.append(ProcessTask.listStrings(command)); + b.append(", env="); + b.append(ProcessTask.listStrings(envs)); + b.append(", "); + b.append("dir="); + b.append(directory); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java new file mode 100644 index 0000000000..0fe313c855 --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/RunnableTask.java @@ -0,0 +1,76 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A {@link Task} implementation acting as a wrapper around a {@link Runnable} object. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class RunnableTask extends Task +{ + /** + * The wrapped runnable object. + */ + private final Runnable runnable; + + /** + * Builds the task. + * @param runnable The wrapped Runnable object. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public RunnableTask(Runnable runnable) throws InvalidPatternException + { + this.runnable = runnable; + } + + /** + * Returns the wrapped Runnable object. + * @return The wrapped Runnable object. + */ + public Runnable getRunnable() + { + return runnable; + } + + /** + * Implements {@link Task#execute(TaskExecutionContext)}, launching the {@link Runnable#run()} method on the wrapped object. + */ + @Override + public void execute(TaskExecutionContext context) + { + runnable.run(); + } + + /** + * Overrides {@link Object#toString()}. + */ + @Override + public String toString() + { + StringBuffer b = new StringBuffer(); + b.append("Task["); + b.append("runnable="); + b.append(runnable); + b.append("]"); + return b.toString(); + } +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java new file mode 100644 index 0000000000..d284db56ac --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Scheduler.java @@ -0,0 +1,728 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * The cron4j scheduler. + *
+ * @author Carlo Pelliccia + */ +public class Scheduler +{ + /** + * A GUID for this scheduler. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The time zone applied by the scheduler. + */ + private TimeZone timezone = null; + + /** + * The daemon flag. If true the scheduler and its spawned threads acts like daemons. + */ + private boolean daemon = false; + + /** + * The state flag. If true the scheduler is started and running, otherwise it is paused and no task is launched. + */ + private boolean started = false; + + /** + * Registered {@link TaskCollector}s list. + */ + private final ArrayList+ * Sets the time zone applied by the scheduler. + *
+ *+ * Current system time is adapted to the supplied time zone before comparing it with registered scheduling patterns. The result is that any supplied scheduling pattern is treated according to the specified time zone. In example, suppose: + *
+ *+ * The scheduler, before comparing system time with patterns, translates 10:00 from GMT+1 to GMT+3. It means that 10:00 becomes 12:00. The resulted time is then used by the scheduler to activate tasks. So, in the given configuration at the given moment, any task scheduled as 0 12 * * * + * will be executed, while any 0 10 * * * will not. + *
+ * @param timezone The time zone applied by the scheduler. + */ + public void setTimeZone(TimeZone timezone) + { + this.timezone = timezone; + } + + /** + * Returns the time zone applied by the scheduler. + * @return The time zone applied by the scheduler. + */ + public TimeZone getTimeZone() + { + return timezone != null ? timezone : TimeZone.getDefault(); + } + + /** + * Tests whether this scheduler is a daemon scheduler. + * @return true if this scheduler is a daemon scheduler; false otherwise. + */ + public boolean isDaemon() + { + return daemon; + } + + /** + * Marks this scheduler daemon flag. When a scheduler is marked as a daemon scheduler it spawns only daemon threads. The Java Virtual Machine exits when the only threads running are all daemon threads. This method must be called before the scheduler is started. + * @param on If true, the scheduler will spawn only daemon threads. + * @throws IllegalStateException If the scheduler is started. + */ + public void setDaemon(boolean on) throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + this.daemon = on; + } + } + + /** + * Tests if this scheduler is started. + * @return true if the scheduler is started, false if it is stopped. + */ + public boolean isStarted() + { + synchronized (lock) + { + return started; + } + } + + /** + * Adds a {@link File} instance to the scheduler. Every minute the file will be parsed. The scheduler will execute any declared task whose scheduling pattern matches the current system time. See {@link CronParser} documentation for informations about the file contents syntax. + * @param file The {@link File} instance. + */ + public void scheduleFile(File file) + { + fileTaskCollector.addFile(file); + } + + /** + * Removes a {@link File} instance previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @param file The {@link File} instance. + */ + public void descheduleFile(File file) + { + fileTaskCollector.removeFile(file); + } + + /** + * Returns an array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + * @return An array containing any {@link File} previously scheduled with the {@link Scheduler#scheduleFile(File)} method. + */ + public File[] getScheduledFiles() + { + return fileTaskCollector.getFiles(); + } + + /** + * Adds a custom {@link TaskCollector} instance to the scheduler. The supplied object, once added to the scheduler, will be query every minute for its task list. The scheduler will execute any of the returned tasks whose scheduling pattern matches the current system time. + * @param collector The custom {@link TaskCollector} instance. + */ + public void addTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.add(collector); + } + } + + /** + * Removes a previously registered custom {@link TaskCollector} instance. + * @param collector The custom {@link TaskCollector} instance. + */ + public void removeTaskCollector(TaskCollector collector) + { + synchronized (collectors) + { + collectors.remove(collector); + } + } + + /** + * Returns an array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + * @return An array containing any custom {@link TaskCollector} instance previously registered in the scheduler with the {@link Scheduler#addTaskCollector(TaskCollector)} method. + */ + public TaskCollector[] getTaskCollectors() + { + synchronized (collectors) + { + // Discard the first 2 elements in the list. + int size = collectors.size() - 2; + TaskCollector[] ret = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + ret[i] = collectors.get(i + 2); + } + return ret; + } + } + + /** + * Adds a {@link SchedulerListener} to the scheduler. A {@link SchedulerListener} is notified every time a task is launching, has succeeded or has failed. + * @param listener The listener. + */ + public void addSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.add(listener); + } + } + + /** + * Removes a {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @param listener The listener. + */ + public void removeSchedulerListener(SchedulerListener listener) + { + synchronized (listeners) + { + listeners.remove(listener); + } + } + + /** + * Returns an array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + * @return An array containing any {@link SchedulerListener} previously registered with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method. + */ + public SchedulerListener[] getSchedulerListeners() + { + synchronized (listeners) + { + int size = listeners.size(); + SchedulerListener[] ret = new SchedulerListener[size]; + for (int i = 0; i < size; i++) + { + ret[i] = listeners.get(i); + } + return ret; + } + } + + /** + * Returns an array containing any currently executing task, in the form of {@link TaskExecutor} objects. Each running task is executed by a different thread. A {@link TaskExecutor} object allows the control of the running task. The inner {@link Task} representation could be retrieved, the + * status of the task could be detected and the thread could be interrupted using any standard {@link Thread} method ( {@link Thread#interrupt()}, {@link Thread#isAlive() etc}. + * @return An array containing any currently executing task, in the form of {@link TaskExecutor} objects. + */ + public TaskExecutor[] getExecutingTasks() + { + synchronized (executors) + { + int size = executors.size(); + TaskExecutor[] ret = new TaskExecutor[size]; + for (int i = 0; i < size; i++) + { + ret[i] = executors.get(i); + } + return ret; + } + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public String schedule(String schedulingPattern, Runnable task) throws InvalidPatternException + { + return schedule(schedulingPattern, new RunnableTask(task)); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @since 2.0 + */ + public String schedule(String schedulingPattern, Task task) throws InvalidPatternException + { + return schedule(new SchedulingPattern(schedulingPattern), task); + } + + /** + * This method schedules a task execution. + * @param schedulingPattern The scheduling pattern for the task. + * @param task The task, as a plain Runnable object. + * @return The task auto-generated ID assigned by the scheduler. This ID can be used later to reschedule and deschedule the task, and also to retrieve informations about it. + * @since 2.0 + */ + public String schedule(SchedulingPattern schedulingPattern, Task task) + { + return memoryTaskCollector.add(schedulingPattern, task); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + * @deprecated Use {@link Scheduler#reschedule(String, String)}. + */ + @Deprecated + public void reschedule(Object id, String schedulingPattern) throws InvalidPatternException + { + reschedule((String) id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @throws InvalidPatternException If the supplied pattern is not valid. + */ + public void reschedule(String id, String schedulingPattern) throws InvalidPatternException + { + reschedule(id, new SchedulingPattern(schedulingPattern)); + } + + /** + * This method changes the scheduling pattern of a task. + * @param id The ID assigned to the previously scheduled task. + * @param schedulingPattern The new scheduling pattern for the task. + * @since 2.0 + */ + public void reschedule(String id, SchedulingPattern schedulingPattern) + { + memoryTaskCollector.update(id, schedulingPattern); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + * @deprecated Use {@link Scheduler#deschedule(String)}. + */ + @Deprecated + public void deschedule(Object id) + { + deschedule((String) id); + } + + /** + * This methods cancels the scheduling of a task. + * @param id The ID of the task. + */ + public void deschedule(String id) + { + memoryTaskCollector.remove(id); + } + + /** + * This method retrieves a previously scheduled task. + * @param id The task ID. + * @return The requested task, or null if the task was not found. + * @since 2.0 + */ + public Task getTask(String id) + { + return memoryTaskCollector.getTask(id); + } + + /** + * This method retrieves a previously scheduled task scheduling pattern. + * @param id The task ID. + * @return The requested scheduling pattern, or null if the task was not found. + * @since 2.0 + */ + public SchedulingPattern getSchedulingPattern(String id) + { + return memoryTaskCollector.getSchedulingPattern(id); + } + + /** + * This method retrieves the Runnable object of a previously scheduled task. + * @param id The task ID. + * @return The Runnable object of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getTask(String)}. + */ + @Deprecated + public Runnable getTaskRunnable(Object id) + { + Task task = getTask((String) id); + if (task instanceof RunnableTask) + { + RunnableTask rt = (RunnableTask) task; + return rt.getRunnable(); + } + return null; + } + + /** + * This method retrieves the scheduling pattern of a previously scheduled task. + * @param id The task ID. + * @return The scheduling pattern of the task, or null if the task was not found. + * @deprecated Use {@link Scheduler#getSchedulingPattern(String)}. + */ + @Deprecated + public String getTaskSchedulingPattern(Object id) + { + return getSchedulingPattern((String) id).toString(); + } + + /** + * Executes immediately a task, without scheduling it. + * @param task The task. + * @return The {@link TaskExecutor} executing the given task. + * @throws IllegalStateException If the scheduler is not started. + */ + public TaskExecutor launch(Task task) + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + return spawnExecutor(task); + } + } + + /** + * This method starts the scheduler. When the scheduled is started the supplied tasks are executed at the given moment. + * @throws IllegalStateException Thrown if this scheduler is already started. + */ + public void start() throws IllegalStateException + { + synchronized (lock) + { + if (started) + { + throw new IllegalStateException("Scheduler already started"); + } + // Initializes required lists. + launchers = new ArrayList<>(); + executors = new ArrayList<>(); + // Starts the timer thread. + timer = new TimerThread(this); + timer.setDaemon(daemon); + timer.start(); + // Change the state of the scheduler. + started = true; + } + } + + /** + * This method stops the scheduler execution. Before returning, it waits the end of all the running tasks previously launched. Once the scheduler has been stopped it can be started again with a start() call. + * @throws IllegalStateException Thrown if this scheduler is not started. + */ + public void stop() throws IllegalStateException + { + synchronized (lock) + { + if (!started) + { + throw new IllegalStateException("Scheduler not started"); + } + // Interrupts the timer and waits for its death. + timer.interrupt(); + tillThreadDies(timer); + timer = null; + // Interrupts any running launcher and waits for its death. + for (;;) + { + LauncherThread launcher = null; + synchronized (launchers) + { + if (launchers.size() == 0) + { + break; + } + launcher = launchers.remove(0); + } + launcher.interrupt(); + tillThreadDies(launcher); + } + launchers = null; + // Interrupts any running executor and waits for its death. + // Before exiting wait for all the active tasks end. + for (;;) + { + TaskExecutor executor = null; + synchronized (executors) + { + if (executors.size() == 0) + { + break; + } + executor = executors.remove(0); + } + if (executor.canBeStopped()) + { + executor.stop(); + } + tillExecutorDies(executor); + } + executors = null; + // Change the state of the object. + started = false; + } + } + + // -- PACKAGE RESERVED METHODS -------------------------------------------- + + /** + * Starts a launcher thread. + * @param referenceTimeInMillis Reference time in millis for the launcher. + * @return The spawned launcher. + */ + LauncherThread spawnLauncher(long referenceTimeInMillis) + { + TaskCollector[] nowCollectors; + synchronized (collectors) + { + int size = collectors.size(); + nowCollectors = new TaskCollector[size]; + for (int i = 0; i < size; i++) + { + nowCollectors[i] = collectors.get(i); + } + } + LauncherThread l = new LauncherThread(this, nowCollectors, referenceTimeInMillis); + synchronized (launchers) + { + launchers.add(l); + } + l.setDaemon(daemon); + l.start(); + return l; + } + + /** + * Starts the given task within a task executor. + * @param task The task. + * @return The spawned task executor. + */ + TaskExecutor spawnExecutor(Task task) + { + TaskExecutor e = new TaskExecutor(this, task); + synchronized (executors) + { + executors.add(e); + } + e.start(daemon); + return e; + } + + /** + * This method is called by a launcher thread to notify that the execution is completed. + * @param launcher The launcher which has completed its task. + */ + void notifyLauncherCompleted(LauncherThread launcher) + { + synchronized (launchers) + { + launchers.remove(launcher); + } + } + + /** + * This method is called by a task executor to notify that the execution is completed. + * @param executor The executor which has completed its task. + */ + void notifyExecutorCompleted(TaskExecutor executor) + { + synchronized (executors) + { + executors.remove(executor); + } + } + + /** + * Notifies every registered listener that a task is going to be launched. + * @param executor The task executor. + */ + void notifyTaskLaunching(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskLaunching(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has successfully completed. + * @param executor The task executor. + */ + void notifyTaskSucceeded(TaskExecutor executor) + { + synchronized (listeners) + { + int size = listeners.size(); + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskSucceeded(executor); + } + } + } + + /** + * Notifies every registered listener that a task execution has failed due to an uncaught exception. + * @param executor The task executor. + * @param exception The exception. + */ + void notifyTaskFailed(TaskExecutor executor, Throwable exception) + { + synchronized (listeners) + { + int size = listeners.size(); + if (size > 0) + { + for (int i = 0; i < size; i++) + { + SchedulerListener l = listeners.get(i); + l.taskFailed(executor, exception); + } + } + else + { + // Logs on console if no one has been notified about it. + exception.printStackTrace(); + } + } + } + + // -- PRIVATE METHODS ----------------------------------------------------- + + /** + * It waits until the given thread is dead. It is similar to {@link Thread#join()}, but this one avoids {@link InterruptedException} instances. + * @param thread The thread. + */ + private void tillThreadDies(Thread thread) + { + boolean dead = false; + do + { + try + { + thread.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + + /** + * It waits until the given task executor is dead. It is similar to {@link TaskExecutor#join()}, but this one avoids {@link InterruptedException} instances. + * @param executor The task executor. + */ + private void tillExecutorDies(TaskExecutor executor) + { + boolean dead = false; + do + { + try + { + executor.join(); + dead = true; + } + catch (InterruptedException e) + { + } + } + while (!dead); + } + +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java new file mode 100644 index 0000000000..2f186eb42f --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulerListener.java @@ -0,0 +1,48 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Implement this interface and register your instance with the {@link Scheduler#addSchedulerListener(SchedulerListener)} method to receive notifications about scheduled task executions. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface SchedulerListener +{ + /** + * This one is called by the scheduler when a task execution is starting. + * @param executor The task executor. + */ + public void taskLaunching(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has been successfully completed. + * @param executor The task executor. + */ + public void taskSucceeded(TaskExecutor executor); + + /** + * This one is called by the scheduler to notify that a task execution has failed. + * @param executor The task executor. + * @param exception The exception representing the failure notification. + */ + public void taskFailed(TaskExecutor executor, Throwable exception); +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java index d4c8eaaa86..ac993fb52b 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/SchedulingPattern.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see+ * A scheduling patterns validator. + *
+ *+ * The class lets you validate a scheduling pattern before/without using it with a {@link Scheduler} instance. Simply call: + *
+ * + *+ * boolean valid = SchedulingPatternValidator.validate(thePattern); + *+ *
+ * It is useful in validating user-entered patterns. + *
+ * @author Carlo Pelliccia + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ +@Deprecated +public class SchedulingPatternValidator +{ + /** + * Validates a string as a scheduling pattern. + * @param schedulingPattern The pattern to validate. + * @return true if the given string represents a valid scheduling pattern; false otherwise. + * @deprecated Use {@link SchedulingPattern#validate(String)}. + */ + @Deprecated + public static boolean validate(String schedulingPattern) + { + return SchedulingPattern.validate(schedulingPattern); + } +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java new file mode 100644 index 0000000000..bbb04bf752 --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/StaticMethodTask.java @@ -0,0 +1,107 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A package-reserved utility class. It spawns a secondary thread in which the supplied {@link InputStream} instance is read, and the incoming contents are written in the supplied {@link OutputStream}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class StreamBridge +{ + /** + * Used to trace alive instances. + */ + static ArrayListmillis
milliseconds for this thread to die. A timeout of 0
means to wait forever.
+ * @param millis the time to wait in milliseconds.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis) throws InterruptedException
+ {
+ thread.join(millis);
+ }
+
+ /**
+ * @param millis the time to wait in milliseconds.
+ * @param nanos 0-999999 additional nanoseconds to wait.
+ * @throws IllegalArgumentException if the value of millis is negative the value of nanos is not in the range 0-999999.
+ * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.
+ */
+ public void join(long millis, int nanos) throws IllegalArgumentException, InterruptedException
+ {
+ thread.join(millis, nanos);
+ }
+
+ /**
+ * Tests if this bridge is alive. A job is alive if it has been started and has not yet completed.
+ * @return true
if this thread is alive; false
otherwise.
+ */
+ public boolean isAlive()
+ {
+ return thread.isAlive();
+ }
+
+ /**
+ * Contains the routine doing the job in the secondary thread.
+ */
+ private class Runner implements Runnable
+ {
+ public Runner()
+ {
+ }
+
+ @Override
+ public void run()
+ {
+ boolean skipout = false;
+ for (;;)
+ {
+ int b;
+ try
+ {
+ b = in.read();
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ break;
+ }
+ if (b == -1)
+ {
+ break;
+ }
+ if (!skipout)
+ {
+ try
+ {
+ out.write(b);
+ }
+ catch (IOException e)
+ {
+ if (!Thread.interrupted())
+ {
+ e.printStackTrace();
+ }
+ skipout = true;
+ }
+ }
+ }
+ try
+ {
+ out.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ try
+ {
+ in.close();
+ }
+ catch (Throwable t)
+ {
+ }
+ synchronized (traced)
+ {
+ traced.remove(myself);
+ }
+ }
+ }
+}
diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Task.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Task.java
new file mode 100644
index 0000000000..e46211e0f3
--- /dev/null
+++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/Task.java
@@ -0,0 +1,144 @@
+/*
+ * cron4j - A pure Java cron-like scheduler
+ *
+ * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version
+ * 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License 2.1 for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License version 2.1 along with this program.
+ * If not, see + * Abstract base representation of a cron4j task. + *
+ *+ * Developers can extends this abstract class to build their own tasks. + *
+ *+ * Extending Task means, above all, implementing the {@link Task#execute(TaskExecutionContext)} method. Within this method the task must perform its operation. If the execute() method returns regularly then the execution is considered to be successfully completed. If execute() dies + * throwing a {@link RuntimeException} then the task execution is considered to be failed. The supplied parameter, which is a {@link TaskExecutionContext} instance, helps the developer in integrating his task with the scheduler executor. Through the context the developer can check if the execution + * has been paused or stopped, and he can also push back some status informations by calling {@link TaskExecutionContext#setCompleteness(double)} and {@link TaskExecutionContext#setStatusMessage(String)}. + *
+ *+ * If the custom task supports pausing, stopping and/or tracking, that should be notified by overriding {@link Task#canBePaused()}, {@link Task#canBeStopped()}, {@link Task#supportsCompletenessTracking()} and/or {@link Task#supportsStatusTracking()}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public abstract class Task +{ + /** + * The ID for this task. Also used as an instance synchronization lock. + */ + private final Object id = GUIDGenerator.generate(); + + /** + * Empty constructor, does nothing. + */ + public Task() + { + } + + /** + * It returns the ID for this task. + * @return The ID for this task. + */ + Object getId() + { + return id; + } + + /** + *+ * Checks whether this task supports pause requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that pause requests are really handled. This can be done calling regularly the + * {@link TaskExecutionContext#pauseIfRequested()} method during the task execution. + *
+ * @return true if this task can be paused; false otherwise. + */ + public boolean canBePaused() + { + return false; + } + + /** + *+ * Checks whether this task supports stop requests. + *
+ *+ * Default implementation returns false. + *
+ *+ * Task developers can override this method to let it return a true value, and at the same time they have to implement the {@link Task#execute(TaskExecutionContext)} method, so that stop requests are really handled. This can be done checking regularly the + * {@link TaskExecutionContext#isStopped()} method during the task execution. + *
+ * @return true if this task can be stopped; false otherwise. + */ + public boolean canBeStopped() + { + return false; + } + + /** + *+ * Tests whether this task supports status tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setStatusMessage(String)} method during the task execution. + *
+ * @return true if this task, during its execution, provides status message regularly. + */ + public boolean supportsStatusTracking() + { + return false; + } + + /** + *+ * Tests whether this task supports completeness tracking. + *
+ *+ * Default implementation returns false. + *
+ *+ * The task developer can override this method and returns true, having care to regularly calling the {@link TaskExecutionContext#setCompleteness(double)} method during the task execution. + *
+ * @return true if this task, during its execution, provides a completeness value regularly. + */ + public boolean supportsCompletenessTracking() + { + return false; + } + + /** + *+ * This method is called to require a task execution, and should contain the core routine of any scheduled task. + *
+ *+ * If the execute() method ends regularly the scheduler will consider the execution successfully completed, and this will be communicated to any {@link SchedulerListener} interested in it. If the execute() method dies throwing a {@link RuntimeException} the scheduler will + * consider it as a failure notification. Any {@link SchedulerListener} will be notified about the occurred exception. + *
+ * @param context The execution context. + * @throws RuntimeException Task execution has somehow failed. + */ + public abstract void execute(TaskExecutionContext context) throws RuntimeException; +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java new file mode 100644 index 0000000000..4cb2f9fa2f --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskCollector.java @@ -0,0 +1,37 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * This interface describes a task collector. Task collectors can be registered in a {@link Scheduler} instance with the {@link Scheduler#addTaskCollector(TaskCollector)} method. Any registered task collector is queried by the scheduler once a minute. The developer has to implement the + * {@link TaskCollector#getTasks()} method, returning a {@link TaskTable} whose elements has been collected with a custom logic. In example the list can be extracted from a database. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskCollector +{ + /** + * Once the instance has been registered on a {@link Scheduler} instance, with the {@link Scheduler#addTaskCollector(TaskCollector)} method, this method will be queried once a minute. It should return a custom {@link TaskTable} object. The scheduler instance will automatically iterate over the + * returned table elements, executing any task whose scheduling pattern is matching the current system time. + * @return The task table that will be automatically injected in the scheduler. + */ + public TaskTable getTasks(); +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java new file mode 100644 index 0000000000..8e0d1a8c05 --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutionContext.java @@ -0,0 +1,67 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * A TaskExecutionContext object provides support methods for the execution of a task. An instance of this class is always passed to the task when its {@link Task#execute(TaskExecutionContext)} method is called. The task, while executing, can use the received context to exchange informations with + * its own executor. If the task declares to supports pausing, stopping, completeness tracking and/or status tracking, it has to use its context methods to perform any declared operation (checks pause and stop requests, sends back tracking informations). + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public interface TaskExecutionContext +{ + /** + * Returns the scheduler. + * @return The scheduler. + */ + public Scheduler getScheduler(); + + /** + * Returns the task executor. + * @return The task executor. + */ + public TaskExecutor getTaskExecutor(); + + /** + * Sets the current status tracking message, that has to be something about what the task is doing at the moment. + * @param message A message representing the current execution status. Null messages will be blanked. + */ + public void setStatusMessage(String message); + + /** + * Sets the completeness tracking value, that has to be between 0 and 1. + * @param completeness A completeness value, between 0 and 1. Values out of range will be ignored. + */ + public void setCompleteness(double completeness); + + /** + * If the task execution has been paused, stops until the operation is resumed. It can also returns because of a stop operation without any previous resuming. Due to this the task developer should always check the {@link TaskExecutionContext#isStopped()} value after any + * pauseIfRequested() call. Note that a task execution can be paused only if the task {@link Task#canBePaused()} method returns true. + */ + public void pauseIfRequested(); + + /** + * Checks whether the task execution has been demanded to be stopped. If the returned value is true, the task developer must shut down gracefully its task execution, as soon as possible. Note that a task execution can be stopped only if the task {@link Task#canBePaused()} method returns + * true. + * @return true if the current task execution has been demanded to be stopped; false otherwise. + */ + public boolean isStopped(); +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java new file mode 100644 index 0000000000..4911f2826c --- /dev/null +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/TaskExecutor.java @@ -0,0 +1,612 @@ +/* + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License 2.1 for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License version 2.1 along with this program. + * If not, see+ * Represents a task executor, which is something similar to threads. + *
+ *+ * Each time a task is launched, a new executor is spawned, executing and watching the task + *
+ *+ * Alive task executors can be retrieved with the {@link Scheduler#getExecutingTasks()} method, and they expose method to control the ongoing execution. + *
+ * @see Scheduler#getExecutingTasks() + * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskExecutor +{ + /** + * The scheduler whose this executor belongs to. + */ + final Scheduler scheduler; + + /** + * The executed task. + */ + final Task task; + + /** + * A task execution context. + */ + final MyContext context; + + /** + * A unique ID for this executor (used also as a lock object). + */ + private final String guid = GUIDGenerator.generate(); + + /** + * An alternative to this (inner classes need it). + */ + final TaskExecutor myself = this; + + /** + * A list of {@link TaskExecutorListener} instances. + */ + private final ArrayList+ * A table coupling tasks with scheduling patterns. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +public class TaskTable +{ + /** + * Table size. + */ + private int size = 0; + + /** + * Pattern list. + */ + private final ArrayList+ * TimeThreads are used by {@link Scheduler} instances. A TimerThread spends most of the time sleeping. It wakes up every minute and it requests to the scheduler the spawning of a {@link LauncherThread}. + *
+ * @author Carlo Pelliccia + * @since 2.0 + */ +class TimerThread extends Thread +{ + /** + * A GUID for this object. + */ + private final String guid = GUIDGenerator.generate(); + + /** + * The owner scheduler. + */ + private Scheduler scheduler; + + /** + * Builds the timer thread. + * @param scheduler The owner scheduler. + */ + public TimerThread(Scheduler scheduler) + { + this.scheduler = scheduler; + // Thread name. + String name = "cron4j::scheduler[" + scheduler.getGuid() + "]::timer[" + guid + "]"; + setName(name); + } + + /** + * Returns the GUID for this object. + * @return The GUID for this object. + */ + public Object getGuid() + { + return guid; + } + + /** + * It has been reported that the {@link Thread#sleep(long)} method sometimes exits before the requested time has passed. This one offers an alternative that sometimes could sleep a few millis more than requested, but never less. + * @param millis The length of time to sleep in milliseconds. + * @throws InterruptedException If another thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown. + * @see Thread#sleep(long) + */ + 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); + } + + /** + * Overrides {@link Thread#run()}. + */ + @Override + public void run() + { + // What time is it? + long millis = System.currentTimeMillis(); + // Calculating next minute. + long nextMinute = ((millis / 60000) + 1) * 60000; + // Work until the scheduler is started. + for (;;) + { + // Coffee break 'till next minute comes! + long sleepTime = (nextMinute - System.currentTimeMillis()); + if (sleepTime > 0) + { + try + { + safeSleep(sleepTime); + } + catch (InterruptedException e) + { + // Must exit! + break; + } + } + // What time is it? + millis = System.currentTimeMillis(); + // Launching the launching thread! + scheduler.spawnLauncher(millis); + // Calculating next minute. + nextMinute = ((millis / 60000) + 1) * 60000; + } + // Discard scheduler reference. + scheduler = null; + } +} diff --git a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java index 8e7b79e0d3..c5c1a0e5ec 100644 --- a/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java +++ b/L2J_Mobius_Underground/java/com/l2jmobius/gameserver/util/cron4j/ValueMatcher.java @@ -1,18 +1,20 @@ /* - * This file is part of the L2J Mobius project. + * cron4j - A pure Java cron-like scheduler + * + * Copyright (C) 2007-2010 Carlo Pelliccia (www.sauronsoftware.it) * * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * + * it under the terms of the GNU Lesser General Public License version + * 2.1, as published by the Free Software Foundation. + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see