Browse Source

#88
rename: CancelableRunnable
new: CancelableConsumer
new: EventHandler.LAZY supported
new: TaskInvoker#lazy(consumer)

Ranides Atterwim 3 năm trước cách đây
mục cha
commit
aacb7b1d30

+ 101 - 7
assira.core/src/main/java/net/ranides/assira/concurrent/TaskInvoker.java

@@ -1,11 +1,13 @@
 package net.ranides.assira.concurrent;
 
 import lombok.experimental.UtilityClass;
-import net.ranides.assira.functional.special.Cancelable;
+import net.ranides.assira.functional.special.CancelableRunnable;
+import net.ranides.assira.functional.special.CancelableConsumer;
 
 import java.util.Timer;
 import java.util.TimerTask;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
 
 /**
  * Utility methods scheduling tasks using global instance of java.util.Timer
@@ -94,11 +96,37 @@ public class TaskInvoker {
      *
      * @param delay delay in ms
      * @param action action
-     * @return Cancelable
+     * @return CancelableRunnable
      */
-    public static Cancelable lazy(long delay, Runnable action) {
-        // @todo #87
-        return new LazyCancelable(delay, action);
+    public static CancelableRunnable lazy(long delay, Runnable action) {
+        return new LazyRunnable(delay, action);
+    }
+
+    /**
+     * Creates object which can be used to execute action after "delay" milliseconds.
+     * If the same action is scheduled more than one time, it will cancel previous calls.
+     * Returned object can be used to execute or cancel action.
+     *
+     * Example usage:
+     *      action = lazy(1000, () -> code...)
+     *
+     *      action.run()
+     *      ...
+     *      action.run()
+     *
+     * Effect will be dependent on delay between "run" calls.
+     *
+     * It is guaranteed that "code..." will be executed ~500ms after last "run".
+     *
+     * Other "run" calls can cause execution of code, only if delay between them is bigger than 500ms
+     * If not, "code..." execution will be canceled and just moved 500ms into the future.
+     *
+     * @param delay delay in ms
+     * @param action action
+     * @return CancelableRunnable
+     */
+    public static <T> CancelableConsumer<T> lazy(long delay, Consumer<T> action) {
+        return new LazyConsumer<>(delay, action);
     }
 
     private static TimerTask asTimerTask(Runnable action) {
@@ -110,7 +138,7 @@ public class TaskInvoker {
         };
     }
 
-    private static class LazyCancelable implements Cancelable {
+    private static class LazyRunnable implements CancelableRunnable {
 
         private final AtomicReference<TimerTask> task = new AtomicReference<>();
 
@@ -122,7 +150,7 @@ public class TaskInvoker {
 
         private final Runnable action;
 
-        public LazyCancelable(long delay, Runnable action) {
+        public LazyRunnable(long delay, Runnable action) {
             this.delay = delay;
             this.action = action;
         }
@@ -176,4 +204,70 @@ public class TaskInvoker {
         }
     }
 
+    private static class LazyConsumer<T> implements CancelableConsumer<T> {
+
+        private final AtomicReference<TimerTask> task = new AtomicReference<>();
+
+        private volatile boolean finished;
+
+        private volatile boolean canceled;
+
+        private final long delay;
+
+        private final Consumer<T> action;
+
+        public LazyConsumer(long delay, Consumer<T> action) {
+            this.delay = delay;
+            this.action = action;
+        }
+
+        @Override
+        public void accept(T argument) {
+            task.getAndUpdate(prev -> {
+                if(prev != null) {
+                    prev.cancel();
+                }
+                this.canceled = false;
+                this.finished = false;
+                return later(delay, () -> call(argument));
+            });
+        }
+
+        private void call(T argument) {
+            try {
+                action.accept(argument);
+            } finally {
+                task.set(null);
+                finished = true;
+                canceled = true;
+            }
+        }
+
+        @Override
+        public boolean cancel() {
+            if(this.canceled || this.finished) {
+                return false;
+            }
+            task.getAndUpdate(prev -> {
+                if(prev != null) {
+                    prev.cancel();
+                    this.canceled = true;
+                    this.finished = false;
+                }
+                return null;
+            });
+            return true;
+        }
+
+        @Override
+        public boolean isDone() {
+            return this.finished;
+        }
+
+        @Override
+        public boolean isCanceled() {
+            return this.canceled;
+        }
+    }
+
 }

+ 18 - 1
assira.core/src/main/java/net/ranides/assira/events/EventObserver.java

@@ -10,6 +10,7 @@ import java.util.List;
 
 import net.ranides.assira.concurrent.SwingInvoker;
 import net.ranides.assira.functional.special.AnyFunction;
+import net.ranides.assira.functional.special.CancelableConsumer;
 import net.ranides.assira.reflection.IAttribute;
 import net.ranides.assira.reflection.IClass;
 import net.ranides.assira.reflection.IClasses;
@@ -74,7 +75,7 @@ public class EventObserver implements EventListener<Event> {
             case EventHandler.LATER:
                 return new LaterHandler(type, m.bind(delegator), info.delay());
             case EventHandler.LAZY:
-                throw new UnsupportedOperationException("@todo #88");
+                return new LazyHandler(type, m.bind(delegator), info.delay());
             default:
                 return new NHandler(router, type, m.bind(delegator));
         }
@@ -139,6 +140,22 @@ public class EventObserver implements EventListener<Event> {
         }
 
     }
+
+    private static final class LazyHandler extends IHandler {
+
+        private final CancelableConsumer<Object> handle;
+
+        public LazyHandler(IClass<?> type, AnyFunction<?> handle, int delay) {
+            super(type);
+            this.handle = TaskInvoker.lazy(delay, event -> handle.call(event));
+        }
+
+        @Override
+        public void call(Event event) {
+            handle.accept(event);
+        }
+
+    }
     
     private static final class UIHandler extends IHandler {
         

+ 33 - 0
assira.core/src/main/java/net/ranides/assira/functional/special/CancelableConsumer.java

@@ -0,0 +1,33 @@
+package net.ranides.assira.functional.special;
+
+import java.util.function.Consumer;
+
+/**
+ * This interface represents Runnable task which can be canceled.
+ * It is used by TaskInvoker.
+ */
+public interface CancelableConsumer<T> extends Consumer<T> {
+
+    /**
+     * Cancels execution
+     * Returns false if it is impossible to cancel because it is already canceled or finished.
+     *
+     * @return boolean
+     */
+    boolean cancel();
+
+    /**
+     * Returns true if it is finished successfully.
+     *
+     * @return boolean
+     */
+    boolean isDone();
+
+    /**
+     * Returns true if it was canceled.
+     *
+     * @return boolean
+     */
+    boolean isCanceled();
+
+}

+ 1 - 1
assira.core/src/main/java/net/ranides/assira/functional/special/Cancelable.java

@@ -4,7 +4,7 @@ package net.ranides.assira.functional.special;
  * This interface represents Runnable task which can be canceled.
  * It is used by TaskInvoker.
  */
-public interface Cancelable extends Runnable {
+public interface CancelableRunnable extends Runnable {
 
     /**
      * Cancels execution

+ 3 - 3
assira.core/src/test/java/net/ranides/assira/concurrent/TaskInvokerTest.java

@@ -1,6 +1,6 @@
 package net.ranides.assira.concurrent;
 
-import net.ranides.assira.functional.special.Cancelable;
+import net.ranides.assira.functional.special.CancelableRunnable;
 import net.ranides.assira.junit.ManualTestUtils;
 import org.junit.Ignore;
 import org.junit.Test;
@@ -14,7 +14,7 @@ public class TaskInvokerTest {
     public void lazyOnce() {
         final long ts = System.currentTimeMillis();
 
-        Cancelable lazy = TaskInvoker.lazy(500, () -> {
+        CancelableRunnable lazy = TaskInvoker.lazy(500, () -> {
             System.out.println("t=" + (System.currentTimeMillis()-ts) + " executed" );
         });
 
@@ -38,7 +38,7 @@ public class TaskInvokerTest {
     public void lazyMany() {
         final long ts = System.currentTimeMillis();
 
-        Cancelable lazy = TaskInvoker.lazy(500, () -> {
+        CancelableRunnable lazy = TaskInvoker.lazy(500, () -> {
             System.out.println("t=" + (System.currentTimeMillis()-ts) + " executed" );
         });