|
|
@@ -0,0 +1,284 @@
|
|
|
+package net.ranides.assira.time;
|
|
|
+
|
|
|
+import net.ranides.assira.awt.AWTInvoker;
|
|
|
+import net.ranides.assira.events.Event;
|
|
|
+import net.ranides.assira.events.EventDispatcher;
|
|
|
+import net.ranides.assira.events.EventHandler;
|
|
|
+import net.ranides.assira.events.EventRouter;
|
|
|
+
|
|
|
+import java.time.Duration;
|
|
|
+import java.util.TimerTask;
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
+import java.util.concurrent.RunnableFuture;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.concurrent.TimeoutException;
|
|
|
+import java.util.concurrent.atomic.AtomicInteger;
|
|
|
+import java.util.function.Consumer;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.function.Supplier;
|
|
|
+
|
|
|
+public class TaskScheduler {
|
|
|
+
|
|
|
+ private EventRouter router;
|
|
|
+
|
|
|
+ private String routerName;
|
|
|
+
|
|
|
+ private Duration delay;
|
|
|
+
|
|
|
+ private Duration interval;
|
|
|
+
|
|
|
+ private Integer repeat;
|
|
|
+
|
|
|
+ private Consumer<Integer> actionInterval;
|
|
|
+
|
|
|
+ private Consumer<Integer> actionStart;
|
|
|
+
|
|
|
+ private Consumer<Integer> actionFinish;
|
|
|
+
|
|
|
+ private Consumer<Integer> actionCancel;
|
|
|
+
|
|
|
+ private Function<Integer, Event> eventInterval;
|
|
|
+
|
|
|
+ private Function<Integer, Event> eventStart;
|
|
|
+
|
|
|
+ private Function<Integer, Event> eventFinish;
|
|
|
+
|
|
|
+ private Function<Integer, Event> eventCancel;
|
|
|
+
|
|
|
+ public TaskScheduler() {
|
|
|
+ // do nothing
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler router(EventRouter router) {
|
|
|
+ this.router = router;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler router(String router) {
|
|
|
+ this.routerName = router;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler delay(long millis) {
|
|
|
+ return delay(Duration.ofMillis(millis));
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler delay(Duration duration) {
|
|
|
+ this.delay = duration;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(long interval) {
|
|
|
+ return interval(Duration.ofMillis(interval));
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(Duration interval) {
|
|
|
+ this.interval = interval;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler repeat(int repeat) {
|
|
|
+ this.repeat = repeat;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler start(Runnable action) {
|
|
|
+ this.actionStart = (v) -> action.run();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler start(Supplier<Event> action) {
|
|
|
+ this.eventStart = (v) -> action.get();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler finish(Runnable action) {
|
|
|
+ this.actionFinish = (v) -> action.run();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler finish(Supplier<Event> action) {
|
|
|
+ this.eventFinish = (v) -> action.get();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(Consumer<Integer> action) {
|
|
|
+ this.actionInterval = action;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(Runnable action) {
|
|
|
+ this.actionInterval = (v) -> action.run();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(Function<Integer, Event> action) {
|
|
|
+ this.eventInterval = action;
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler interval(Supplier<Event> action) {
|
|
|
+ this.eventInterval = (v) -> action.get();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler cancel(Runnable action) {
|
|
|
+ this.actionCancel = (v) -> action.run();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TaskScheduler cancel(Supplier<Event> action) {
|
|
|
+ this.eventCancel = (v) -> action.get();
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public TimerTask build() {
|
|
|
+
|
|
|
+ initRouter();
|
|
|
+
|
|
|
+ actionStart = initDispatch(actionStart, eventStart);
|
|
|
+ actionCancel = initDispatch(actionCancel, eventCancel);
|
|
|
+ actionInterval = initDispatch(actionInterval, eventInterval);
|
|
|
+ actionFinish = initDispatch(actionFinish, eventFinish);
|
|
|
+
|
|
|
+ initIntervalStart();
|
|
|
+
|
|
|
+ boolean awt = EventHandler.UI.equals(routerName) || EventHandler.UI_WAIT.equals(routerName);
|
|
|
+ boolean snc = EventHandler.UI_WAIT.equals(routerName);
|
|
|
+ if(awt) {
|
|
|
+ actionStart = initUI(actionStart, snc);
|
|
|
+ actionFinish = initUI(actionFinish, snc);
|
|
|
+ actionCancel = initUI(actionCancel, snc);
|
|
|
+ actionInterval = initUI(actionInterval, snc);
|
|
|
+ }
|
|
|
+
|
|
|
+ return newTimer();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initRouter() {
|
|
|
+ if(routerName != null) {
|
|
|
+ if(router != null) {
|
|
|
+ throw new IllegalStateException("Both router and router name is specified in invoker");
|
|
|
+ }
|
|
|
+ router = EventDispatcher.find(routerName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Consumer<Integer> initDispatch(Consumer<Integer> action, Function<Integer, Event> event) {
|
|
|
+ if(event == null) {
|
|
|
+ return action;
|
|
|
+ }
|
|
|
+ if(action != null) {
|
|
|
+ throw new IllegalStateException("Both action and event supplier is specified in invoker");
|
|
|
+ }
|
|
|
+ if(router == EventDispatcher.NULL) {
|
|
|
+ throw new IllegalStateException("Event supplier is defined, but event dispatcher not");
|
|
|
+ }
|
|
|
+ return (v) -> router.signalEvent( event.apply(v) );
|
|
|
+ }
|
|
|
+
|
|
|
+ private void initIntervalStart() {
|
|
|
+ if(interval == null) {
|
|
|
+ if(actionInterval != null) {
|
|
|
+ throw new IllegalStateException("Task has defined repeat interval but not interval action");
|
|
|
+ }
|
|
|
+ if(actionStart != null && actionFinish != null) {
|
|
|
+ actionStart = (v) -> { actionStart.accept(v); actionFinish.accept(v); };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Consumer<Integer> initUI(Consumer<Integer> action, boolean sync) {
|
|
|
+ if(action == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return (v) -> AWTInvoker.run(sync, () -> action.accept(v));
|
|
|
+ }
|
|
|
+
|
|
|
+ private TimerTask newTimer() {
|
|
|
+ if(actionInterval != null) {
|
|
|
+ Integer max = initMax();
|
|
|
+ return TimerInvoker.repeat(toMillis(delay), toMillis(interval), new IntervalTask(max, actionStart, actionCancel, actionFinish, actionInterval));
|
|
|
+ } else {
|
|
|
+ return TimerInvoker.later(toMillis(delay), new LaterTask(actionStart, actionCancel));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer initMax() {
|
|
|
+ if(repeat == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return repeat + (actionStart !=null ? 1 : 0) + (actionFinish !=null ? 1 : 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static long toMillis(Duration duration) {
|
|
|
+ return duration!=null ? duration.toMillis() : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class LaterTask extends TimerTask {
|
|
|
+
|
|
|
+ protected final Consumer<Integer> rStart;
|
|
|
+
|
|
|
+ protected final Consumer<Integer> rCancel;
|
|
|
+
|
|
|
+ public LaterTask(Consumer<Integer> rStart, Consumer<Integer> rCancel) {
|
|
|
+ this.rStart = rStart;
|
|
|
+ this.rCancel = rCancel;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ rStart.accept(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean cancel() {
|
|
|
+ if(super.cancel()) {
|
|
|
+ if(rCancel!=null) {
|
|
|
+ rCancel.accept(0);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class IntervalTask extends LaterTask {
|
|
|
+
|
|
|
+ private final AtomicInteger counter = new AtomicInteger(0);
|
|
|
+
|
|
|
+ private final Integer max;
|
|
|
+
|
|
|
+ protected final Consumer<Integer> rFinish;
|
|
|
+
|
|
|
+ protected final Consumer<Integer> rInterval;
|
|
|
+
|
|
|
+ public IntervalTask(Integer max, Consumer<Integer> rStart, Consumer<Integer> rCancel, Consumer<Integer> rFinish, Consumer<Integer> rInterval) {
|
|
|
+ super(rStart, rCancel);
|
|
|
+ this.max = max;
|
|
|
+ this.rFinish = rFinish;
|
|
|
+ this.rInterval = rInterval;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void run() {
|
|
|
+ int now = counter.getAndIncrement();
|
|
|
+ if(now == 0 && rStart!=null) {
|
|
|
+ rStart.accept(0);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if(max != null && now > max) {
|
|
|
+ super.cancel();
|
|
|
+ if(rFinish != null) {
|
|
|
+ rFinish.accept(now);
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ rInterval.accept(rStart!=null ? now-1 : now);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|