From b2bb3da8d32eedfd99c9c7de2c88865003c5650d Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 10 Nov 2017 07:12:26 -0800 Subject: [PATCH 1/5] Converted API to 1.8 only. - Changes the Listener abstract classes into interfaces with default methods. - Isolates SignalView.Listener from ValueView.Listener and makes Slot into a functional interface that works for both. - Eliminates Functions because that can be done with lambdas more concisely than the Functions helpers. - Moves Slots statics into Slot since interfaces can have static methods now. - Eliminates UnitSlot as it's easy enough to have an ignored parameter in a lambda (and Java 9/10 will allow use of _ parameter). UnitSignal becomes Signal.Unit to reduce top-level namespace names. - Removes various methods/helpers that existed only to save boilerplate which is now better accomplished via lambdas and/or bound methods. RPromise.completer, for example, which can now be expressed as p::complete. - Moves Closeable.Util helpers into Closeable because we can have static members of interfaces now. --- pom.xml | 4 +- src/main/java/react/AbstractSignal.java | 6 +- src/main/java/react/AbstractValue.java | 48 ++---- src/main/java/react/Closeable.java | 76 +++++----- src/main/java/react/Connection.java | 9 +- src/main/java/react/Function.java | 18 --- src/main/java/react/Functions.java | 161 -------------------- src/main/java/react/RCollection.java | 4 +- src/main/java/react/RFuture.java | 42 +++--- src/main/java/react/RList.java | 34 ++--- src/main/java/react/RMap.java | 28 ++-- src/main/java/react/RPromise.java | 27 ---- src/main/java/react/RQueue.java | 6 +- src/main/java/react/RSet.java | 14 +- src/main/java/react/Signal.java | 32 ++-- src/main/java/react/SignalView.java | 7 +- src/main/java/react/Slot.java | 61 ++++---- src/main/java/react/Slots.java | 33 ----- src/main/java/react/Try.java | 2 + src/main/java/react/UnitSignal.java | 38 ----- src/main/java/react/UnitSlot.java | 52 ------- src/main/java/react/Value.java | 12 -- src/main/java/react/ValueView.java | 27 +--- src/main/java/react/Values.java | 115 ++++++--------- src/test/java/react/RFutureTest.java | 117 +++++---------- src/test/java/react/RListTest.java | 2 +- src/test/java/react/RMapTest.java | 6 +- src/test/java/react/RQueueTest.java | 2 +- src/test/java/react/RSetTest.java | 2 +- src/test/java/react/SignalTest.java | 188 +++++++++--------------- src/test/java/react/TestBase.java | 2 +- src/test/java/react/ValueTest.java | 71 +++------ src/test/java/react/ValuesTest.java | 11 +- 33 files changed, 349 insertions(+), 908 deletions(-) delete mode 100644 src/main/java/react/Function.java delete mode 100644 src/main/java/react/Functions.java delete mode 100644 src/main/java/react/Slots.java delete mode 100644 src/main/java/react/UnitSignal.java delete mode 100644 src/main/java/react/UnitSlot.java diff --git a/pom.xml b/pom.xml index def8e61..f793b6a 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.threerings react jar - 1.6-SNAPSHOT + 2.0-SNAPSHOT react A signals/slots plus FRP-like library for Java. @@ -47,7 +47,7 @@ - 1.7 + 1.8 UTF-8 diff --git a/src/main/java/react/AbstractSignal.java b/src/main/java/react/AbstractSignal.java index 9789036..5cec4f9 100644 --- a/src/main/java/react/AbstractSignal.java +++ b/src/main/java/react/AbstractSignal.java @@ -5,6 +5,8 @@ package react; +import java.util.function.Function; + /** * Handles the machinery of connecting slots to a signal and emitting events to them, without * exposing a public interface for emitting events. This can be used by entities which wish to @@ -58,7 +60,7 @@ public class AbstractSignal extends Reactor implements SignalView @Override public RFuture next () { final RPromise result = RPromise.create(); - connect(result.succeeder()).once(); + connect(result::succeed).once(); return result; } @@ -71,7 +73,7 @@ public class AbstractSignal extends Reactor implements SignalView } @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)Slots.NOOP; + @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; return p; } diff --git a/src/main/java/react/AbstractValue.java b/src/main/java/react/AbstractValue.java index 3a9bbb3..c78a7f9 100644 --- a/src/main/java/react/AbstractValue.java +++ b/src/main/java/react/AbstractValue.java @@ -5,6 +5,8 @@ package react; +import java.util.function.Function; + /** * Handles the machinery of connecting listeners to a value and notifying them, without exposing a * public interface for updating the value. This can be used by libraries which wish to provide @@ -23,11 +25,7 @@ public abstract class AbstractValue extends Reactor implements ValueView return outer + ".map(" + func + ")"; } @Override protected Connection connect () { - return outer.connect(new Listener() { - @Override public void onChange (T value, T ovalue) { - notifyChange(func.apply(value), func.apply(ovalue)); - } - }); + return outer.connect((v, ov) -> notifyChange(func.apply(v), func.apply(ov))); } }; } @@ -46,14 +44,8 @@ public abstract class AbstractValue extends Reactor implements ValueView return outer + ".flatMap(" + func + ")"; } @Override protected Connection connect () { - conn = mapped.connect(new UnitSlot() { - public void onEmit () { reconnect(); } - }); - return mapped.get().connect(new Listener() { - @Override public void onChange (M value, M ovalue) { - notifyChange(value, ovalue); - } - }); + conn = mapped.connect(v -> reconnect()); + return mapped.get().connect((v, ov) -> notifyChange(v, ov)); } @Override protected void disconnect () { super.disconnect(); @@ -66,11 +58,7 @@ public abstract class AbstractValue extends Reactor implements ValueView final AbstractValue outer = this; return new MappedSignal() { @Override protected Connection connect () { - return outer.connect(new ValueView.Listener() { - @Override public void onChange (T value, T oldValue) { - notifyEmit(value); - } - }); + return outer.connect((v, ov) -> notifyEmit(v)); } }; } @@ -102,25 +90,11 @@ public abstract class AbstractValue extends Reactor implements ValueView } } - @Override public Connection connect (SignalView.Listener listener) { - return connect(wrap(listener)); - } - @Override public Connection connectNotify (SignalView.Listener listener) { - return connectNotify(wrap(listener)); - } - private static Listener wrap (final SignalView.Listener listener) { - return new Listener() { - public void onChange (T newValue, T oldValue) { - listener.onEmit(newValue); - } - }; - } - - @Override public Connection connect (Slot slot) { - return connect((Listener)slot); + @Override public Connection connect (Slot listener) { + return connect((Listener)listener); } - @Override public Connection connectNotify (Slot slot) { - return connectNotify((Listener)slot); + @Override public Connection connectNotify (Slot listener) { + return connectNotify((Listener)listener); } @Override public void disconnect (Listener listener) { @@ -146,7 +120,7 @@ public void onChange (T newValue, T oldValue) { } @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)Slots.NOOP; + @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; return p; } diff --git a/src/main/java/react/Closeable.java b/src/main/java/react/Closeable.java index b06bfb0..a75c1cb 100644 --- a/src/main/java/react/Closeable.java +++ b/src/main/java/react/Closeable.java @@ -48,49 +48,45 @@ public void remove (AutoCloseable c) { protected HashSet _set; // lazily created } - /** Provides some {@link Closeable}-related utilities. */ - class Util { + /** A closable which no-ops on {@link #close} and throws an exception for all other + * methods. This is for the following code pattern: + * + *
{@code
+      * Closable _conn = Closeable.Util.NOOP;
+      * void open () {
+      *    _conn = whatever.connect(...);
+      * }
+      * void close () {
+      *    _conn = Closeable.Util.close(_conn);
+      * }
+      * }
+ * + * In that it allows {@code close} to avoid a null check if it's possible for + * {@code close} to be called with no call to {@code open} or repeatedly. + */ + static final Closeable NOOP = new Closeable() { + public void close () {} // noop! + }; - /** A closable which no-ops on {@link #close} and throws an exception for all other - * methods. This is for the following code pattern: - * - *
{@code
-          * Closable _conn = Closeable.Util.NOOP;
-          * void open () {
-          *    _conn = whatever.connect(...);
-          * }
-          * void close () {
-          *    _conn = Closeable.Util.close(_conn);
-          * }
-          * }
- * - * In that it allows {@code close} to avoid a null check if it's possible for - * {@code close} to be called with no call to {@code open} or repeatedly. - */ - public static final Closeable NOOP = new Closeable() { - public void close () {} // noop! - }; - - /** Creates a closable that closes multiple connections at once. */ - public static Closeable join (final Closeable... cons) { - return new Closeable() { - @Override public void close () { - for (int ii = 0; ii < cons.length; ii++) { - if (cons[ii] == null) continue; - cons[ii].close(); - cons[ii] = null; - } + /** Creates a closable that closes multiple connections at once. */ + static Closeable join (final Closeable... cons) { + return new Closeable() { + @Override public void close () { + for (int ii = 0; ii < cons.length; ii++) { + if (cons[ii] == null) continue; + cons[ii].close(); + cons[ii] = null; } - }; - } + } + }; + } - /** Closes {@code con} and returns {@link #NOOP}. This enables code like: - * {@code con = Connection.close(con);} which simplifies disconnecting and resetting to - * {@link #NOOP}, a given connection reference. */ - public static Closeable close (Closeable con) { - con.close(); - return NOOP; - } + /** Closes {@code con} and returns {@link #NOOP}. This enables code like: + * {@code con = Connection.close(con);} which simplifies disconnecting and resetting to + * {@link #NOOP}, a given connection reference. */ + static Closeable close (Closeable con) { + con.close(); + return NOOP; } /** Closes this closeable resource. */ diff --git a/src/main/java/react/Connection.java b/src/main/java/react/Connection.java index 4618f65..436d932 100644 --- a/src/main/java/react/Connection.java +++ b/src/main/java/react/Connection.java @@ -42,9 +42,6 @@ public static Connection join (final Connection... conns) { */ public abstract void close (); - /** @deprecated Call {@link #close} instead. */ - @Deprecated public void disconnect () { close(); } - /** * Converts this connection into a one-shot connection. After the first time the slot or * listener is notified, it will automatically be disconnected. @@ -59,7 +56,7 @@ public static Connection join (final Connection... conns) { * Signal signal = ...; * Connection conn; * synchronized (signal) { - * conn = signal.connect(slot).once(); + * conn = signal.connect(v -> ...).once(); * } * } * @@ -75,7 +72,7 @@ public static Connection join (final Connection... conns) { * *
{@code
      * Signal signal = ...;
-     * Connection conn = signal.connect(new Slot() { ... }).atPrio(5);
+     * Connection conn = signal.connect(value -> ... ).atPrio(5);
      * }
* *

NOTE: if you are dispatching signals in a multithreaded environment, it is @@ -87,7 +84,7 @@ public static Connection join (final Connection... conns) { * Signal signal = ...; * Connection conn; * synchronized (signal) { - * conn = signal.connect(slot).atPrio(5); + * conn = signal.connect(v -> ...).atPrio(5); * } * } * diff --git a/src/main/java/react/Function.java b/src/main/java/react/Function.java deleted file mode 100644 index e1d709c..0000000 --- a/src/main/java/react/Function.java +++ /dev/null @@ -1,18 +0,0 @@ -// -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. -// http://github.com/threerings/react/blob/master/LICENSE - -package react; - -/** - * Models a single argument function. - */ -public interface Function -{ - /** - * Applies this function to the supplied input value. A function is generally expected to have - * no side effects; violate that assumption at your peril. - */ - T apply (F input); -} diff --git a/src/main/java/react/Functions.java b/src/main/java/react/Functions.java deleted file mode 100644 index 2e6ec9e..0000000 --- a/src/main/java/react/Functions.java +++ /dev/null @@ -1,161 +0,0 @@ -// -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. -// http://github.com/threerings/react/blob/master/LICENSE - -package react; - -import java.util.Map; - -/** - * Various {@link Function} related utility methods. - */ -public class Functions -{ - /** Implements boolean not. */ - public static Function NOT = new Function() { - public Boolean apply (Boolean value) { - return !value; - } - }; - - /** A function that applies {@link String#valueOf} to its argument. */ - public static Function TO_STRING = new Function() { - public String apply (Object value) { - return String.valueOf(value); - } - }; - - /** A function that returns true for null values and false for non-null values. */ - public static Function IS_NULL = new Function() { - public Boolean apply (Object value) { - return (value == null); - } - }; - - /** A function that returns true for non-null values and false for null values. */ - public static Function NON_NULL = new Function() { - public Boolean apply (Object value) { - return (value != null); - } - }; - - /** A function that returns the float value of a number. */ - public static Function FLOAT_VALUE = new Function() { - public Float apply (Number value) { - return value.floatValue(); - } - }; - - /** A function that returns the int value of a number. */ - public static Function INT_VALUE = new Function() { - public Integer apply (Number value) { - return value.intValue(); - } - }; - - /** - * Returns a function that always returns the supplied constant value. - */ - public static Function constant (final E constant) { - return new Function() { - public E apply (Object value) { - return constant; - } - }; - } - - /** - * Returns a function that computes whether a value is greater than {@code target}. - */ - public static Function greaterThan (final int target) { - return new Function() { - public Boolean apply (Integer value) { - return value > target; - } - }; - } - - /** - * Returns a function that computes whether a value is greater than or equal to {@code value}. - */ - public static Function greaterThanEqual (final int target) { - return new Function() { - public Boolean apply (Integer value) { - return value >= target; - } - }; - } - - /** - * Returns a function that computes whether a value is less than {@code target}. - */ - public static Function lessThan (final int target) { - return new Function() { - public Boolean apply (Integer value) { - return value < target; - } - }; - } - - /** - * Returns a function that computes whether a value is less than or equal to {@code target}. - */ - public static Function lessThanEqual (final int target) { - return new Function() { - public Boolean apply (Integer value) { - return value <= target; - } - }; - } - - /** - * Returns a function which performs a map lookup with a default value. The function created by - * this method returns defaultValue for all inputs that do not belong to the map's key set. - */ - public static Function forMap (final Map map, final V defaultValue) - { - return new Function() { - public V apply (K key) { - V value = map.get(key); - return (value != null || map.containsKey(key)) ? value : defaultValue; - } - }; - } - - /** - * Returns a function which returns its argument as a string with {@code prefix} prepended. - */ - public static Function prefix (final String prefix) { - return new Function() { - public String apply (T value) { - return prefix + value; - } - }; - } - - /** - * Returns a function which returns its argument as a string with {@code suffix} appended. - */ - public static Function suffix (final String suffix) { - return new Function() { - public String apply (T value) { - return value + suffix; - } - }; - } - - /** - * Returns the identity function for type {@code T}. - */ - public static Function identity () { - @SuppressWarnings("unchecked") Function ident = (Function)IDENT; - return ident; - } - - protected static final Function IDENT = new Function() { - public Object apply (Object value) { - return value; - } - }; -} diff --git a/src/main/java/react/RCollection.java b/src/main/java/react/RCollection.java index d1dffe9..5e205cc 100644 --- a/src/main/java/react/RCollection.java +++ b/src/main/java/react/RCollection.java @@ -29,14 +29,14 @@ public synchronized ValueView sizeView () { * Returns a reactive value which is true when this collection is empty, false otherwise. */ public ValueView isEmptyView () { - return sizeView().map(Functions.lessThanEqual(0)); + return sizeView().map(s -> s == 0); } /** * Returns a reactive value which is false when this collection is empty, true otherwise. */ public ValueView isNonEmptyView () { - return sizeView().map(Functions.greaterThan(0)); + return sizeView().map(s -> s > 0); } /** diff --git a/src/main/java/react/RFuture.java b/src/main/java/react/RFuture.java index 2420ba5..770dda7 100644 --- a/src/main/java/react/RFuture.java +++ b/src/main/java/react/RFuture.java @@ -12,6 +12,7 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; +import java.util.function.Function; /** * Represents an asynchronous result. Unlike standard Java futures, you cannot block on this @@ -82,7 +83,7 @@ public static RFuture failure (Throwable cause) { } /** Returns a future with an already-computed result. */ - public static RFuture result (final Try result) { + public static RFuture result (Try result) { return new RFuture() { public Try result () { return result; } }; @@ -98,8 +99,8 @@ public static RFuture> sequence (Collection> fu // if we're passed an empty list of futures, succeed immediately with an empty list if (futures.isEmpty()) return RFuture.success(Collections.emptyList()); - final RPromise> pseq = RPromise.create(); - final int count = futures.size(); + RPromise> pseq = RPromise.create(); + int count = futures.size(); class Sequencer { public synchronized void onResult (int idx, Try result) { if (result.isSuccess()) { @@ -120,10 +121,10 @@ public synchronized void onResult (int idx, Try result) { protected int _remain = count; protected MultiFailureException _error; } - final Sequencer seq = new Sequencer(); + Sequencer seq = new Sequencer(); Iterator> iter = futures.iterator(); for (int ii = 0; iter.hasNext(); ii++) { - final int idx = ii; + int idx = ii; iter.next().onComplete(new SignalView.Listener>() { public void onEmit (Try result) { seq.onResult(idx, result); } }); @@ -170,8 +171,8 @@ public static RFuture> collect (Collection>success(Collections.emptyList()); - final RPromise> pseq = RPromise.create(); - final int count = futures.size(); + RPromise> pseq = RPromise.create(); + int count = futures.size(); SignalView.Listener> collector = new SignalView.Listener>() { public synchronized void onEmit (Try result) { if (result.isSuccess()) _results.add(result.get()); @@ -187,7 +188,7 @@ public synchronized void onEmit (Try result) { /** Causes {@code slot} to be notified if/when this future is completed with success. If it has * already succeeded, the slot will be notified immediately. * @return this future for chaining. */ - public RFuture onSuccess (final SignalView.Listener slot) { + public RFuture onSuccess (SignalView.Listener slot) { return onComplete(new SignalView.Listener>() { public void onEmit (Try result) { if (result.isSuccess()) slot.onEmit(result.get()); @@ -198,7 +199,7 @@ public void onEmit (Try result) { /** Causes {@code slot} to be notified if/when this future is completed with failure. If it has * already failed, the slot will be notified immediately. * @return this future for chaining. */ - public RFuture onFailure (final SignalView.Listener slot) { + public RFuture onFailure (SignalView.Listener slot) { return onComplete(new SignalView.Listener>() { public void onEmit (Try result) { if (result.isFailure()) slot.onEmit(result.getFailure()); @@ -209,7 +210,7 @@ public void onEmit (Try result) { /** Causes {@code slot} to be notified when this future is completed. If it has already * completed, the slot will be notified immediately. * @return this future for chaining. */ - public RFuture onComplete (final SignalView.Listener> slot) { + public RFuture onComplete (SignalView.Listener> slot) { Try result = result(); if (result != null) slot.onEmit(result); else addConnection(slot); @@ -219,7 +220,7 @@ public RFuture onComplete (final SignalView.Listener> slot) { /** Returns a value that indicates whether this future has completed. */ public ValueView isComplete () { if (_isCompleteView == null) { - final Value isCompleteView = Value.create(false); + Value isCompleteView = Value.create(false); onComplete(new SignalView.Listener>() { public void onEmit (Try result) { isCompleteView.update(true); @@ -240,14 +241,14 @@ public boolean isCompleteNow () { * This is useful for binding the disabled state of UI elements to this future's completeness * (i.e. disabled while the future is incomplete, then reenabled when it is completed). * @return this future for chaining. */ - public RFuture bindComplete (SignalView.Listener slot) { + public RFuture bindComplete (ValueView.Listener slot) { isComplete().connectNotify(slot); return this; } /** Transforms this future by mapping its result upon arrival. */ - public RFuture transform (final Function,Try> func) { - final RPromise xf = RPromise.create(); + public RFuture transform (Function,Try> func) { + RPromise xf = RPromise.create(); onComplete(new SignalView.Listener>() { public void onEmit (Try result) { Try xfResult; @@ -264,7 +265,7 @@ public void onEmit (Try result) { } /** Maps the value of a successful result using {@code func} upon arrival. */ - public RFuture map (final Function func) { + public RFuture map (Function func) { Object sigh = Try.lift(func); @SuppressWarnings("unchecked") Function,Try> lifted = (Function,Try>)sigh; @@ -274,7 +275,7 @@ public RFuture map (final Function func) { /** Maps the value of a failed result using {@code func} upon arrival. Ideally one could * generalize the type {@code T} here but Java doesn't allow type parameters with lower * bounds. */ - public RFuture recover (final Function func) { + public RFuture recover (Function func) { Object sigh = new Function,Try>() { public Try apply (Try result) { return result.recover(func); @@ -288,8 +289,8 @@ public Try apply (Try result) { /** Maps a successful result to a new result using {@code func} when it arrives. Failure on the * original result or the mapped result are both dispatched to the mapped result. This is * useful for chaining asynchronous actions. It's also known as monadic bind. */ - public RFuture flatMap (final Function> func) { - final RPromise mapped = RPromise.create(); + public RFuture flatMap (Function> func) { + RPromise mapped = RPromise.create(); onComplete(new SignalView.Listener>() { public void onEmit (Try result) { if (result.isFailure()) mapped.fail(result.getFailure()); @@ -301,7 +302,7 @@ public void onEmit (Try result) { mapped.fail(t); return; } - mappedResult.onComplete(mapped.completer()); + mappedResult.onComplete(mapped::complete); } } }); @@ -321,8 +322,7 @@ public void onEmit (Try result) { public abstract Try result (); @Override RListener placeholderListener () { - /*@SuppressWarnings("unchecked")*/ RListener p = (RListener)Slots.NOOP; - return p; + return Slot.NOOP; } private ValueView _isCompleteView; diff --git a/src/main/java/react/RList.java b/src/main/java/react/RList.java index dd850db..d152bb4 100644 --- a/src/main/java/react/RList.java +++ b/src/main/java/react/RList.java @@ -16,43 +16,31 @@ * notification if the removed element is not present in the list. Use {@link #removeForce} to * force a notification. */ -public class RList extends RCollection implements List -{ +public class RList extends RCollection implements List { + /** Publishes list events to listeners. */ - public static abstract class Listener implements Reactor.RListener - { + public interface Listener extends Reactor.RListener { + /** Notifies listener of an added element. This method will call the index-forgetting * version ({@link #onAdd(Object)}) by default. */ - public void onAdd (int index, E elem) { - onAdd(elem); - } + default void onAdd (int index, E elem) { onAdd(elem); } /** Notifies listener of an added element. */ - public void onAdd (E elem) { - // noop - } + default void onAdd (E elem) {} // noop /** Notifies listener of an updated element. This method will call the old-value-forgetting * version ({@link #onSet(int,Object)}) by default. */ - public void onSet (int index, E newElem, E oldElem) { - onSet(index, newElem); - } + default void onSet (int index, E newElem, E oldElem) { onSet(index, newElem); } /** Notifies listener of an updated element. */ - public void onSet (int index, E newElem) { - // noop - } + default void onSet (int index, E newElem) {} // noop /** Notifies listener of a removed element. This method will call the index-forgetting * version ({@link #onRemove(Object)}) by default. */ - public void onRemove (int index, E elem) { - onRemove(elem); - } + default void onRemove (int index, E elem) { onRemove(elem); } /** Notifies listener of a removed element. */ - public void onRemove (E elem) { - // noop - } + default void onRemove (E elem) {} // noop } /** @@ -148,7 +136,7 @@ public boolean removeForce (E elem) { } @Override public ListIterator listIterator (int index) { - final ListIterator iiter = _impl.listIterator(); + ListIterator iiter = _impl.listIterator(); return new ListIterator () { public void add (E elem) { checkMutate(); diff --git a/src/main/java/react/RMap.java b/src/main/java/react/RMap.java index 30d99b0..6326b0e 100644 --- a/src/main/java/react/RMap.java +++ b/src/main/java/react/RMap.java @@ -24,33 +24,25 @@ public class RMap extends RCollection> implements Map { /** An interface for publishing map events to listeners. */ - public static abstract class Listener implements Reactor.RListener + public interface Listener extends Reactor.RListener { /** * Notifies listener of an added or updated mapping. This method will call the * old-value-forgetting version ({@link #onPut(Object,Object)}) by default. */ - public void onPut (K key, V value, V oldValue) { - onPut(key, value); - } + default void onPut (K key, V value, V oldValue) { onPut(key, value); } /** Notifies listener of an added or updated mapping. */ - public void onPut (K key, V value) { - // noop - } + default void onPut (K key, V value) {} // noop /** * Notifies listener of a removed mapping. This method will call the old-value-forgetting * version ({@link #onRemove(Object)}) by default. */ - public void onRemove (K key, V oldValue) { - onRemove(key); - } + default void onRemove (K key, V oldValue) { onRemove(key); } /** Notifies listener of a removed mapping. */ - public void onRemove (K key) { - // noop - } + default void onRemove (K key) {} // noop } /** @@ -141,7 +133,7 @@ public V removeForce (K key) { * this view only works on maps that do not contain mappings to {@code null}. The view * will retain a connection to this map for as long as it has connections of its own. */ - public ValueView containsKeyView (final K key) { + public ValueView containsKeyView (K key) { if (key == null) throw new NullPointerException("Must supply non-null 'key'."); return new MappedValue() { @Override public Boolean get () { @@ -165,7 +157,7 @@ public ValueView containsKeyView (final K key) { * report a change when the mapping for the specified key is changed or removed. The view will * retain a connection to this map for as long as it has connections of its own. */ - public ValueView getView (final K key) { + public ValueView getView (K key) { if (key == null) throw new NullPointerException("Must supply non-null 'key'."); return new MappedValue() { @Override public V get () { @@ -266,7 +258,7 @@ public void clear () { // from interface Map public Set keySet () { - final Set iset = _impl.keySet(); + Set iset = _impl.keySet(); return new AbstractSet() { public Iterator iterator () { final Iterator iiter = iset.iterator(); @@ -309,7 +301,7 @@ public void clear () { // from interface Map public Collection values () { - final Collection> iset = _impl.entrySet(); + Collection> iset = _impl.entrySet(); return new AbstractCollection() { public Iterator iterator () { final Iterator> iiter = iset.iterator(); @@ -343,7 +335,7 @@ public void clear () { // from interface Map public Set> entrySet () { - final Set> iset = _impl.entrySet(); + Set> iset = _impl.entrySet(); return new AbstractSet>() { public Iterator> iterator () { final Iterator> iiter = iset.iterator(); diff --git a/src/main/java/react/RPromise.java b/src/main/java/react/RPromise.java index 6141eb5..b8de4e8 100644 --- a/src/main/java/react/RPromise.java +++ b/src/main/java/react/RPromise.java @@ -43,33 +43,6 @@ public void fail (Throwable cause) { complete(Try.failure(cause)); } - /** Returns a slot that can be used to complete this promise. */ - public Slot> completer () { - return new Slot>() { - public void onEmit (Try result) { - complete(result); - } - }; - } - - /** Returns a slot that can be used to {@link #succeed} this promise. */ - public Slot succeeder () { - return new Slot() { - public void onEmit (T result) { - succeed(result); - } - }; - } - - /** Returns a slot that can be used to {@link #fail} this promise. */ - public Slot failer () { - return new Slot() { - public void onEmit (Throwable cause) { - fail(cause); - } - }; - } - @Override public Try result () { return _result; } diff --git a/src/main/java/react/RQueue.java b/src/main/java/react/RQueue.java index 035f71a..6767883 100644 --- a/src/main/java/react/RQueue.java +++ b/src/main/java/react/RQueue.java @@ -18,13 +18,13 @@ public class RQueue extends RCollection implements Queue { /** Publishes queue events to listeners. */ - public static abstract class Listener implements Reactor.RListener + public interface Listener extends Reactor.RListener { /** Notifies listener of an offered (added) element. */ - public void onOffer (E elem) {} // noop + default void onOffer (E elem) {} // noop /** Notifies listener of a polled (removed) element. */ - public void onPoll (E elem) {} // noop + default void onPoll (E elem) {} // noop } /** diff --git a/src/main/java/react/RSet.java b/src/main/java/react/RSet.java index 6cae645..179ca18 100644 --- a/src/main/java/react/RSet.java +++ b/src/main/java/react/RSet.java @@ -22,17 +22,13 @@ public class RSet extends RCollection implements Set { /** An interface for publishing set events to listeners. */ - public static abstract class Listener implements Reactor.RListener + public interface Listener extends Reactor.RListener { /** Notifies listener of an added element. */ - public void onAdd (E elem) { - // noop - } + default void onAdd (E elem) {} // noop /** Notifies listener of a removed element. */ - public void onRemove (E elem) { - // noop - } + default void onRemove (E elem) {} // noop } /** @@ -110,7 +106,7 @@ public boolean removeForce (E elem) { * #addForce} or {@link #removeForce} will cause this view to trigger and incorrectly report * that the element was not or was previously contained in the set. Caveat user. */ - public ValueView containsView (final E elem) { + public ValueView containsView (E elem) { if (elem == null) throw new NullPointerException("Must supply non-null 'elem'."); return new MappedValue() { @Override public Boolean get () { @@ -208,7 +204,7 @@ public void clear () { // from interface Set public Iterator iterator () { - final Iterator iiter = _impl.iterator(); + Iterator iiter = _impl.iterator(); return new Iterator() { public boolean hasNext () { return iiter.hasNext(); diff --git a/src/main/java/react/Signal.java b/src/main/java/react/Signal.java index b9fffb4..cf2e9f5 100644 --- a/src/main/java/react/Signal.java +++ b/src/main/java/react/Signal.java @@ -11,6 +11,21 @@ */ public class Signal extends AbstractSignal { + /** + * A signal that emits an event with no associated data. It can be used like so: + */ + public static class Unit extends Signal { + /** Connects a zero-argument listener to this signal. */ + public Connection connect (Runnable slot) { + return connect(v -> slot.run()); + } + + /** Causes this signal to emit an event to its connected slots. */ + public void emit () { + notifyEmit(null); + } + } + /** * Convenience method for creating a signal without repeating the type parameter. */ @@ -19,21 +34,16 @@ public static Signal create () { } /** - * Causes this signal to emit the supplied event to connected slots. + * Convenience method for creating a {@link Unit} signal. */ - public void emit (T event) { - notifyEmit(event); + public static Signal.Unit createUnit () { + return new Signal.Unit(); } /** - * Returns a slot which can be used to wire this signal to the emissions of a {@link Signal} or - * another value. + * Causes this signal to emit the supplied event to connected slots. */ - public Slot slot () { - return new Slot () { - @Override public void onEmit (T value) { - emit(value); - } - }; + public void emit (T event) { + notifyEmit(event); } } diff --git a/src/main/java/react/SignalView.java b/src/main/java/react/SignalView.java index 027ddc5..21e9c6e 100644 --- a/src/main/java/react/SignalView.java +++ b/src/main/java/react/SignalView.java @@ -5,6 +5,8 @@ package react; +import java.util.function.Function; + /** * A view of a {@link Signal}, on which slots may listen, but to which one cannot emit events. This * is generally used to provide signal-like views of changing entities. See {@link AbstractValue} @@ -20,11 +22,6 @@ interface Listener extends Reactor.RListener { * @param event the event emitted by the signal. */ void onEmit (T event); - - // TODO: when we stop supporting Java 1.7, uncomment this method and make Listener extend - // ValueView.Listener - // - // default void onChange (T newValue, T oldValue) { onEmit(newValue); } } /** diff --git a/src/main/java/react/Slot.java b/src/main/java/react/Slot.java index 9deff9e..ad81c15 100644 --- a/src/main/java/react/Slot.java +++ b/src/main/java/react/Slot.java @@ -5,56 +5,57 @@ package react; +import java.util.function.Function; + /** - * Reacts to signal emissions. + * A synonym for {@link SignalView.Listener} and {@link ValueView.Listener} (ignoring the old + * value) name. Also provides some filtering and composition methods. */ -public abstract class Slot implements ValueView.Listener, SignalView.Listener -{ +public interface Slot extends SignalView.Listener, ValueView.Listener { + + /** A slot that does nothing. Useful when you don't want to fiddle with null checks. */ + public static Slot NOOP = v -> {}; // noop! + + /** + * Returns a slot that logs the supplied message (via {@link System#err}) with the emitted + * value appended to it before passing the emitted value on to {@code slot}. Useful for + * debugging. + */ + public static Slot trace (String message, Slot slot) { + return value -> { + System.err.println(message + value); + slot.onEmit(value); + }; + } + /** * Returns a slot that maps values via {@code f} and then passes them to this slot. * This is essentially function composition in that {@code slot.compose(f)} means * {@code slot(f(value)))} where this slot is treated as a side effecting void function. */ - public Slot compose (final Function f) { - final Slot outer = this; - return new Slot() { - public void onEmit (S value) { - outer.onEmit(f.apply(value)); - } - }; + default Slot compose (Function f) { + return value -> this.onEmit(f.apply(value)); } /** * Returns a slot that is only notified when the signal to which this slot is connected emits a * value which causes {@code pred} to return true. */ - public Slot filtered (final Function pred) { - final Slot outer = this; - return new Slot() { - public void onEmit (S value) { - if (pred.apply(value)) outer.onEmit(value); - } + default Slot filtered (Function pred) { + return value -> { + if (pred.apply(value)) this.onEmit(value); }; } /** * Returns a new slot that invokes this slot and then evokes {@code after}. */ - public Slot andThen (final Slot after) { - final Slot before = this; - return new Slot() { - public void onEmit (S event) { - before.onEmit(event); - after.onEmit(event); - } + default Slot andThen (Slot after) { + return value -> { + this.onEmit(value); + after.onEmit(value); }; } - /** - * Allows a slot to be used as a {@link ValueView.Listener} by passing just the new value - * through to {@link #onEmit}. - */ - @Override public final void onChange (T value, T oldValue) { - onEmit(value); - } + default void onChange (T newValue, T oldValue) { onEmit(newValue); } } diff --git a/src/main/java/react/Slots.java b/src/main/java/react/Slots.java deleted file mode 100644 index f18eeab..0000000 --- a/src/main/java/react/Slots.java +++ /dev/null @@ -1,33 +0,0 @@ -// -// React - a library for functional-reactive-like programming -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. -// http://github.com/threerings/react/blob/master/LICENSE - -package react; - -/** - * Provides utility methods for {@link Slot}s. - */ -public class Slots -{ - /** A slot that does nothing. Useful when you don't want to fiddle with null checks. */ - public static UnitSlot NOOP = new UnitSlot() { - public void onEmit () {} // noop! - }; - - /** - * Returns a slot that logs the supplied message (via {@link System#err}) with the emitted - * value appended to it before passing the emitted value on to {@code slot}. Useful for - * debugging. - */ - public static Slot trace (final String message, final Slot slot) { - return new Slot() { - public void onEmit (T value) { - System.err.println(message + value); - slot.onEmit(value); - } - }; - } - - private Slots () {} // no constructski -} diff --git a/src/main/java/react/Try.java b/src/main/java/react/Try.java index 5201f7e..ca263c2 100644 --- a/src/main/java/react/Try.java +++ b/src/main/java/react/Try.java @@ -5,6 +5,8 @@ package react; +import java.util.function.Function; + /** * Represents a computation that either provided a result, or failed with an exception. Monadic * methods are provided that allow one to map and compose tries in ways that propagate failure. diff --git a/src/main/java/react/UnitSignal.java b/src/main/java/react/UnitSignal.java deleted file mode 100644 index cd5b7ad..0000000 --- a/src/main/java/react/UnitSignal.java +++ /dev/null @@ -1,38 +0,0 @@ -// -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. -// http://github.com/threerings/react/blob/master/LICENSE - -package react; - -/** - * A signal that emits an event with no associated data. It can be used with {@code UnitSlot} - * like so: - *
{@code
- * UnitSignal signal = new UnitSignal();
- * signal.connect(new UnitSlot() {
- *   public void onEmit () {
- *     // ...
- *   }
- * });
- * }
- */ -public class UnitSignal extends AbstractSignal -{ - /** - * Causes this signal to emit an event to its connected slots. - */ - public void emit () { - notifyEmit(null); - } - - /** - * Returns a slot which can be used to wire this signal to the emissions of a {@link Signal} or - * another value. - */ - public UnitSlot slot () { - return new UnitSlot () { - @Override public void onEmit () { emit(); } - }; - } -} diff --git a/src/main/java/react/UnitSlot.java b/src/main/java/react/UnitSlot.java deleted file mode 100644 index f43dedf..0000000 --- a/src/main/java/react/UnitSlot.java +++ /dev/null @@ -1,52 +0,0 @@ -// -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. -// http://github.com/threerings/react/blob/master/LICENSE - -package react; - -/** - * A {@link Slot} for use when the type of emitted signal is ignored. As slots are contravariant, - * this slot may be wired to any signal without bothering to determine its type. Instances of this - * class also implement {@link Runnable} so that they may be invoked by code that expects runnables - * rather than no-arg slots. - */ -public abstract class UnitSlot extends Slot implements Runnable -{ - /** - * Wraps the supplied runnable in a {@link UnitSlot}. - */ - public static UnitSlot toSlot (final Runnable runnable) { - return new UnitSlot() { public void onEmit () { - runnable.run(); - }}; - } - - /** - * Called when a signal to which this slot is connected has emitted an event. - */ - public abstract void onEmit (); - - /** - * Returns a new slot that invokes this slot and then evokes {@code after}. - */ - public UnitSlot andThen (final UnitSlot after) { - final UnitSlot before = this; - return new UnitSlot() { - public void onEmit () { - before.onEmit(); - after.onEmit(); - } - }; - } - - @Override // if you're using unit slot, you're not allow to see the event - public final void onEmit (Object event) { - onEmit(); - } - - @Override // from Runnable - public void run () { - onEmit(); - } -} diff --git a/src/main/java/react/Value.java b/src/main/java/react/Value.java index bc26bd6..873a0e0 100644 --- a/src/main/java/react/Value.java +++ b/src/main/java/react/Value.java @@ -43,18 +43,6 @@ public T updateForce (T value) { return updateAndNotify(value); } - /** - * Returns a slot which can be used to wire this value to the emissions of a {@link Signal} or - * another value. - */ - public Slot slot () { - return new Slot () { - @Override public void onEmit (T value) { - update(value); - } - }; - } - @Override public T get () { return _value; } diff --git a/src/main/java/react/ValueView.java b/src/main/java/react/ValueView.java index 997910c..2fb1357 100644 --- a/src/main/java/react/ValueView.java +++ b/src/main/java/react/ValueView.java @@ -5,6 +5,8 @@ package react; +import java.util.function.Function; + /** * A view of a {@link Value}, to which listeners may be added, but which one cannot update. This * can be used in combination with {@link AbstractValue} to provide {@link Value} semantics to an @@ -77,29 +79,8 @@ interface Listener extends Reactor.RListener { */ void disconnect (Listener listener); - // these methods exist only to let javac know that it can synthesize a SignalView.Listener - // instance from a single argument lambda; otherwise they are unnecessary because - // SignalView.Listener is a subtype of ValueView.Listener - - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. The listener is held by a strong reference, so it's held in memory by virtue of - * being connected. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connect (SignalView.Listener listener); - - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. Also immediately notifies the listener of the current value. If the notification - * triggers an unchecked exception, the slot will automatically be disconnected and the caller - * need not worry about cleaning up after itself. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connectNotify (SignalView.Listener listener); - - // these methods exist to help javac disambiguate between the above two methods, yay - // TODO: when we drop support for java 1.7, we can remove these methods + // these methods exist to let javac know that it can synthesize a Slot when a single argument + // lambda is passed to connect/connectNotify /** * Connects the supplied listener to this value, such that it will be notified when this value diff --git a/src/main/java/react/Values.java b/src/main/java/react/Values.java index c2dc0af..d4c4bcb 100644 --- a/src/main/java/react/Values.java +++ b/src/main/java/react/Values.java @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.Iterator; import java.util.Objects; +import java.util.function.Function; /** * Provides utility methods for {@link Value}s. @@ -65,7 +66,7 @@ public T3 (A a, B b, C c) { * Returns a reactive value which is triggered when either of {@code a, b} emits an event. The * mapped value will retain connections to {@code a+b} only while it itself has connections. */ - public static ValueView> join (final ValueView a, final ValueView b) { + public static ValueView> join (ValueView a, ValueView b) { return new MappedValue>() { @Override public T2 get () { return _current; @@ -73,14 +74,12 @@ public static ValueView> join (final ValueView a, final ValueVi @Override protected Connection connect () { return Connection.join(a.connect(_trigger), b.connect(_trigger)); } - protected final UnitSlot _trigger = new UnitSlot() { - public void onEmit () { - T2 ovalue = _current; - _current = new T2(a.get(), b.get()); - notifyChange(_current, ovalue); - } - }; protected T2 _current = new T2(a.get(), b.get()); + protected final Slot _trigger = value -> { + T2 ovalue = _current; + _current = new T2(a.get(), b.get()); + notifyChange(_current, ovalue); + }; }; } @@ -89,24 +88,23 @@ public void onEmit () { * The mapped value will retain connections to {@code a+b+c} only while it itself has * connections. */ - public static ValueView> join (final ValueView a, final ValueView b, - final ValueView c) { + public static ValueView> join ( + ValueView a, ValueView b, ValueView c) { + return new MappedValue>() { @Override public T3 get () { return _current; } @Override protected Connection connect () { - return Connection.join( - a.connect(_trigger), b.connect(_trigger), c.connect(_trigger)); + return Connection.join(a.connect(_trigger), b.connect(_trigger), + c.connect(_trigger)); } - protected final UnitSlot _trigger = new UnitSlot() { - public void onEmit () { - T3 ovalue = _current; - _current = new T3(a.get(), b.get(), c.get()); - notifyChange(_current, ovalue); - } + protected T3 _current = new T3<>(a.get(), b.get(), c.get()); + protected final Slot _trigger = value -> { + T3 ovalue = _current; + _current = new T3(a.get(), b.get(), c.get()); + notifyChange(_current, ovalue); }; - protected T3 _current = new T3(a.get(), b.get(), c.get()); }; } @@ -116,30 +114,21 @@ public void onEmit () { * @param signal the signal that will trigger the toggling. * @param initial the initial value of the to be toggled value. */ - public static ValueView toggler (final SignalView signal, final boolean initial) { + public static ValueView toggler (SignalView signal, boolean initial) { return new MappedValue() { @Override public Boolean get () { return _current; } @Override protected Connection connect () { - return signal.connect(new UnitSlot() { - @Override public void onEmit () { - boolean old = _current; - notifyChange(_current = !old, old); - } + return signal.connect(value -> { + boolean old = _current; + notifyChange(_current = !old, old); }); } protected boolean _current = initial; }; } - /** - * Returns a value which is the logical NOT of the supplied value. - */ - public static ValueView not (ValueView value) { - return value.map(Functions.NOT); - } - /** * Returns a value which is the logical AND of the supplied values. */ @@ -158,8 +147,8 @@ public static ValueView and (ValueView... values) { /** * Returns a value which is the logical AND of the supplied values. */ - public static ValueView and (final Collection> values) { - return aggValue(values, COMPUTE_AND); + public static ValueView and (Collection> values) { + return aggValue(values, Values::computeAnd); } /** @@ -180,15 +169,15 @@ public static ValueView or (ValueView... values) { /** * Returns a value which is the logical OR of the supplied values. */ - public static ValueView or (final Collection> values) { - return aggValue(values, COMPUTE_OR); + public static ValueView or (Collection> values) { + return aggValue(values, Values::computeOr); } /** * Returns a view of the supplied signal as a value. It will contain the value {@code initial} * until the signal fires, at which time the value will be updated with the emitted value. */ - public static ValueView asValue (final SignalView signal, final T initial) { + public static ValueView asValue (SignalView signal, T initial) { return new MappedValue() { @Override public T get () { return _value; @@ -199,19 +188,15 @@ public static ValueView asValue (final SignalView signal, final T init return ovalue; } @Override protected Connection connect () { - return signal.connect(new Slot() { - public void onEmit (T value) { - updateAndNotifyIf(value); - } - }); + return signal.connect(value -> updateAndNotifyIf(value)); } protected T _value = initial; }; } protected static final ValueView aggValue ( - final Collection> values, - final Function>,Boolean> aggOp) { + Collection> values, + Function>,Boolean> aggOp) { return new MappedValue() { @Override public Boolean get () { @@ -225,36 +210,28 @@ protected static final ValueView aggValue ( return Connection.join(conns); } - protected final UnitSlot _trigger = new UnitSlot() { - public void onEmit () { - boolean ovalue = _current; - _current = aggOp.apply(values); - notifyChange(_current, ovalue); - } - protected boolean _current = aggOp.apply(values); + protected boolean _current = aggOp.apply(values); + protected final Slot _trigger = value -> { + boolean ovalue = _current; + _current = aggOp.apply(values); + notifyChange(_current, ovalue); }; }; } - protected static final Function>,Boolean> COMPUTE_AND = - new Function>,Boolean>() { - public Boolean apply (Iterable> values) { - for (ValueView value : values) { - if (!value.get()) return false; - } - return true; - } - }; + protected static boolean computeAnd (Iterable> values) { + for (ValueView value : values) { + if (!value.get()) return false; + } + return true; + } - protected static final Function>,Boolean> COMPUTE_OR = - new Function>,Boolean>() { - public Boolean apply (Iterable> values) { - for (ValueView value : values) { - if (value.get()) return true; - } - return false; - } - }; + protected static boolean computeOr (Iterable> values) { + for (ValueView value : values) { + if (value.get()) return true; + } + return false; + } private Values () {} // no constructski } diff --git a/src/test/java/react/RFutureTest.java b/src/test/java/react/RFutureTest.java index 3b8e7a1..09c2a24 100644 --- a/src/test/java/react/RFutureTest.java +++ b/src/test/java/react/RFutureTest.java @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.Collections; import java.util.ArrayList; +import java.util.function.Function; import org.junit.*; import static org.junit.Assert.*; @@ -75,11 +76,11 @@ public void reset () { FutureCounter counter = new FutureCounter(); RFuture success = RFuture.success("Yay!"); - counter.bind(success.map(Functions.NON_NULL)); + counter.bind(success.map(v -> v != null)); counter.check("immediate succeed", 1, 0, 1); RFuture failure = RFuture.failure(new Exception("Boo!")); - counter.bind(failure.map(Functions.NON_NULL)); + counter.bind(failure.map(v -> v != null)); counter.check("immediate failure", 0, 1, 1); } @@ -87,13 +88,13 @@ public void reset () { FutureCounter counter = new FutureCounter(); RPromise success = RPromise.create(); - counter.bind(success.map(Functions.NON_NULL)); + counter.bind(success.map(v -> v != null)); counter.check("before succeed", 0, 0, 0); success.succeed("Yay!"); counter.check("after succeed", 1, 0, 1); RPromise failure = RPromise.create(); - counter.bind(failure.map(Functions.NON_NULL)); + counter.bind(failure.map(v -> v != null)); counter.check("before fail", 0, 0, 0); failure.fail(new Exception("Boo!")); counter.check("after fail", 0, 1, 1); @@ -106,21 +107,12 @@ public void reset () { FutureCounter scounter = new FutureCounter(); FutureCounter fcounter = new FutureCounter(); FutureCounter ccounter = new FutureCounter(); - Function> successMap = new Function>() { - public RFuture apply (String value) { - return RFuture.success(value != null); - } - }; - Function> failMap = new Function>() { - public RFuture apply (String value) { - return RFuture.failure(new Exception("Barzle!")); - } - }; - Function> crashMap = new Function>() { - public RFuture apply (String value) { - throw new RuntimeException("Barzle!"); - } - }; + Function> successMap = + value -> RFuture.success(value != null); + Function> failMap = + value -> RFuture.failure(new Exception("Barzle!")); + Function> crashMap = + value -> { throw new RuntimeException("Barzle!"); }; RFuture success = RFuture.success("Yay!"); scounter.bind(success.flatMap(successMap)); @@ -142,16 +134,8 @@ public RFuture apply (String value) { @Test public void testFlatMappedDeferred () { FutureCounter scounter = new FutureCounter(); FutureCounter fcounter = new FutureCounter(); - Function> successMap = new Function>() { - public RFuture apply (String value) { - return RFuture.success(value != null); - } - }; - Function> failMap = new Function>() { - public RFuture apply (String value) { - return RFuture.failure(new Exception("Barzle!")); - } - }; + Function> successMap = v -> RFuture.success(v != null); + Function> failMap = v -> RFuture.failure(new Exception("Barzle!")); RPromise success = RPromise.create(); scounter.bind(success.flatMap(successMap)); @@ -181,18 +165,10 @@ public RFuture apply (String value) { { RPromise success = RPromise.create(); final RPromise innerSuccessSuccess = RPromise.create(); - scounter.bind(success.flatMap(new Function>() { - public RFuture apply (String value) { - return innerSuccessSuccess; - } - })); + scounter.bind(success.flatMap(value -> innerSuccessSuccess)); scounter.check("before succeed/succeed", 0, 0, 0); final RPromise innerSuccessFailure = RPromise.create(); - fcounter.bind(success.flatMap(new Function>() { - public RFuture apply (String value) { - return innerSuccessFailure; - } - })); + fcounter.bind(success.flatMap(value -> innerSuccessFailure)); fcounter.check("before succeed/fail", 0, 0, 0); success.succeed("Yay!"); @@ -210,18 +186,10 @@ public RFuture apply (String value) { { RPromise failure = RPromise.create(); final RPromise innerFailureSuccess = RPromise.create(); - scounter.bind(failure.flatMap(new Function>() { - public RFuture apply (String value) { - return innerFailureSuccess; - } - })); + scounter.bind(failure.flatMap(value -> innerFailureSuccess)); scounter.check("before fail/succeed", 0, 0, 0); final RPromise innerFailureFailure = RPromise.create(); - fcounter.bind(failure.flatMap(new Function>() { - public RFuture apply (String value) { - return innerFailureFailure; - } - })); + fcounter.bind(failure.flatMap(value -> innerFailureFailure)); fcounter.check("before fail/fail", 0, 0, 0); failure.fail(new Exception("Boo!")); @@ -249,11 +217,7 @@ public RFuture apply (String value) { RFuture> sucseq = RFuture.sequence(list(success1, success2)); counter.bind(sucseq); - sucseq.onSuccess(new Slot>() { - public void onEmit (List results) { - assertEquals(list("Yay 1!", "Yay 2!"), results); - } - }); + sucseq.onSuccess(results -> assertEquals(list("Yay 1!", "Yay 2!"), results)); counter.check("immediate seq success/success", 1, 0, 1); counter.bind(RFuture.sequence(list(success1, failure1))); @@ -274,22 +238,16 @@ public void onEmit (List results) { RFuture> suc2seq = RFuture.sequence(list(success1, success2)); counter.bind(suc2seq); - suc2seq.onSuccess(new Slot>() { - public void onEmit (List results) { - assertEquals(list("Yay 1!", "Yay 2!"), results); - } - }); + suc2seq.onSuccess(results -> assertEquals(list("Yay 1!", "Yay 2!"), results)); counter.check("before seq succeed/succeed", 0, 0, 0); success1.succeed("Yay 1!"); success2.succeed("Yay 2!"); counter.check("after seq succeed/succeed", 1, 0, 1); RFuture> sucfailseq = RFuture.sequence(list(success1, failure1)); - sucfailseq.onFailure(new Slot() { - public void onEmit (Throwable cause) { - assertTrue(cause instanceof MultiFailureException); - assertEquals("1 failures: java.lang.Exception: Boo 1!", cause.getMessage()); - } + sucfailseq.onFailure(cause -> { + assertTrue(cause instanceof MultiFailureException); + assertEquals("1 failures: java.lang.Exception: Boo 1!", cause.getMessage()); }); counter.bind(sucfailseq); counter.check("before seq succeed/fail", 0, 0, 0); @@ -297,22 +255,18 @@ public void onEmit (Throwable cause) { counter.check("after seq succeed/fail", 0, 1, 1); RFuture> failsucseq = RFuture.sequence(list(failure1, success2)); - failsucseq.onFailure(new Slot() { - public void onEmit (Throwable cause) { - assertTrue(cause instanceof MultiFailureException); - assertEquals("1 failures: java.lang.Exception: Boo 1!", cause.getMessage()); - } + failsucseq.onFailure(cause -> { + assertTrue(cause instanceof MultiFailureException); + assertEquals("1 failures: java.lang.Exception: Boo 1!", cause.getMessage()); }); counter.bind(failsucseq); counter.check("after seq fail/succeed", 0, 1, 1); RFuture> fail2seq = RFuture.sequence(list(failure1, failure2)); - fail2seq.onFailure(new Slot() { - public void onEmit (Throwable cause) { - assertTrue(cause instanceof MultiFailureException); - assertEquals("2 failures: java.lang.Exception: Boo 1!, java.lang.Exception: Boo 2!", - cause.getMessage()); - } + fail2seq.onFailure(cause -> { + assertTrue(cause instanceof MultiFailureException); + assertEquals("2 failures: java.lang.Exception: Boo 1!, java.lang.Exception: Boo 2!", + cause.getMessage()); }); counter.bind(fail2seq); counter.check("before seq fail/fail", 0, 0, 0); @@ -333,11 +287,9 @@ public void onEmit (Throwable cause) { RFuture integer = RFuture.success(42); RFuture> sucsuc = RFuture.sequence(string, integer); - sucsuc.onSuccess(new Slot>() { - public void onEmit (RFuture.T2 tup) { - assertEquals("string", tup.a); - assertEquals((Integer)42, tup.b); - } + sucsuc.onSuccess(tup -> { + assertEquals("string", tup.a); + assertEquals((Integer)42, tup.b); }); counter.bind(sucsuc); counter.check("tuple2 seq success/success", 1, 0, 1); @@ -354,14 +306,13 @@ public void onEmit (RFuture.T2 tup) { @Test public void testCollectEmpty () { FutureCounter counter = new FutureCounter(); - RFuture> seq = RFuture.collect(Collections.>emptyList()); + RFuture> seq = RFuture.collect(Collections.emptyList()); counter.bind(seq); counter.check("collect empty list succeeds", 1, 0, 1); } - // fucking Java generics and arrays... blah protected List list (T one, T two) { - List list = new ArrayList(); + List list = new ArrayList<>(); list.add(one); list.add(two); return list; diff --git a/src/test/java/react/RListTest.java b/src/test/java/react/RListTest.java index acd316f..4c1bd80 100644 --- a/src/test/java/react/RListTest.java +++ b/src/test/java/react/RListTest.java @@ -14,7 +14,7 @@ public class RListTest { - public static class Counter extends RList.Listener { + public static class Counter implements RList.Listener { public int notifies; @Override public void onAdd (int index, Object elem) { notifies++; diff --git a/src/test/java/react/RMapTest.java b/src/test/java/react/RMapTest.java index be0a8b1..0a05d9e 100644 --- a/src/test/java/react/RMapTest.java +++ b/src/test/java/react/RMapTest.java @@ -19,7 +19,7 @@ */ public class RMapTest { - public static class Counter extends RMap.Listener { + public static class Counter implements RMap.Listener { public int notifies; @Override public void onPut (Object key, Object value) { notifies++; @@ -30,12 +30,12 @@ public static class Counter extends RMap.Listener { } @Test public void testBasicNotify () { - RMap map = RMap.create(new HashMap()); + RMap map = RMap.create(new HashMap<>()); Counter counter = new Counter(); map.connect(counter); // add a mapping, ensure that we're notified - map.connect(new RMap.Listener() { + map.connect(new RMap.Listener() { public void onPut (Integer key, String value, String ovalue) { assertEquals(42, key.intValue()); assertEquals("LTUAE", value); diff --git a/src/test/java/react/RQueueTest.java b/src/test/java/react/RQueueTest.java index 8179f57..afaa4d1 100644 --- a/src/test/java/react/RQueueTest.java +++ b/src/test/java/react/RQueueTest.java @@ -12,7 +12,7 @@ public class RQueueTest { - public static class Counter extends RQueue.Listener { + public static class Counter implements RQueue.Listener { public int notifies; @Override public void onOffer (Object elem) { notifies++; } @Override public void onPoll (Object elem) { notifies++; } diff --git a/src/test/java/react/RSetTest.java b/src/test/java/react/RSetTest.java index 59d5fa8..4ae0f5a 100644 --- a/src/test/java/react/RSetTest.java +++ b/src/test/java/react/RSetTest.java @@ -17,7 +17,7 @@ */ public class RSetTest { - public static class Counter extends RSet.Listener { + public static class Counter implements RSet.Listener { public int notifies; @Override public void onAdd (Object elem) { notifies++; diff --git a/src/test/java/react/SignalTest.java b/src/test/java/react/SignalTest.java index 925afc3..9270dd4 100644 --- a/src/test/java/react/SignalTest.java +++ b/src/test/java/react/SignalTest.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Function; import org.junit.*; import static org.junit.Assert.*; @@ -18,47 +19,43 @@ */ public class SignalTest { - public static class Counter extends UnitSlot { + public static class Counter implements Slot { public int notifies; - @Override public void onEmit () { + @Override public void onEmit (Object value) { notifies++; } } - public static Slot require (final T reqValue) { - return new Slot() { - public void onEmit (T value) { - assertEquals(reqValue, value); - } - }; + public static Slot require (T reqValue) { + return value -> assertEquals(reqValue, value); } @Test public void testSignalToSlot () { Signal signal = Signal.create(); - AccSlot slot = new AccSlot(); + Accum slot = new Accum<>(); signal.connect(slot); signal.emit(1); signal.emit(2); signal.emit(3); - assertEquals(Arrays.asList(1, 2, 3), slot.events); + assertEquals(Arrays.asList(1, 2, 3), slot.values); } @Test public void testOneShotSlot () { Signal signal = Signal.create(); - AccSlot slot = new AccSlot(); + Accum slot = new Accum<>(); signal.connect(slot).once(); signal.emit(1); // slot should be removed after this emit signal.emit(2); signal.emit(3); - assertEquals(Arrays.asList(1), slot.events); + assertEquals(Arrays.asList(1), slot.values); } @Test public void testSlotPriority () { final int[] counter = new int[] { 0 }; - class TestSlot extends UnitSlot { + class TestSlot implements SignalView.Listener { public int order; - public void onEmit () { + public void onEmit (Object value) { order = ++counter[0]; } } @@ -67,7 +64,7 @@ public void onEmit () { TestSlot slot3 = new TestSlot(); TestSlot slot4 = new TestSlot(); - UnitSignal signal = new UnitSignal(); + Signal.Unit signal = new Signal.Unit(); signal.connect(slot3).atPrio(2); signal.connect(slot1).atPrio(4); signal.connect(slot2).atPrio(3); @@ -80,136 +77,102 @@ public void onEmit () { } @Test public void testAddDuringDispatch () { - final Signal signal = Signal.create(); - final AccSlot toAdd = new AccSlot(); - signal.connect(new UnitSlot() { - public void onEmit () { - signal.connect(toAdd); - } - }).once(); + Signal signal = Signal.create(); + Accum toAdd = new Accum<>(); + signal.connect(v -> signal.connect(toAdd)).once(); // this will connect our new signal but not dispatch to it signal.emit(5); - assertEquals(0, toAdd.events.size()); + assertEquals(0, toAdd.values.size()); // now dispatch an event that should go to the added signal signal.emit(42); - assertEquals(Arrays.asList(42), toAdd.events); + assertEquals(Arrays.asList(42), toAdd.values); } @Test public void testRemoveDuringDispatch () { - final Signal signal = Signal.create(); - final AccSlot toRemove = new AccSlot(); - final Connection rconn = signal.connect(toRemove); + Signal signal = Signal.create(); + Accum toRemove = new Accum<>(); + Connection rconn = signal.connect(toRemove); // dispatch one event and make sure it's received signal.emit(5); - assertEquals(Arrays.asList(5), toRemove.events); + assertEquals(Arrays.asList(5), toRemove.values); // now add our removing signal, and dispatch again - signal.connect(new UnitSlot() { - public void onEmit () { rconn.close(); } - }).atPrio(1); // ensure that we're before toRemove + signal.connect(v -> rconn.close()).atPrio(1); // ensure that we're before toRemove signal.emit(42); // since toRemove will have been removed during this dispatch, it will not receive the // signal in question, because the higher priority signal triggered first and removed it - assertEquals(Arrays.asList(5), toRemove.events); + assertEquals(Arrays.asList(5), toRemove.values); // finally dispatch one more event and make sure toRemove didn't get it signal.emit(9); - assertEquals(Arrays.asList(5), toRemove.events); + assertEquals(Arrays.asList(5), toRemove.values); } @Test public void testAddAndRemoveDuringDispatch () { final Signal signal = Signal.create(); - final AccSlot toAdd = new AccSlot(); - final AccSlot toRemove = new AccSlot(); + final Accum toAdd = new Accum<>(); + final Accum toRemove = new Accum<>(); final Connection rconn = signal.connect(toRemove); // dispatch one event and make sure it's received by toRemove signal.emit(5); - assertEquals(Arrays.asList(5), toRemove.events); + assertEquals(Arrays.asList(5), toRemove.values); // now add our adder/remover signal, and dispatch again - signal.connect(new UnitSlot() { - public void onEmit () { - rconn.close(); - signal.connect(toAdd); - } + signal.connect(v -> { + rconn.close(); + signal.connect(toAdd); }); signal.emit(42); // make sure toRemove got this event (in this case the adder/remover signal fires *after* // toRemove gets the event) and toAdd didn't - assertEquals(Arrays.asList(5, 42), toRemove.events); - assertEquals(0, toAdd.events.size()); + assertEquals(Arrays.asList(5, 42), toRemove.values); + assertEquals(0, toAdd.values.size()); // finally emit one more and ensure that toAdd got it and toRemove didn't signal.emit(9); - assertEquals(Arrays.asList(9), toAdd.events); - assertEquals(Arrays.asList(5, 42), toRemove.events); + assertEquals(Arrays.asList(9), toAdd.values); + assertEquals(Arrays.asList(5, 42), toRemove.values); } @Test public void testDispatchDuringDispatch () { final Signal signal = Signal.create(); - AccSlot counter = new AccSlot(); + Accum counter = new Accum<>(); signal.connect(counter); // connect a slot that will emit during dispatch - signal.connect(new Slot() { - public void onEmit (Integer value) { - if (value == 5) signal.emit(value*2); - // ensure that we're not notified twice even though we emit during dispatch - else fail("once() lner notified more than once"); - } + signal.connect(value -> { + if (value == 5) signal.emit(value*2); + // ensure that we're not notified twice even though we emit during dispatch + else fail("once() lner notified more than once"); }).once(); // dispatch one event and make sure that both events are received signal.emit(5); - assertEquals(Arrays.asList(5, 10), counter.events); - } - - @Test public void testUnitSlot () { - Signal signal = Signal.create(); - final boolean[] fired = new boolean[] { false }; - signal.connect(new UnitSlot() { - public void onEmit () { - fired[0] = true; - } - }); - signal.emit(42); - assertTrue(fired[0]); + assertEquals(Arrays.asList(5, 10), counter.values); } @Test(expected=RuntimeException.class) public void testSingleFailure () { - UnitSignal signal = new UnitSignal(); - signal.connect(new UnitSlot() { - public void onEmit () { - throw new RuntimeException("Bang!"); - } - }); + Signal.Unit signal = new Signal.Unit(); + signal.connect(() -> { throw new RuntimeException("Bang!"); }); signal.emit(); } @Test(expected=RuntimeException.class) public void testMultiFailure () { - UnitSignal signal = new UnitSignal(); - signal.connect(new UnitSlot() { - public void onEmit () { - throw new RuntimeException("Bing!"); - } - }); - signal.connect(new UnitSlot() { - public void onEmit () { - throw new RuntimeException("Bang!"); - } - }); + Signal.Unit signal = new Signal.Unit(); + signal.connect(() -> { throw new RuntimeException("Bing!"); }); + signal.connect(() -> { throw new RuntimeException("Bang!"); }); signal.emit(); } @Test public void testMappedSignal () { Signal signal = Signal.create(); - SignalView mapped = signal.map(Functions.TO_STRING); + SignalView mapped = signal.map(String::valueOf); Counter counter = new Counter(); Connection c1 = mapped.connect(counter); @@ -228,14 +191,12 @@ public void onEmit () { @Test public void testFilter () { final int[] triggered = new int[1]; - Slot onString = new Slot() { - public void onEmit (String value) { - assertFalse(value == null); - triggered[0]++; - } + SignalView.Listener onString = value -> { + assertFalse(value == null); + triggered[0]++; }; Signal sig = Signal.create(); - sig.filter(Functions.NON_NULL).connect(onString); + sig.filter(v -> v != null).connect(onString); sig.emit(null); sig.emit("foozle"); assertEquals(1, triggered[0]); @@ -243,18 +204,12 @@ public void onEmit (String value) { @Test public void testCollected () { final int[] triggered = new int[1]; - Slot onFloat = new Slot() { - public void onEmit (Float value) { - assertFalse(value == null); - triggered[0]++; - } + SignalView.Listener onFloat = value -> { + assertFalse(value == null); + triggered[0]++; }; Signal sig = Signal.create(); - sig.collect(new Function() { - public Float apply (Number n) { - return (n instanceof Float) ? (Float)n : null; - } - }).connect(onFloat); + sig.collect(n -> (n instanceof Float) ? (Float)n : null).connect(onFloat); sig.emit(null); sig.emit(1); sig.emit(1.2f); @@ -264,38 +219,24 @@ public Float apply (Number n) { @Test public void testFiltered () { final int[] triggered = new int[1]; - Slot onString = new Slot() { - public void onEmit (String value) { - assertFalse(value == null); - triggered[0]++; - } + Slot onString = value -> { + assertFalse(value == null); + triggered[0]++; }; Signal sig = Signal.create(); - sig.connect(onString.filtered(Functions.NON_NULL)); + sig.connect(onString.filtered(v -> v != null)); sig.emit(null); sig.emit("foozle"); assertEquals(1, triggered[0]); } @Test public void testNext () { - class Accum implements SignalView.Listener { - public List values = new ArrayList<>(); - public void onEmit (T value) { - values.add(value); - } - public void assertContains (List values) { - assertEquals(values, this.values); - } - } - Signal signal = Signal.create(); Accum accum = new Accum<>(); Accum accum3 = new Accum<>(); signal.next().onSuccess(accum); - signal.filter(new Function() { - public Boolean apply (Integer v) { return v == 3; } - }).next().onSuccess(accum3); + signal.filter(v -> v == 3).next().onSuccess(accum3); List NONE = Collections.emptyList(); List ONE = Arrays.asList(1), THREE = Arrays.asList(3); @@ -319,10 +260,13 @@ public void assertContains (List values) { accum3.assertContains(Arrays.asList(3)); } - protected static class AccSlot extends Slot { - public List events = new ArrayList(); - public void onEmit (T event) { - events.add(event); + protected class Accum implements SignalView.Listener { + public List values = new ArrayList<>(); + public void onEmit (T value) { + values.add(value); + } + public void assertContains (List values) { + assertEquals(values, this.values); } } } diff --git a/src/test/java/react/TestBase.java b/src/test/java/react/TestBase.java index 0492b42..00040d4 100644 --- a/src/test/java/react/TestBase.java +++ b/src/test/java/react/TestBase.java @@ -9,7 +9,7 @@ public abstract class TestBase { - public static class Counter extends Slot { + public static class Counter implements Slot { public void trigger () { _count++; } diff --git a/src/test/java/react/ValueTest.java b/src/test/java/react/ValueTest.java index 3143207..497b205 100644 --- a/src/test/java/react/ValueTest.java +++ b/src/test/java/react/ValueTest.java @@ -8,6 +8,7 @@ import org.junit.*; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import static org.junit.Assert.*; @@ -19,12 +20,10 @@ public class ValueTest @Test public void testSimpleListener () { Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.connect(new Value.Listener() { - public void onChange (Integer nvalue, Integer ovalue) { - assertEquals(42, ovalue.intValue()); - assertEquals(15, nvalue.intValue()); - fired[0] = true; - } + value.connect((nvalue, ovalue) -> { + assertEquals(42, ovalue.intValue()); + assertEquals(15, nvalue.intValue()); + fired[0] = true; }); assertEquals(42, value.update(15).intValue()); assertEquals(15, value.get().intValue()); @@ -34,11 +33,9 @@ public void onChange (Integer nvalue, Integer ovalue) { @Test public void testAsSignal () { Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.connect(new Slot() { - public void onEmit (Integer value) { - assertEquals(15, value.intValue()); - fired[0] = true; - } + value.connect(newValue -> { + assertEquals(15, newValue.intValue()); + fired[0] = true; }); value.update(15); assertTrue(fired[0]); @@ -54,16 +51,11 @@ public void onEmit (Integer value) { } @Test public void testSignalListener () { - // this ensures that our SignalListener -> ValueListener wrapping is working until we - // switch to the Java 1.8-only approach which will combine those two interfaces into a - // subtype relationship using a default method Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.connect(new SignalView.Listener() { - public void onEmit (Integer value) { - assertEquals(15, value.intValue()); - fired[0] = true; - } + value.connect(newValue -> { + assertEquals(15, newValue.intValue()); + fired[0] = true; }); value.update(15); assertTrue(fired[0]); @@ -71,7 +63,7 @@ public void onEmit (Integer value) { @Test public void testMappedValue () { Value value = Value.create(42); - ValueView mapped = value.map(Functions.TO_STRING); + ValueView mapped = value.map(String::valueOf); SignalTest.Counter counter = new SignalTest.Counter(); Connection c1 = mapped.connect(counter); @@ -94,9 +86,7 @@ public void onEmit (Integer value) { final Value value1 = Value.create(42); final Value value2 = Value.create(24); Value toggle = Value.create(true); - ValueView flatMapped = toggle.flatMap(new Function>() { - public Value apply (Boolean toggle) { return toggle ? value1 : value2; } - }); + ValueView flatMapped = toggle.flatMap(t -> t ? value1 : value2); SignalTest.Counter counter1 = new SignalTest.Counter(); SignalTest.Counter counter2 = new SignalTest.Counter(); @@ -133,9 +123,7 @@ public void onEmit (Integer value) { final Value value1 = Value.create(42); final Value value2 = Value.create(24); Value toggle = Value.create(true); - ValueView flatMapped = toggle.flatMap(new Function>() { - public Value apply (Boolean toggle) { return toggle ? value1 : value2; } - }); + ValueView flatMapped = toggle.flatMap(t -> t ? value1 : value2); assertEquals(42, flatMapped.get().intValue()); toggle.update(false); assertEquals(24, flatMapped.get().intValue()); @@ -144,11 +132,9 @@ public void onEmit (Integer value) { @Test public void testConnectNotify () { Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.connectNotify(new Slot() { - public void onEmit (Integer value) { - assertEquals(42, value.intValue()); - fired[0] = true; - } + value.connectNotify(newValue -> { + assertEquals(42, newValue.intValue()); + fired[0] = true; }); assertTrue(fired[0]); } @@ -156,11 +142,9 @@ public void onEmit (Integer value) { @Test public void testListenNotify () { Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.connectNotify(new Slot() { - public void onEmit (Integer value) { - assertEquals(42, value.intValue()); - fired[0] = true; - } + value.connectNotify(newValue ->{ + assertEquals(42, newValue.intValue()); + fired[0] = true; }); assertTrue(fired[0]); } @@ -217,12 +201,7 @@ public void onEmit (Integer newValue) { final Value value = Value.create(42); final AtomicInteger fired = new AtomicInteger(0); - ValueView.Listener listener = new ValueView.Listener() { - @Override - public void onChange(Integer value, Integer oldValue) { - fired.incrementAndGet(); - } - }; + ValueView.Listener listener = (newValue, oldValue) -> fired.incrementAndGet(); System.gc(); System.gc(); System.gc(); @@ -270,11 +249,9 @@ public void onChange(Integer value, Integer oldValue) { @Test public void testChanges () { Value value = Value.create(42); final boolean[] fired = new boolean[] { false }; - value.changes().connect(new Slot() { - public void onEmit (Integer v) { - assertEquals(15, v.intValue()); - fired[0] = true; - } + value.changes().connect(v -> { + assertEquals(15, v.intValue()); + fired[0] = true; }); value.update(15); assertTrue(fired[0]); diff --git a/src/test/java/react/ValuesTest.java b/src/test/java/react/ValuesTest.java index dfc8471..f4c944a 100644 --- a/src/test/java/react/ValuesTest.java +++ b/src/test/java/react/ValuesTest.java @@ -19,15 +19,12 @@ public class ValuesTest Value c = Value.create(false); final boolean[] fired = new boolean[] { false }; - @SuppressWarnings("unchecked") // TODO: remove when we use JDK 1.7 @SafeVarargs ValueView anded = Values.and(a, b, c); assertFalse(anded.get()); - anded.connect(new Value.Listener() { - public void onChange (Boolean value, Boolean oldValue) { - assertFalse(value); - assertFalse(oldValue); - fired[0] = true; - } + anded.connect((value, oldValue) -> { + assertFalse(value); + assertFalse(oldValue); + fired[0] = true; }).once(); a.update(true); assertTrue(fired[0]); From b4d50202c874af7c171bbdbf70df14fcdc4f0cd5 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 10 Nov 2017 08:16:54 -0800 Subject: [PATCH 2/5] Switch to two space indent. --- src/main/java/react/AbstractSignal.java | 134 +-- src/main/java/react/AbstractValue.java | 336 ++++---- src/main/java/react/Closeable.java | 136 +-- src/main/java/react/Connection.java | 178 ++-- src/main/java/react/Cons.java | 214 ++--- src/main/java/react/IntValue.java | 94 +- src/main/java/react/MappedValue.java | 58 +- .../java/react/MultiFailureException.java | 68 +- src/main/java/react/RCollection.java | 78 +- src/main/java/react/RFuture.java | 532 ++++++------ src/main/java/react/RList.java | 586 ++++++------- src/main/java/react/RMap.java | 801 +++++++++--------- src/main/java/react/RPromise.java | 62 +- src/main/java/react/RQueue.java | 358 ++++---- src/main/java/react/RSet.java | 481 ++++++----- src/main/java/react/Reactor.java | 370 ++++---- src/main/java/react/Signal.java | 62 +- src/main/java/react/SignalView.java | 88 +- src/main/java/react/Slot.java | 90 +- src/main/java/react/Try.java | 188 ++-- src/main/java/react/Value.java | 88 +- src/main/java/react/ValueView.java | 144 ++-- src/main/java/react/Values.java | 408 ++++----- 23 files changed, 2769 insertions(+), 2785 deletions(-) diff --git a/src/main/java/react/AbstractSignal.java b/src/main/java/react/AbstractSignal.java index 5cec4f9..9ce052c 100644 --- a/src/main/java/react/AbstractSignal.java +++ b/src/main/java/react/AbstractSignal.java @@ -12,81 +12,81 @@ * exposing a public interface for emitting events. This can be used by entities which wish to * expose a signal-like interface for listening, without allowing external callers to emit signals. */ -public class AbstractSignal extends Reactor implements SignalView -{ - @Override public SignalView map (final Function func) { - final AbstractSignal outer = this; - return new MappedSignal() { - @Override protected Connection connect () { - return outer.connect(new Listener() { - @Override public void onEmit (T value) { - notifyEmit(func.apply(value)); - } - }); - } - }; - } +public class AbstractSignal extends Reactor implements SignalView { + + @Override public SignalView map (final Function func) { + final AbstractSignal outer = this; + return new MappedSignal() { + @Override protected Connection connect () { + return outer.connect(new Listener() { + @Override public void onEmit (T value) { + notifyEmit(func.apply(value)); + } + }); + } + }; + } - @Override public SignalView filter (final Function pred) { - final AbstractSignal outer = this; - return new MappedSignal() { - @Override protected Connection connect () { - return outer.connect(new Listener() { - @Override public void onEmit (T value) { - if (pred.apply(value)) { - notifyEmit(value); - } - } - }); + @Override public SignalView filter (final Function pred) { + final AbstractSignal outer = this; + return new MappedSignal() { + @Override protected Connection connect () { + return outer.connect(new Listener() { + @Override public void onEmit (T value) { + if (pred.apply(value)) { + notifyEmit(value); } - }; - } + } + }); + } + }; + } - @Override public SignalView collect (final Function collector) { - final AbstractSignal outer = this; - return new MappedSignal() { - @Override protected Connection connect () { - return outer.connect(new Listener() { - @Override public void onEmit (T value) { - M mapped = collector.apply(value); - if (mapped != null) { - notifyEmit(mapped); - } - } - }); + @Override public SignalView collect (final Function collector) { + final AbstractSignal outer = this; + return new MappedSignal() { + @Override protected Connection connect () { + return outer.connect(new Listener() { + @Override public void onEmit (T value) { + M mapped = collector.apply(value); + if (mapped != null) { + notifyEmit(mapped); } - }; - } + } + }); + } + }; + } - @Override public RFuture next () { - final RPromise result = RPromise.create(); - connect(result::succeed).once(); - return result; - } + @Override public RFuture next () { + final RPromise result = RPromise.create(); + connect(result::succeed).once(); + return result; + } - @Override public Connection connect (Listener slot) { - return addConnection(slot); - } + @Override public Connection connect (Listener slot) { + return addConnection(slot); + } - @Override public void disconnect (Listener slot) { - removeConnection(slot); - } + @Override public void disconnect (Listener slot) { + removeConnection(slot); + } - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; - return p; - } + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; + return p; + } - /** - * Emits the supplied event to all connected slots. - */ - protected void notifyEmit (T event) { - notify(EMIT, event, null, null); - } + /** + * Emits the supplied event to all connected slots. + */ + protected void notifyEmit (T event) { + notify(EMIT, event, null, null); + } - @SuppressWarnings("unchecked") protected static final Notifier EMIT = new Notifier() { - public void notify (Object slot, Object event, Object _1, Object _2) { - ((Listener)slot).onEmit(event); - } - }; + @SuppressWarnings("unchecked") protected static final Notifier EMIT = new Notifier() { + public void notify (Object slot, Object event, Object _1, Object _2) { + ((Listener)slot).onEmit(event); + } + }; } diff --git a/src/main/java/react/AbstractValue.java b/src/main/java/react/AbstractValue.java index c78a7f9..9301b30 100644 --- a/src/main/java/react/AbstractValue.java +++ b/src/main/java/react/AbstractValue.java @@ -13,173 +13,173 @@ * observable values, but must manage the maintenance and distribution of value updates themselves * (so that they may send them over the network, for example). */ -public abstract class AbstractValue extends Reactor implements ValueView -{ - @Override public ValueView map (final Function func) { - final AbstractValue outer = this; - return new MappedValue() { - @Override public M get () { - return func.apply(outer.get()); - } - @Override public String toString () { - return outer + ".map(" + func + ")"; - } - @Override protected Connection connect () { - return outer.connect((v, ov) -> notifyChange(func.apply(v), func.apply(ov))); - } - }; - } - - @Override public ValueView flatMap ( - final Function> func) { - final AbstractValue outer = this; - final ValueView> mapped = map(func); - return new MappedValue() { - private Connection conn; - - @Override public M get () { - return mapped.get().get(); - } - @Override public String toString () { - return outer + ".flatMap(" + func + ")"; - } - @Override protected Connection connect () { - conn = mapped.connect(v -> reconnect()); - return mapped.get().connect((v, ov) -> notifyChange(v, ov)); - } - @Override protected void disconnect () { - super.disconnect(); - if (conn != null) conn.close(); - } - }; - } - - @Override public SignalView changes () { - final AbstractValue outer = this; - return new MappedSignal() { - @Override protected Connection connect () { - return outer.connect((v, ov) -> notifyEmit(v)); - } - }; - } - - @Override public RFuture when (Function cond) { - T current = get(); - if (cond.apply(current)) return RFuture.success(current); - else return changes().filter(cond).next(); - } - - @Override public Connection connect (Listener listener) { - return addConnection(listener); - } - @Override public Connection connectNotify (Listener listener) { - // connect before calling emit; if the listener changes the value in the body of onEmit, it - // will expect to be notified of that change; however if onEmit throws a runtime exception, - // we need to take care of disconnecting the listener because the returned connection - // instance will never reach the caller - Connection conn = connect(listener); - try { - listener.onChange(get(), null); - return conn; - } catch (RuntimeException re) { - conn.close(); - throw re; - } catch (Error e) { - conn.close(); - throw e; - } - } - - @Override public Connection connect (Slot listener) { - return connect((Listener)listener); - } - @Override public Connection connectNotify (Slot listener) { - return connectNotify((Listener)listener); - } - - @Override public void disconnect (Listener listener) { - removeConnection(listener); - } - - @Override public int hashCode () { - T value = get(); - return (value == null) ? 0 : value.hashCode(); - } - - @Override public boolean equals (Object other) { - if (other == null) return false; - if (other.getClass() != getClass()) return false; - T value = get(); - @SuppressWarnings("unchecked") T ovalue = ((AbstractValue)other).get(); - return areEqual(value, ovalue); - } - - @Override public String toString () { - String cname = getClass().getName(); - return cname.substring(cname.lastIndexOf(".")+1) + "(" + get() + ")"; - } - - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; - return p; - } - - /** - * Updates the value contained in this instance and notifies registered listeners iff said - * value is not equal to the value already contained in this instance (per {@link #areEqual}). - */ - protected T updateAndNotifyIf (T value) { - return updateAndNotify(value, false); - } - - /** - * Updates the value contained in this instance and notifies registered listeners. - * @return the previously contained value. - */ - protected T updateAndNotify (T value) { - return updateAndNotify(value, true); - } - - /** - * Updates the value contained in this instance and notifies registered listeners. - * @param force if true, the listeners will always be notified, if false the will be notified - * only if the new value is not equal to the old value (per {@link #areEqual}). - * @return the previously contained value. - */ - protected T updateAndNotify (T value, boolean force) { - checkMutate(); - T ovalue = updateLocal(value); - if (force || !areEqual(value, ovalue)) { - emitChange(value, ovalue); - } - return ovalue; - } - - /** - * Emits a change notification. Default implementation immediately notifies listeners. - */ - protected void emitChange (T value, T oldValue) { - notifyChange(value, oldValue); - } - - /** - * Notifies our listeners of a value change. - */ - protected void notifyChange (T value, T oldValue) { - notify(CHANGE, value, oldValue, null); - } - - /** - * Updates our locally stored value. Default implementation throws unsupported operation. - * @return the previously stored value. - */ - protected T updateLocal (T value) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("unchecked") protected static final Notifier CHANGE = new Notifier() { - public void notify (Object lner, Object value, Object oldValue, Object ignored) { - ((Listener)lner).onChange(value, oldValue); - } +public abstract class AbstractValue extends Reactor implements ValueView { + + @Override public ValueView map (final Function func) { + final AbstractValue outer = this; + return new MappedValue() { + @Override public M get () { + return func.apply(outer.get()); + } + @Override public String toString () { + return outer + ".map(" + func + ")"; + } + @Override protected Connection connect () { + return outer.connect((v, ov) -> notifyChange(func.apply(v), func.apply(ov))); + } + }; + } + + @Override public ValueView flatMap ( + final Function> func) { + final AbstractValue outer = this; + final ValueView> mapped = map(func); + return new MappedValue() { + private Connection conn; + + @Override public M get () { + return mapped.get().get(); + } + @Override public String toString () { + return outer + ".flatMap(" + func + ")"; + } + @Override protected Connection connect () { + conn = mapped.connect(v -> reconnect()); + return mapped.get().connect((v, ov) -> notifyChange(v, ov)); + } + @Override protected void disconnect () { + super.disconnect(); + if (conn != null) conn.close(); + } + }; + } + + @Override public SignalView changes () { + final AbstractValue outer = this; + return new MappedSignal() { + @Override protected Connection connect () { + return outer.connect((v, ov) -> notifyEmit(v)); + } }; + } + + @Override public RFuture when (Function cond) { + T current = get(); + if (cond.apply(current)) return RFuture.success(current); + else return changes().filter(cond).next(); + } + + @Override public Connection connect (Listener listener) { + return addConnection(listener); + } + @Override public Connection connectNotify (Listener listener) { + // connect before calling emit; if the listener changes the value in the body of onEmit, it + // will expect to be notified of that change; however if onEmit throws a runtime exception, + // we need to take care of disconnecting the listener because the returned connection + // instance will never reach the caller + Connection conn = connect(listener); + try { + listener.onChange(get(), null); + return conn; + } catch (RuntimeException re) { + conn.close(); + throw re; + } catch (Error e) { + conn.close(); + throw e; + } + } + + @Override public Connection connect (Slot listener) { + return connect((Listener)listener); + } + @Override public Connection connectNotify (Slot listener) { + return connectNotify((Listener)listener); + } + + @Override public void disconnect (Listener listener) { + removeConnection(listener); + } + + @Override public int hashCode () { + T value = get(); + return (value == null) ? 0 : value.hashCode(); + } + + @Override public boolean equals (Object other) { + if (other == null) return false; + if (other.getClass() != getClass()) return false; + T value = get(); + @SuppressWarnings("unchecked") T ovalue = ((AbstractValue)other).get(); + return areEqual(value, ovalue); + } + + @Override public String toString () { + String cname = getClass().getName(); + return cname.substring(cname.lastIndexOf(".")+1) + "(" + get() + ")"; + } + + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)Slot.NOOP; + return p; + } + + /** + * Updates the value contained in this instance and notifies registered listeners iff said + * value is not equal to the value already contained in this instance (per {@link #areEqual}). + */ + protected T updateAndNotifyIf (T value) { + return updateAndNotify(value, false); + } + + /** + * Updates the value contained in this instance and notifies registered listeners. + * @return the previously contained value. + */ + protected T updateAndNotify (T value) { + return updateAndNotify(value, true); + } + + /** + * Updates the value contained in this instance and notifies registered listeners. + * @param force if true, the listeners will always be notified, if false the will be notified + * only if the new value is not equal to the old value (per {@link #areEqual}). + * @return the previously contained value. + */ + protected T updateAndNotify (T value, boolean force) { + checkMutate(); + T ovalue = updateLocal(value); + if (force || !areEqual(value, ovalue)) { + emitChange(value, ovalue); + } + return ovalue; + } + + /** + * Emits a change notification. Default implementation immediately notifies listeners. + */ + protected void emitChange (T value, T oldValue) { + notifyChange(value, oldValue); + } + + /** + * Notifies our listeners of a value change. + */ + protected void notifyChange (T value, T oldValue) { + notify(CHANGE, value, oldValue, null); + } + + /** + * Updates our locally stored value. Default implementation throws unsupported operation. + * @return the previously stored value. + */ + protected T updateLocal (T value) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") protected static final Notifier CHANGE = new Notifier() { + public void notify (Object lner, Object value, Object oldValue, Object ignored) { + ((Listener)lner).onChange(value, oldValue); + } + }; } diff --git a/src/main/java/react/Closeable.java b/src/main/java/react/Closeable.java index a75c1cb..8662c1c 100644 --- a/src/main/java/react/Closeable.java +++ b/src/main/java/react/Closeable.java @@ -14,81 +14,81 @@ */ public interface Closeable extends AutoCloseable { - /** Maintains a set of closeables to allow mass operations on them. */ - class Set implements Closeable { + /** Maintains a set of closeables to allow mass operations on them. */ + class Set implements Closeable { - /** Closes all connections in this set and empties it. */ - @Override public void close () { - if (_set != null) { - MultiFailureException error = null; - for (AutoCloseable c : _set) try { - c.close(); - } catch (Exception e) { - if (error == null) error = new MultiFailureException(); - error.addSuppressed(e); - } - _set.clear(); - if (error != null) throw error; - } - } - - /** Adds the supplied connection to this set. - * @return the supplied connection.*/ - public T add (T c) { - if (_set == null) _set = new HashSet<>(); - _set.add(c); - return c; + /** Closes all connections in this set and empties it. */ + @Override public void close () { + if (_set != null) { + MultiFailureException error = null; + for (AutoCloseable c : _set) try { + c.close(); + } catch (Exception e) { + if (error == null) error = new MultiFailureException(); + error.addSuppressed(e); } + _set.clear(); + if (error != null) throw error; + } + } - /** Removes a closeable from this set while leaving its status unchanged. */ - public void remove (AutoCloseable c) { - if (_set != null) _set.remove(c); - } + /** Adds the supplied connection to this set. + * @return the supplied connection.*/ + public T add (T c) { + if (_set == null) _set = new HashSet<>(); + _set.add(c); + return c; + } - protected HashSet _set; // lazily created + /** Removes a closeable from this set while leaving its status unchanged. */ + public void remove (AutoCloseable c) { + if (_set != null) _set.remove(c); } - /** A closable which no-ops on {@link #close} and throws an exception for all other - * methods. This is for the following code pattern: - * - *
{@code
-      * Closable _conn = Closeable.Util.NOOP;
-      * void open () {
-      *    _conn = whatever.connect(...);
-      * }
-      * void close () {
-      *    _conn = Closeable.Util.close(_conn);
-      * }
-      * }
- * - * In that it allows {@code close} to avoid a null check if it's possible for - * {@code close} to be called with no call to {@code open} or repeatedly. - */ - static final Closeable NOOP = new Closeable() { - public void close () {} // noop! - }; + protected HashSet _set; // lazily created + } - /** Creates a closable that closes multiple connections at once. */ - static Closeable join (final Closeable... cons) { - return new Closeable() { - @Override public void close () { - for (int ii = 0; ii < cons.length; ii++) { - if (cons[ii] == null) continue; - cons[ii].close(); - cons[ii] = null; - } - } - }; - } + /** A closable which no-ops on {@link #close} and throws an exception for all other + * methods. This is for the following code pattern: + * + *
{@code
+    * Closable _conn = Closeable.Util.NOOP;
+    * void open () {
+    *    _conn = whatever.connect(...);
+    * }
+    * void close () {
+    *    _conn = Closeable.Util.close(_conn);
+    * }
+    * }
+ * + * In that it allows {@code close} to avoid a null check if it's possible for + * {@code close} to be called with no call to {@code open} or repeatedly. + */ + static final Closeable NOOP = new Closeable() { + public void close () {} // noop! + }; - /** Closes {@code con} and returns {@link #NOOP}. This enables code like: - * {@code con = Connection.close(con);} which simplifies disconnecting and resetting to - * {@link #NOOP}, a given connection reference. */ - static Closeable close (Closeable con) { - con.close(); - return NOOP; - } + /** Creates a closable that closes multiple connections at once. */ + static Closeable join (final Closeable... cons) { + return new Closeable() { + @Override public void close () { + for (int ii = 0; ii < cons.length; ii++) { + if (cons[ii] == null) continue; + cons[ii].close(); + cons[ii] = null; + } + } + }; + } + + /** Closes {@code con} and returns {@link #NOOP}. This enables code like: + * {@code con = Connection.close(con);} which simplifies disconnecting and resetting to + * {@link #NOOP}, a given connection reference. */ + static Closeable close (Closeable con) { + con.close(); + return NOOP; + } - /** Closes this closeable resource. */ - void close (); + /** Closes this closeable resource. */ + void close (); } diff --git a/src/main/java/react/Connection.java b/src/main/java/react/Connection.java index 436d932..7212550 100644 --- a/src/main/java/react/Connection.java +++ b/src/main/java/react/Connection.java @@ -9,97 +9,97 @@ * Provides a mechanism to cancel a slot or listener registration, or to perform post-registration * adjustment like making the registration single-shot. */ -public abstract class Connection implements Closeable -{ - /** - * Returns a single connection which aggregates all of the supplied connections. When the - * aggregated connection is closed, the underlying connections are closed. When its priority is - * changed the underlying connections' priorities are changed. Etc. - */ - public static Connection join (final Connection... conns) { - return new Connection() { - @Override public void close () { - for (Connection c : conns) c.close(); - } - @Override public Connection once () { - for (Connection c : conns) c.once(); - return this; - } - @Override public Connection atPrio (int priority) { - for (Connection c : conns) c.atPrio(priority); - return this; - } - @Override public Connection holdWeakly () { - for (Connection c : conns) c.holdWeakly(); - return this; - } - }; - } +public abstract class Connection implements Closeable { - /** - * Disconnects this registration. Subsequent events will not be dispatched to the associated - * slot or listener. - */ - public abstract void close (); + /** + * Returns a single connection which aggregates all of the supplied connections. When the + * aggregated connection is closed, the underlying connections are closed. When its priority is + * changed the underlying connections' priorities are changed. Etc. + */ + public static Connection join (final Connection... conns) { + return new Connection() { + @Override public void close () { + for (Connection c : conns) c.close(); + } + @Override public Connection once () { + for (Connection c : conns) c.once(); + return this; + } + @Override public Connection atPrio (int priority) { + for (Connection c : conns) c.atPrio(priority); + return this; + } + @Override public Connection holdWeakly () { + for (Connection c : conns) c.holdWeakly(); + return this; + } + }; + } - /** - * Converts this connection into a one-shot connection. After the first time the slot or - * listener is notified, it will automatically be disconnected. - * - *

NOTE: if you are dispatching signals in a multithreaded environment, it is - * possible for your connected listener to be notified before this call has a chance to mark it - * as one-shot. Thus you could receive multiple notifications. If you require this to be - * avoided, you must synchronize on the signal/value/etc. on which you are adding a - * listener:

- * - *
{@code
-     * Signal signal = ...;
-     * Connection conn;
-     * synchronized (signal) {
-     *   conn = signal.connect(v -> ...).once();
-     * }
-     * }
- * - * @return this connection instance for convenient chaining. - */ - public abstract Connection once (); + /** + * Disconnects this registration. Subsequent events will not be dispatched to the associated slot + * or listener. + */ + public abstract void close (); - /** - * Changes the priority of this connection to the specified value. Connections are notified from - * highest priority to lowest priority. The default priority is zero. - * - *

This should generally be done simultaneously with creating a connection. For example:

- * - *
{@code
-     * Signal signal = ...;
-     * Connection conn = signal.connect(value -> ... ).atPrio(5);
-     * }
- * - *

NOTE: if you are dispatching signals in a multithreaded environment, it is - * possible for your connected listener to be notified at priority zero before this call has a - * chance to update its priority. If you require this to be avoided, you must synchronize on - * the signal/value/etc. on which you are adding a listener:

- * - *
{@code
-     * Signal signal = ...;
-     * Connection conn;
-     * synchronized (signal) {
-     *   conn = signal.connect(v -> ...).atPrio(5);
-     * }
-     * }
- * - * @return this connection instance for convenient chaining. - */ - public abstract Connection atPrio (int priority); + /** + * Converts this connection into a one-shot connection. After the first time the slot or listener + * is notified, it will automatically be disconnected. + * + *

NOTE: if you are dispatching signals in a multithreaded environment, it is + * possible for your connected listener to be notified before this call has a chance to mark it + * as one-shot. Thus you could receive multiple notifications. If you require this to be + * avoided, you must synchronize on the signal/value/etc. on which you are adding a + * listener:

+ * + *
{@code
+   * Signal signal = ...;
+   * Connection conn;
+   * synchronized (signal) {
+   *   conn = signal.connect(v -> ...).once();
+   * }
+   * }
+ * + * @return this connection instance for convenient chaining. + */ + public abstract Connection once (); - /** - * Changes this connection to one held by a weak reference. It only remains connected as long - * as its target listener is referenced elsewhere. - * - *

NOTE: weak references are not supported in JavaScript. When using this library - * in GWT, the reference remains strong.

- * - * @return this connection instance for convenient chaining. - */ - public abstract Connection holdWeakly (); + /** + * Changes the priority of this connection to the specified value. Connections are notified from + * highest priority to lowest priority. The default priority is zero. + * + *

This should generally be done simultaneously with creating a connection. For example:

+ * + *
{@code
+   * Signal signal = ...;
+   * Connection conn = signal.connect(value -> ... ).atPrio(5);
+   * }
+ * + *

NOTE: if you are dispatching signals in a multithreaded environment, it is + * possible for your connected listener to be notified at priority zero before this call has a + * chance to update its priority. If you require this to be avoided, you must synchronize on + * the signal/value/etc. on which you are adding a listener:

+ * + *
{@code
+   * Signal signal = ...;
+   * Connection conn;
+   * synchronized (signal) {
+   *   conn = signal.connect(v -> ...).atPrio(5);
+   * }
+   * }
+ * + * @return this connection instance for convenient chaining. + */ + public abstract Connection atPrio (int priority); + + /** + * Changes this connection to one held by a weak reference. It only remains connected as long + * as its target listener is referenced elsewhere. + * + *

NOTE: weak references are not supported in JavaScript. When using this library in + * GWT, the reference remains strong.

+ * + * @return this connection instance for convenient chaining. + */ + public abstract Connection holdWeakly (); } diff --git a/src/main/java/react/Cons.java b/src/main/java/react/Cons.java index 149f2e3..a4508ac 100644 --- a/src/main/java/react/Cons.java +++ b/src/main/java/react/Cons.java @@ -12,118 +12,118 @@ /** * Implements {@link Connection} and a linked-list style listener list for {@link Reactor}s. */ -class Cons extends Connection -{ - /** The next connection in our chain. */ - public Cons next; +class Cons extends Connection { - /** Indicates whether this connection is one-shot or persistent. */ - public final boolean oneShot () { return _oneShot; } + /** The next connection in our chain. */ + public Cons next; - /** Returns the listener for this cons cell. */ - public RListener listener () { - return _ref.get(this); - } - - @Override public void close () { - // multiple disconnects are OK, we just NOOP after the first one - if (_owner != null) { - _ref.defang(_owner.placeholderListener()); - _owner.disconnect(this); - _owner = null; - } - } + /** Indicates whether this connection is one-shot or persistent. */ + public final boolean oneShot () { return _oneShot; } - @Override public Connection once () { - _oneShot = true; - return this; - } + /** Returns the listener for this cons cell. */ + public RListener listener () { + return _ref.get(this); + } - @Override public Connection atPrio (int priority) { - if (_owner == null) throw new IllegalStateException( - "Cannot change priority of disconnected connection."); - _owner.disconnect(this); - next = null; - _priority = priority; - _owner.addCons(this); - return this; + @Override public void close () { + // multiple disconnects are OK, we just NOOP after the first one + if (_owner != null) { + _ref.defang(_owner.placeholderListener()); + _owner.disconnect(this); + _owner = null; } - - @Override public Connection holdWeakly () { - if (_owner == null) throw new IllegalStateException( - "Cannot change disconnected connection to weak."); - if (!_ref.isWeak()) _ref = new WeakRef(_ref.get(this)); - return this; + } + + @Override public Connection once () { + _oneShot = true; + return this; + } + + @Override public Connection atPrio (int priority) { + if (_owner == null) throw new IllegalStateException( + "Cannot change priority of disconnected connection."); + _owner.disconnect(this); + next = null; + _priority = priority; + _owner.addCons(this); + return this; + } + + @Override public Connection holdWeakly () { + if (_owner == null) throw new IllegalStateException( + "Cannot change disconnected connection to weak."); + if (!_ref.isWeak()) _ref = new WeakRef(_ref.get(this)); + return this; + } + + @Override public String toString () { + return "[owner=" + _owner + ", pri=" + _priority + ", lner=" + listener() + + ", hasNext=" + (next != null) + ", oneShot=" + oneShot() + "]"; + } + + protected Cons (Reactor owner, RListener listener) { + _owner = owner; + _ref = new StrongRef(listener); + } + + private static abstract class ListenerRef { + abstract boolean isWeak (); + abstract void defang (RListener noop); + abstract RListener get (Cons cons); + } + + private static class StrongRef extends ListenerRef { + private RListener _lner; + public StrongRef (RListener lner) { _lner = lner; } + public boolean isWeak () { return false; } + public void defang (RListener noop) { _lner = noop; } + public RListener get (Cons cons) { return _lner; } + } + + private static class WeakRef extends ListenerRef { + private WeakReference _wref; + private RListener _noop; + public WeakRef (RListener lner) { _wref = new WeakReference(lner); } + public boolean isWeak () { return true; } + public void defang (RListener noop) { _noop = noop; _wref = null; } + public RListener get (Cons cons) { + if (_wref != null) { + RListener listener = _wref.get(); + if (listener != null) return listener; + cons.close(); // close will defang() us + } + return _noop; } - - @Override public String toString () { - return "[owner=" + _owner + ", pri=" + _priority + ", lner=" + listener() + - ", hasNext=" + (next != null) + ", oneShot=" + oneShot() + "]"; + } + + static Cons insert (Cons head, Cons cons) { + if (head == null) { + return cons; + } else if (cons._priority > head._priority) { + cons.next = head; + return cons; + } else { + head.next = insert(head.next, cons); + return head; } - - protected Cons (Reactor owner, RListener listener) { - _owner = owner; - _ref = new StrongRef(listener); - } - - private static abstract class ListenerRef { - abstract boolean isWeak (); - abstract void defang (RListener noop); - abstract RListener get (Cons cons); - } - - private static class StrongRef extends ListenerRef { - private RListener _lner; - public StrongRef (RListener lner) { _lner = lner; } - public boolean isWeak () { return false; } - public void defang (RListener noop) { _lner = noop; } - public RListener get (Cons cons) { return _lner; } - } - - private static class WeakRef extends ListenerRef { - private WeakReference _wref; - private RListener _noop; - public WeakRef (RListener lner) { _wref = new WeakReference(lner); } - public boolean isWeak () { return true; } - public void defang (RListener noop) { _noop = noop; _wref = null; } - public RListener get (Cons cons) { - if (_wref != null) { - RListener listener = _wref.get(); - if (listener != null) return listener; - cons.close(); // close will defang() us - } - return _noop; - } - } - - static Cons insert (Cons head, Cons cons) { - if (head == null) { - return cons; - } else if (cons._priority > head._priority) { - cons.next = head; - return cons; - } else { - head.next = insert(head.next, cons); - return head; - } - } - - static Cons remove (Cons head, Cons cons) { - if (head == null) return head; - if (head == cons) return head.next; - head.next = remove(head.next, cons); - return head; - } - - static Cons removeAll (Cons head, RListener listener) { - if (head == null) return null; - if (head.listener() == listener) return removeAll(head.next, listener); - head.next = removeAll(head.next, listener); - return head; - } - - private Reactor _owner; - private ListenerRef _ref; - private boolean _oneShot; // defaults to false - private int _priority; // defaults to zero + } + + static Cons remove (Cons head, Cons cons) { + if (head == null) return head; + if (head == cons) return head.next; + head.next = remove(head.next, cons); + return head; + } + + static Cons removeAll (Cons head, RListener listener) { + if (head == null) return null; + if (head.listener() == listener) return removeAll(head.next, listener); + head.next = removeAll(head.next, listener); + return head; + } + + private Reactor _owner; + private ListenerRef _ref; + private boolean _oneShot; // defaults to false + private int _priority; // defaults to zero } diff --git a/src/main/java/react/IntValue.java b/src/main/java/react/IntValue.java index aa23fb6..5577059 100644 --- a/src/main/java/react/IntValue.java +++ b/src/main/java/react/IntValue.java @@ -9,56 +9,56 @@ * A {@link Value} specialized for ints, which has some useful methods. Note: this specialization * does not mean "avoids auto-boxing", it just means "adds useful methods". */ -public class IntValue extends Value -{ - /** - * Creates an instance with the specified starting value. - */ - public IntValue (int value) { - super(value); - } +public class IntValue extends Value { - /** - * Increments this value by {@code amount}. - * @return the incremented value. Note that this differs from {@link #update}, which returns - * the previous value. - */ - public int increment (int amount) { - return updateInt(get() + amount); - } + /** + * Creates an instance with the specified starting value. + */ + public IntValue (int value) { + super(value); + } - /** - * Increments this value by {@code amount}, clamping at {@code max} if the current value plus - * {@code amount} exceeds {@code max}. - * @return the incremented and clamped value. Note that this differs from {@link #update}, - * which returns the previous value. - */ - public int incrementClamp (int amount, int max) { - return updateInt(Math.min(get() + amount, max)); - } + /** + * Increments this value by {@code amount}. + * @return the incremented value. Note that this differs from {@link #update}, which returns + * the previous value. + */ + public int increment (int amount) { + return updateInt(get() + amount); + } - /** - * Increments this value by {@code amount} (or decrements if negative), clamping to the range - * [{@code min}, {@code max}]. - * @return the incremented and clamped value. Note that this differs from {@link #update}, - * which returns the previous value. - */ - public int incrementClamp (int amount, int min, int max) { - return updateInt(Math.max(min, Math.min(get() + amount, max))); - } + /** + * Increments this value by {@code amount}, clamping at {@code max} if the current value plus + * {@code amount} exceeds {@code max}. + * @return the incremented and clamped value. Note that this differs from {@link #update}, + * which returns the previous value. + */ + public int incrementClamp (int amount, int max) { + return updateInt(Math.min(get() + amount, max)); + } - /** - * Decrements this value by {@code amount}, clamping at {@code min} if the current value minus - * {@code amount} is less than {@code min}. - * @return the decremented and clamped value. Note that this differs from {@link #update}, - * which returns the previous value. - */ - public int decrementClamp (int amount, int min) { - return updateInt(Math.max(get() - amount, min)); - } + /** + * Increments this value by {@code amount} (or decrements if negative), clamping to the range + * {@code [min, max]}. + * @return the incremented and clamped value. Note that this differs from {@link #update}, + * which returns the previous value. + */ + public int incrementClamp (int amount, int min, int max) { + return updateInt(Math.max(min, Math.min(get() + amount, max))); + } - protected int updateInt (int value) { - update(value); - return value; - } + /** + * Decrements this value by {@code amount}, clamping at {@code min} if the current value minus + * {@code amount} is less than {@code min}. + * @return the decremented and clamped value. Note that this differs from {@link #update}, + * which returns the previous value. + */ + public int decrementClamp (int amount, int min) { + return updateInt(Math.max(get() - amount, min)); + } + + protected int updateInt (int value) { + update(value); + return value; + } } diff --git a/src/main/java/react/MappedValue.java b/src/main/java/react/MappedValue.java index c2aa418..bf11a39 100644 --- a/src/main/java/react/MappedValue.java +++ b/src/main/java/react/MappedValue.java @@ -11,39 +11,37 @@ * connection to the underlying value, and when it removes its last connection it clears its * connection from the underlying value. */ -abstract class MappedValue extends AbstractValue -{ - /** - * Establishes a connection to our source value. Called when go from zero to one listeners. - * When we go from one to zero listeners, the connection will automatically be cleared. - * - * @return the newly established connection. - */ - protected abstract Connection connect (); - - protected void disconnect () { - if (_conn != null) { - _conn.close(); - _conn = null; - } - } +abstract class MappedValue extends AbstractValue { - protected void reconnect () { - disconnect(); - _conn = connect(); - } + /** + * Establishes a connection to our source value. Called when go from zero to one listeners. + * When we go from one to zero listeners, the connection will automatically be cleared. + * + * @return the newly established connection. + */ + protected abstract Connection connect (); - @Override - protected void connectionAdded () { - super.connectionAdded(); - if (_conn == null) _conn = connect(); + protected void disconnect () { + if (_conn != null) { + _conn.close(); + _conn = null; } + } - @Override - protected void connectionRemoved () { - super.connectionRemoved(); - if (!hasConnections()) disconnect(); - } + protected void reconnect () { + disconnect(); + _conn = connect(); + } + + @Override protected void connectionAdded () { + super.connectionAdded(); + if (_conn == null) _conn = connect(); + } + + @Override protected void connectionRemoved () { + super.connectionRemoved(); + if (!hasConnections()) disconnect(); + } - protected Connection _conn; + protected Connection _conn; } diff --git a/src/main/java/react/MultiFailureException.java b/src/main/java/react/MultiFailureException.java index f81efe6..25c46a5 100644 --- a/src/main/java/react/MultiFailureException.java +++ b/src/main/java/react/MultiFailureException.java @@ -13,45 +13,45 @@ /** * An exception thrown to communicate multiple failures. */ -public class MultiFailureException extends RuntimeException -{ - public Iterable failures () { - return _failures; +public class MultiFailureException extends RuntimeException { + + public Iterable failures () { + return _failures; + } + + public void addFailure (Throwable t) { + _failures.add(t); + } + + @Override + public String getMessage () { + StringBuilder buf = new StringBuilder(); + for (Throwable failure : _failures) { + if (buf.length() > 0) buf.append(", "); + buf.append(failure.getClass().getName()).append(": ").append(failure.getMessage()); } + return _failures.size() + " failures: " + buf; + } - public void addFailure (Throwable t) { - _failures.add(t); + @Override + public void printStackTrace (PrintStream s) { + for (Throwable failure : _failures) { + failure.printStackTrace(s); } + } - @Override - public String getMessage () { - StringBuilder buf = new StringBuilder(); - for (Throwable failure : _failures) { - if (buf.length() > 0) buf.append(", "); - buf.append(failure.getClass().getName()).append(": ").append(failure.getMessage()); - } - return _failures.size() + " failures: " + buf; + @Override + public void printStackTrace (PrintWriter w) { + for (Throwable failure : _failures) { + failure.printStackTrace(w); } + } - @Override - public void printStackTrace (PrintStream s) { - for (Throwable failure : _failures) { - failure.printStackTrace(s); - } - } - - @Override - public void printStackTrace (PrintWriter w) { - for (Throwable failure : _failures) { - failure.printStackTrace(w); - } - } - - @Override - public Throwable fillInStackTrace () { - return this; // no stack trace here - } + @Override + public Throwable fillInStackTrace () { + return this; // no stack trace here + } - // this must be non-final so that GWT can serialize it - protected List _failures = new ArrayList(); + // this must be non-final so that GWT can serialize it + protected List _failures = new ArrayList(); } diff --git a/src/main/java/react/RCollection.java b/src/main/java/react/RCollection.java index 5e205cc..537efaf 100644 --- a/src/main/java/react/RCollection.java +++ b/src/main/java/react/RCollection.java @@ -10,51 +10,43 @@ */ public abstract class RCollection extends Reactor { - /** - * Returns the number of elements in this collection. - */ - public abstract int size (); - - /** - * Exposes the size of this collection as a value. - */ - public synchronized ValueView sizeView () { - if (_sizeView == null) { - _sizeView = Value.create(size()); - } - return _sizeView; - } - - /** - * Returns a reactive value which is true when this collection is empty, false otherwise. - */ - public ValueView isEmptyView () { - return sizeView().map(s -> s == 0); - } + /** Returns the number of elements in this collection. */ + public abstract int size (); - /** - * Returns a reactive value which is false when this collection is empty, true otherwise. - */ - public ValueView isNonEmptyView () { - return sizeView().map(s -> s > 0); + /** Exposes the size of this collection as a value. */ + public synchronized ValueView sizeView () { + if (_sizeView == null) { + _sizeView = Value.create(size()); } - - /** - * Updates the reactive size value. The underlying collection need only call this method if it - * changes the size of its collection without also calling {@link #notify}. - */ - protected void updateSize () { - if (_sizeView != null) _sizeView.update(size()); - } - - @Override protected void notify (Notifier notifier, Object a1, Object a2, Object a3) { - try { - super.notify(notifier, a1, a2, a3); - } finally { - updateSize(); - } + return _sizeView; + } + + /** Returns a reactive value which is true when this collection is empty, false otherwise. */ + public ValueView isEmptyView () { + return sizeView().map(s -> s == 0); + } + + /** Returns a reactive value which is false when this collection is empty, true otherwise. */ + public ValueView isNonEmptyView () { + return sizeView().map(s -> s > 0); + } + + /** + * Updates the reactive size value. The underlying collection need only call this method if it + * changes the size of its collection without also calling {@link #notify}. + */ + protected void updateSize () { + if (_sizeView != null) _sizeView.update(size()); + } + + @Override protected void notify (Notifier notifier, Object a1, Object a2, Object a3) { + try { + super.notify(notifier, a1, a2, a3); + } finally { + updateSize(); } + } - /** Used to expose the size of this set as a value. Initialized lazily. */ - private Value _sizeView; + /** Used to expose the size of this set as a value. Initialized lazily. */ + private Value _sizeView; } diff --git a/src/main/java/react/RFuture.java b/src/main/java/react/RFuture.java index 770dda7..4f414a2 100644 --- a/src/main/java/react/RFuture.java +++ b/src/main/java/react/RFuture.java @@ -27,303 +27,303 @@ */ public abstract class RFuture extends Reactor { - /** Used by {@link #sequence(RFuture,RFuture)}. */ - public static class T2 { - public final A a; - public final B b; - public T2 (A a, B b) { - this.a = a; - this.b = b; - } - - @Override public int hashCode () { - return Objects.hashCode(a) ^ Objects.hashCode(b); - } - @Override public boolean equals (Object other) { - if (!(other instanceof T2)) return false; - T2 ot = (T2)other; - return Objects.equals(a, ot.a) && Objects.equals(a, ot.b); - } + /** Used by {@link #sequence(RFuture,RFuture)}. */ + public static class T2 { + public final A a; + public final B b; + public T2 (A a, B b) { + this.a = a; + this.b = b; } - /** Used by {@link #sequence(RFuture,RFuture,RFuture)}. */ - public static class T3 { - public final A a; - public final B b; - public final C c; - public T3 (A a, B b, C c) { - this.a = a; - this.b = b; - this.c = c; - } - - @Override public int hashCode () { - return Objects.hashCode(a) ^ Objects.hashCode(b); - } - @Override public boolean equals (Object other) { - if (!(other instanceof T3)) return false; - T3 ot = (T3)other; - return Objects.equals(a, ot.a) && Objects.equals(a, ot.b) && Objects.equals(c, ot.c); - } + @Override public int hashCode () { + return Objects.hashCode(a) ^ Objects.hashCode(b); } - - /** Returns a future with a pre-existing success value. */ - public static RFuture success (T value) { - return result(Try.success(value)); + @Override public boolean equals (Object other) { + if (!(other instanceof T2)) return false; + T2 ot = (T2)other; + return Objects.equals(a, ot.a) && Objects.equals(a, ot.b); } + } - /** Returns a future result for a {@code Void} method. */ - public static RFuture success () { - return success(null); + /** Used by {@link #sequence(RFuture,RFuture,RFuture)}. */ + public static class T3 { + public final A a; + public final B b; + public final C c; + public T3 (A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; } - /** Returns a future with a pre-existing failure value. */ - public static RFuture failure (Throwable cause) { - return result(Try.failure(cause)); + @Override public int hashCode () { + return Objects.hashCode(a) ^ Objects.hashCode(b); } - - /** Returns a future with an already-computed result. */ - public static RFuture result (Try result) { - return new RFuture() { - public Try result () { return result; } - }; + @Override public boolean equals (Object other) { + if (!(other instanceof T3)) return false; + T3 ot = (T3)other; + return Objects.equals(a, ot.a) && Objects.equals(a, ot.b) && Objects.equals(c, ot.c); } + } + + /** Returns a future with a pre-existing success value. */ + public static RFuture success (T value) { + return result(Try.success(value)); + } + + /** Returns a future result for a {@code Void} method. */ + public static RFuture success () { + return success(null); + } - /** Returns a future containing a list of all success results from {@code futures} if all of - * the futures complete successfully, or a {@link MultiFailureException} aggregating all - * failures, if any of the futures fails. - * - *

If {@code futures} is an ordered collection, the resulting list will match the order of - * the futures. If not, result list is in {@code futures}' iteration order.

*/ - public static RFuture> sequence (Collection> futures) { - // if we're passed an empty list of futures, succeed immediately with an empty list - if (futures.isEmpty()) return RFuture.success(Collections.emptyList()); + /** Returns a future with a pre-existing failure value. */ + public static RFuture failure (Throwable cause) { + return result(Try.failure(cause)); + } - RPromise> pseq = RPromise.create(); - int count = futures.size(); - class Sequencer { - public synchronized void onResult (int idx, Try result) { - if (result.isSuccess()) { - _results[idx] = result.get(); - } else { - if (_error == null) _error = new MultiFailureException(); - _error.addFailure(result.getFailure()); - } - if (--_remain == 0) { - if (_error != null) pseq.fail(_error); - else { - @SuppressWarnings("unchecked") T[] results = (T[])_results; - pseq.succeed(Arrays.asList(results)); - } - } - } - protected final Object[] _results = new Object[count]; - protected int _remain = count; - protected MultiFailureException _error; + /** Returns a future with an already-computed result. */ + public static RFuture result (Try result) { + return new RFuture() { + public Try result () { return result; } + }; + } + + /** Returns a future containing a list of all success results from {@code futures} if all of the + * futures complete successfully, or a {@link MultiFailureException} aggregating all failures, + * if any of the futures fails. + * + *

If {@code futures} is an ordered collection, the resulting list will match the order of + * the futures. If not, result list is in {@code futures}' iteration order.

*/ + public static RFuture> sequence (Collection> futures) { + // if we're passed an empty list of futures, succeed immediately with an empty list + if (futures.isEmpty()) return RFuture.success(Collections.emptyList()); + + RPromise> pseq = RPromise.create(); + int count = futures.size(); + class Sequencer { + public synchronized void onResult (int idx, Try result) { + if (result.isSuccess()) { + _results[idx] = result.get(); + } else { + if (_error == null) _error = new MultiFailureException(); + _error.addFailure(result.getFailure()); } - Sequencer seq = new Sequencer(); - Iterator> iter = futures.iterator(); - for (int ii = 0; iter.hasNext(); ii++) { - int idx = ii; - iter.next().onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { seq.onResult(idx, result); } - }); + if (--_remain == 0) { + if (_error != null) pseq.fail(_error); + else { + @SuppressWarnings("unchecked") T[] results = (T[])_results; + pseq.succeed(Arrays.asList(results)); + } } - return pseq; + } + protected final Object[] _results = new Object[count]; + protected int _remain = count; + protected MultiFailureException _error; } - - /** Returns a future containing the results of {@code a} and {@code b} if both futures complete - * successfully, or a {@link MultiFailureException} aggregating all failures, if either of the - * futures fails. */ - public static RFuture> sequence (RFuture a, RFuture b) { - @SuppressWarnings("unchecked") RFuture oa = (RFuture)a; - @SuppressWarnings("unchecked") RFuture ob = (RFuture)b; - return sequence(Arrays.asList(oa, ob)).map(new Function,T2>() { - public T2 apply (List results) { - @SuppressWarnings("unchecked") A a = (A)results.get(0); - @SuppressWarnings("unchecked") B b = (B)results.get(1); - return new T2(a, b); - } - }); + Sequencer seq = new Sequencer(); + Iterator> iter = futures.iterator(); + for (int ii = 0; iter.hasNext(); ii++) { + int idx = ii; + iter.next().onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { seq.onResult(idx, result); } + }); } + return pseq; + } - /** Returns a future containing the results of {@code a}, {@code b}, and {@code c} if all - * futures complete successfully, or a {@link MultiFailureException} aggregating all failures, - * if any of the futures fails. */ - public static RFuture> sequence (RFuture a, RFuture b, RFuture c) { - @SuppressWarnings("unchecked") RFuture oa = (RFuture)a; - @SuppressWarnings("unchecked") RFuture ob = (RFuture)b; - @SuppressWarnings("unchecked") RFuture oc = (RFuture)c; - return sequence(Arrays.asList(oa, ob, oc)).map(new Function,T3>() { - public T3 apply (List results) { - @SuppressWarnings("unchecked") A a = (A)results.get(0); - @SuppressWarnings("unchecked") B b = (B)results.get(1); - @SuppressWarnings("unchecked") C c = (C)results.get(2); - return new T3(a, b, c); - } - }); - } + /** Returns a future containing the results of {@code a} and {@code b} if both futures complete + * successfully, or a {@link MultiFailureException} aggregating all failures, if either of the + * futures fails. */ + public static RFuture> sequence (RFuture a, RFuture b) { + @SuppressWarnings("unchecked") RFuture oa = (RFuture)a; + @SuppressWarnings("unchecked") RFuture ob = (RFuture)b; + return sequence(Arrays.asList(oa, ob)).map(new Function,T2>() { + public T2 apply (List results) { + @SuppressWarnings("unchecked") A a = (A)results.get(0); + @SuppressWarnings("unchecked") B b = (B)results.get(1); + return new T2(a, b); + } + }); + } - /** Returns a future containing a list of all success results from {@code futures}. Any failure - * results are simply omitted from the list. The success results are also in no particular - * order. If all of {@code futures} fail, the resulting list will be empty. */ - public static RFuture> collect (Collection> futures) { - // if we're passed an empty list of futures, succeed immediately with an empty list - if (futures.isEmpty()) return RFuture.>success(Collections.emptyList()); + /** Returns a future containing the results of {@code a}, {@code b}, and {@code c} if all + * futures complete successfully, or a {@link MultiFailureException} aggregating all failures, + * if any of the futures fails. */ + public static RFuture> sequence (RFuture a, RFuture b, RFuture c) { + @SuppressWarnings("unchecked") RFuture oa = (RFuture)a; + @SuppressWarnings("unchecked") RFuture ob = (RFuture)b; + @SuppressWarnings("unchecked") RFuture oc = (RFuture)c; + return sequence(Arrays.asList(oa, ob, oc)).map(new Function,T3>() { + public T3 apply (List results) { + @SuppressWarnings("unchecked") A a = (A)results.get(0); + @SuppressWarnings("unchecked") B b = (B)results.get(1); + @SuppressWarnings("unchecked") C c = (C)results.get(2); + return new T3(a, b, c); + } + }); + } - RPromise> pseq = RPromise.create(); - int count = futures.size(); - SignalView.Listener> collector = new SignalView.Listener>() { - public synchronized void onEmit (Try result) { - if (result.isSuccess()) _results.add(result.get()); - if (--_remain == 0) pseq.succeed(_results); - } - protected final List _results = new ArrayList(); - protected int _remain = count; - }; - for (RFuture future : futures) future.onComplete(collector); - return pseq; - } + /** Returns a future containing a list of all success results from {@code futures}. Any failure + * results are simply omitted from the list. The success results are also in no particular + * order. If all of {@code futures} fail, the resulting list will be empty. */ + public static RFuture> collect (Collection> futures) { + // if we're passed an empty list of futures, succeed immediately with an empty list + if (futures.isEmpty()) return RFuture.>success(Collections.emptyList()); - /** Causes {@code slot} to be notified if/when this future is completed with success. If it has - * already succeeded, the slot will be notified immediately. - * @return this future for chaining. */ - public RFuture onSuccess (SignalView.Listener slot) { - return onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { - if (result.isSuccess()) slot.onEmit(result.get()); - } - }); - } + RPromise> pseq = RPromise.create(); + int count = futures.size(); + SignalView.Listener> collector = new SignalView.Listener>() { + public synchronized void onEmit (Try result) { + if (result.isSuccess()) _results.add(result.get()); + if (--_remain == 0) pseq.succeed(_results); + } + protected final List _results = new ArrayList(); + protected int _remain = count; + }; + for (RFuture future : futures) future.onComplete(collector); + return pseq; + } - /** Causes {@code slot} to be notified if/when this future is completed with failure. If it has - * already failed, the slot will be notified immediately. - * @return this future for chaining. */ - public RFuture onFailure (SignalView.Listener slot) { - return onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { - if (result.isFailure()) slot.onEmit(result.getFailure()); - } - }); - } + /** Causes {@code slot} to be notified if/when this future is completed with success. If it has + * already succeeded, the slot will be notified immediately. + * @return this future for chaining. */ + public RFuture onSuccess (SignalView.Listener slot) { + return onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { + if (result.isSuccess()) slot.onEmit(result.get()); + } + }); + } - /** Causes {@code slot} to be notified when this future is completed. If it has already - * completed, the slot will be notified immediately. - * @return this future for chaining. */ - public RFuture onComplete (SignalView.Listener> slot) { - Try result = result(); - if (result != null) slot.onEmit(result); - else addConnection(slot); - return this; - } + /** Causes {@code slot} to be notified if/when this future is completed with failure. If it has + * already failed, the slot will be notified immediately. + * @return this future for chaining. */ + public RFuture onFailure (SignalView.Listener slot) { + return onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { + if (result.isFailure()) slot.onEmit(result.getFailure()); + } + }); + } + + /** Causes {@code slot} to be notified when this future is completed. If it has already + * completed, the slot will be notified immediately. + * @return this future for chaining. */ + public RFuture onComplete (SignalView.Listener> slot) { + Try result = result(); + if (result != null) slot.onEmit(result); + else addConnection(slot); + return this; + } - /** Returns a value that indicates whether this future has completed. */ - public ValueView isComplete () { - if (_isCompleteView == null) { - Value isCompleteView = Value.create(false); - onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { - isCompleteView.update(true); - } - }); - _isCompleteView = isCompleteView; + /** Returns a value that indicates whether this future has completed. */ + public ValueView isComplete () { + if (_isCompleteView == null) { + Value isCompleteView = Value.create(false); + onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { + isCompleteView.update(true); } - return _isCompleteView; + }); + _isCompleteView = isCompleteView; } + return _isCompleteView; + } - /** Returns whether this future is complete right now. This is an unfortunate name, but I - * foolishly defined {@link #isComplete} to return a reactive view of completeness. */ - public boolean isCompleteNow () { - return result() != null; - } + /** Returns whether this future is complete right now. This is an unfortunate name, but I + * foolishly defined {@link #isComplete} to return a reactive view of completeness. */ + public boolean isCompleteNow () { + return result() != null; + } - /** Convenience method to {@link ValueView#connectNotify} {@code slot} to {@link #isComplete}. - * This is useful for binding the disabled state of UI elements to this future's completeness - * (i.e. disabled while the future is incomplete, then reenabled when it is completed). - * @return this future for chaining. */ - public RFuture bindComplete (ValueView.Listener slot) { - isComplete().connectNotify(slot); - return this; - } + /** Convenience method to {@link ValueView#connectNotify} {@code slot} to {@link #isComplete}. + * This is useful for binding the disabled state of UI elements to this future's completeness + * (i.e. disabled while the future is incomplete, then reenabled when it is completed). + * @return this future for chaining. */ + public RFuture bindComplete (ValueView.Listener slot) { + isComplete().connectNotify(slot); + return this; + } - /** Transforms this future by mapping its result upon arrival. */ - public RFuture transform (Function,Try> func) { - RPromise xf = RPromise.create(); - onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { - Try xfResult; - try { - xfResult = func.apply(result); - } catch (Throwable t) { - xf.fail(t); - return; - } - xf.complete(xfResult); - } - }); - return xf; - } + /** Transforms this future by mapping its result upon arrival. */ + public RFuture transform (Function,Try> func) { + RPromise xf = RPromise.create(); + onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { + Try xfResult; + try { + xfResult = func.apply(result); + } catch (Throwable t) { + xf.fail(t); + return; + } + xf.complete(xfResult); + } + }); + return xf; + } - /** Maps the value of a successful result using {@code func} upon arrival. */ - public RFuture map (Function func) { - Object sigh = Try.lift(func); - @SuppressWarnings("unchecked") Function,Try> lifted = - (Function,Try>)sigh; - return transform(lifted); - } + /** Maps the value of a successful result using {@code func} upon arrival. */ + public RFuture map (Function func) { + Object sigh = Try.lift(func); + @SuppressWarnings("unchecked") Function,Try> lifted = + (Function,Try>)sigh; + return transform(lifted); + } - /** Maps the value of a failed result using {@code func} upon arrival. Ideally one could - * generalize the type {@code T} here but Java doesn't allow type parameters with lower - * bounds. */ - public RFuture recover (Function func) { - Object sigh = new Function,Try>() { - public Try apply (Try result) { - return result.recover(func); - } - }; - @SuppressWarnings("unchecked") Function,Try> lifted = - (Function,Try>)sigh; - return transform(lifted); - } + /** Maps the value of a failed result using {@code func} upon arrival. Ideally one could + * generalize the type {@code T} here but Java doesn't allow type parameters with lower + * bounds. */ + public RFuture recover (Function func) { + Object sigh = new Function,Try>() { + public Try apply (Try result) { + return result.recover(func); + } + }; + @SuppressWarnings("unchecked") Function,Try> lifted = + (Function,Try>)sigh; + return transform(lifted); + } - /** Maps a successful result to a new result using {@code func} when it arrives. Failure on the - * original result or the mapped result are both dispatched to the mapped result. This is - * useful for chaining asynchronous actions. It's also known as monadic bind. */ - public RFuture flatMap (Function> func) { - RPromise mapped = RPromise.create(); - onComplete(new SignalView.Listener>() { - public void onEmit (Try result) { - if (result.isFailure()) mapped.fail(result.getFailure()); - else { - RFuture mappedResult; - try { - mappedResult = func.apply(result.get()); - } catch (Throwable t) { - mapped.fail(t); - return; - } - mappedResult.onComplete(mapped::complete); - } - } - }); - return mapped; - } + /** Maps a successful result to a new result using {@code func} when it arrives. Failure on the + * original result or the mapped result are both dispatched to the mapped result. This is + * useful for chaining asynchronous actions. It's also known as monadic bind. */ + public RFuture flatMap (Function> func) { + RPromise mapped = RPromise.create(); + onComplete(new SignalView.Listener>() { + public void onEmit (Try result) { + if (result.isFailure()) mapped.fail(result.getFailure()); + else { + RFuture mappedResult; + try { + mappedResult = func.apply(result.get()); + } catch (Throwable t) { + mapped.fail(t); + return; + } + mappedResult.onComplete(mapped::complete); + } + } + }); + return mapped; + } - /** Returns the result of this future, or null if it is not yet complete. - * - *

NOTE: don't use this method! You should wire up reactions to the completion of - * this future via {@link #onSuccess} or {@link #onFailure}. React is not a blocking async - * library where on might block a calling thread on the result of a future and then obtain the - * result synchronously. This is only appropriate when you're trying to abstract over - * synchronous and asynchronous variants of a computation, and you want to use the future - * machinery in both cases, but in the synchronous case you know that your future will be - * complete by the time you want to obtain its result. - */ - public abstract Try result (); + /** Returns the result of this future, or null if it is not yet complete. + * + *

NOTE: don't use this method! You should wire up reactions to the completion of + * this future via {@link #onSuccess} or {@link #onFailure}. React is not a blocking async + * library where on might block a calling thread on the result of a future and then obtain the + * result synchronously. This is only appropriate when you're trying to abstract over + * synchronous and asynchronous variants of a computation, and you want to use the future + * machinery in both cases, but in the synchronous case you know that your future will be + * complete by the time you want to obtain its result. + */ + public abstract Try result (); - @Override RListener placeholderListener () { - return Slot.NOOP; - } + @Override RListener placeholderListener () { + return Slot.NOOP; + } - private ValueView _isCompleteView; + private ValueView _isCompleteView; } diff --git a/src/main/java/react/RList.java b/src/main/java/react/RList.java index d152bb4..8d8a7fe 100644 --- a/src/main/java/react/RList.java +++ b/src/main/java/react/RList.java @@ -18,304 +18,304 @@ */ public class RList extends RCollection implements List { - /** Publishes list events to listeners. */ - public interface Listener extends Reactor.RListener { - - /** Notifies listener of an added element. This method will call the index-forgetting - * version ({@link #onAdd(Object)}) by default. */ - default void onAdd (int index, E elem) { onAdd(elem); } - - /** Notifies listener of an added element. */ - default void onAdd (E elem) {} // noop - - /** Notifies listener of an updated element. This method will call the old-value-forgetting - * version ({@link #onSet(int,Object)}) by default. */ - default void onSet (int index, E newElem, E oldElem) { onSet(index, newElem); } - - /** Notifies listener of an updated element. */ - default void onSet (int index, E newElem) {} // noop - - /** Notifies listener of a removed element. This method will call the index-forgetting - * version ({@link #onRemove(Object)}) by default. */ - default void onRemove (int index, E elem) { onRemove(elem); } - - /** Notifies listener of a removed element. */ - default void onRemove (E elem) {} // noop - } - - /** - * Creates a reactive list backed by an {@link ArrayList}. - */ - public static RList create () { - return create(new ArrayList()); - } - - /** - * Creates a reactive list with the supplied underlying list implementation. - */ - public static RList create (List impl) { - return new RList(impl); - } - - /** - * Creates a reactive list with the supplied underlying list implementation. - */ - public RList (List impl) { - _impl = impl; - } - - /** - * Connects the supplied listener to this list, such that it will be notified on adds and - * removes. - * @return a connection instance which can be used to cancel the connection. - */ - public Connection connect (Listener listener) { - return addConnection(listener); - } - - /** - * Invokes {@code onAdd(int,E)} for all existing list elements, then connects {@code listener}. - */ - public Connection connectNotify (Listener listener) { - for (int ii = 0, ll = size(); ii < ll; ii++) listener.onAdd(ii, get(ii)); - return connect(listener); - } - - /** - * Disconnects the supplied listener from this list if listen was called with it. - */ - public void disconnect (Listener listener) { - removeConnection(listener); - } - - /** - * Removes the supplied element from the list, forcing a notification to the listeners - * regardless of whether the element was in the list or not. - * @return true if the element was in the list and was removed, false if it was not. - */ - public boolean removeForce (E elem) { - checkMutate(); - int index = _impl.indexOf(elem); - if (index >= 0) _impl.remove(index); - emitRemove(index, elem); - return (index >= 0); - } - - // List methods that perform reactive functions in addition to calling through - @Override public boolean add (E element) { - add(size(), element); - return true; - } - - @Override public void add (int index, E element) { - checkMutate(); - _impl.add(index, element); - emitAdd(index, element); - } - - @Override public boolean addAll (Collection collection) { - return addAll(size(), collection); - } - - @Override public boolean addAll (int index, Collection elements) { + /** Publishes list events to listeners. */ + public interface Listener extends Reactor.RListener { + + /** Notifies listener of an added element. This method will call the index-forgetting + * version ({@link #onAdd(Object)}) by default. */ + default void onAdd (int index, E elem) { onAdd(elem); } + + /** Notifies listener of an added element. */ + default void onAdd (E elem) {} // noop + + /** Notifies listener of an updated element. This method will call the old-value-forgetting + * version ({@link #onSet(int,Object)}) by default. */ + default void onSet (int index, E newElem, E oldElem) { onSet(index, newElem); } + + /** Notifies listener of an updated element. */ + default void onSet (int index, E newElem) {} // noop + + /** Notifies listener of a removed element. This method will call the index-forgetting + * version ({@link #onRemove(Object)}) by default. */ + default void onRemove (int index, E elem) { onRemove(elem); } + + /** Notifies listener of a removed element. */ + default void onRemove (E elem) {} // noop + } + + /** + * Creates a reactive list backed by an {@link ArrayList}. + */ + public static RList create () { + return create(new ArrayList()); + } + + /** + * Creates a reactive list with the supplied underlying list implementation. + */ + public static RList create (List impl) { + return new RList(impl); + } + + /** + * Creates a reactive list with the supplied underlying list implementation. + */ + public RList (List impl) { + _impl = impl; + } + + /** + * Connects the supplied listener to this list, such that it will be notified on adds and + * removes. + * @return a connection instance which can be used to cancel the connection. + */ + public Connection connect (Listener listener) { + return addConnection(listener); + } + + /** + * Invokes {@code onAdd(int,E)} for all existing list elements, then connects {@code listener}. + */ + public Connection connectNotify (Listener listener) { + for (int ii = 0, ll = size(); ii < ll; ii++) listener.onAdd(ii, get(ii)); + return connect(listener); + } + + /** + * Disconnects the supplied listener from this list if listen was called with it. + */ + public void disconnect (Listener listener) { + removeConnection(listener); + } + + /** + * Removes the supplied element from the list, forcing a notification to the listeners + * regardless of whether the element was in the list or not. + * @return true if the element was in the list and was removed, false if it was not. + */ + public boolean removeForce (E elem) { + checkMutate(); + int index = _impl.indexOf(elem); + if (index >= 0) _impl.remove(index); + emitRemove(index, elem); + return (index >= 0); + } + + // List methods that perform reactive functions in addition to calling through + @Override public boolean add (E element) { + add(size(), element); + return true; + } + + @Override public void add (int index, E element) { + checkMutate(); + _impl.add(index, element); + emitAdd(index, element); + } + + @Override public boolean addAll (Collection collection) { + return addAll(size(), collection); + } + + @Override public boolean addAll (int index, Collection elements) { + checkMutate(); + // Call add instead of calling _impl.addAll so if a listener throws an exception on + // emission, we don't have elements added without a corresponding emission + for (E elem : elements) { + add(index++, elem); + } + return true; + } + + @Override public Iterator iterator () { + return listIterator(); + } + + @Override public ListIterator listIterator () { + return listIterator(0); + } + + @Override public ListIterator listIterator (int index) { + ListIterator iiter = _impl.listIterator(); + return new ListIterator () { + public void add (E elem) { checkMutate(); - // Call add instead of calling _impl.addAll so if a listener throws an exception on - // emission, we don't have elements added without a corresponding emission - for (E elem : elements) { - add(index++, elem); - } - return true; - } - - @Override public Iterator iterator () { - return listIterator(); - } - - @Override public ListIterator listIterator () { - return listIterator(0); - } - - @Override public ListIterator listIterator (int index) { - ListIterator iiter = _impl.listIterator(); - return new ListIterator () { - public void add (E elem) { - checkMutate(); - int index = iiter.nextIndex(); - iiter.add(elem); - emitAdd(index, elem); - } - public boolean hasNext () { - return iiter.hasNext(); - } - public boolean hasPrevious () { - return iiter.hasPrevious(); - } - public E next () { - return (_current = iiter.next()); - } - public int nextIndex () { - return iiter.nextIndex(); - } - public E previous () { - return (_current = iiter.previous()); - } - public int previousIndex () { - return iiter.previousIndex(); - } - public void remove () { - checkMutate(); - int index = iiter.previousIndex(); - iiter.remove(); - emitRemove(index, _current); - } - public void set (E elem) { - checkMutate(); - iiter.set(elem); - emitSet(iiter.previousIndex(), elem, _current); - _current = elem; - } - protected E _current; // the element targeted by remove or set - }; - } - - @Override public boolean retainAll (Collection collection) { - boolean modified = false; - for (Iterator iter = iterator(); iter.hasNext(); ) { - if (!collection.contains(iter.next())) { - iter.remove(); - modified = true; - } - } - return modified; - } - - @Override public boolean removeAll (Collection collection) { - boolean modified = false; - for (Object o : collection) modified |= remove(o); - return modified; - } - - @Override public boolean remove (Object object) { + int index = iiter.nextIndex(); + iiter.add(elem); + emitAdd(index, elem); + } + public boolean hasNext () { + return iiter.hasNext(); + } + public boolean hasPrevious () { + return iiter.hasPrevious(); + } + public E next () { + return (_current = iiter.next()); + } + public int nextIndex () { + return iiter.nextIndex(); + } + public E previous () { + return (_current = iiter.previous()); + } + public int previousIndex () { + return iiter.previousIndex(); + } + public void remove () { checkMutate(); - int index = _impl.indexOf(object); - if (index < 0) return false; - _impl.remove(index); - // the cast is safe if the element was removed - @SuppressWarnings("unchecked") E elem = (E)object; - emitRemove(index, elem); - return true; - } - - @Override public E remove (int index) { + int index = iiter.previousIndex(); + iiter.remove(); + emitRemove(index, _current); + } + public void set (E elem) { checkMutate(); - E removed = _impl.remove(index); - emitRemove(index, removed); - return removed; - } - - @Override public E set (int index, E element) { - checkMutate(); - E removed = _impl.set(index, element); - emitSet(index, element, removed); - return removed; - } - - @Override public List subList (int fromIndex, int toIndex) { - return new RList(_impl.subList(fromIndex, toIndex)); - } - - @Override public boolean equals (Object other) { - return other == this || _impl.equals(other); - } - - @Override public String toString () { - return "RList(" + _impl + ")"; - } - - // List methods that purely pass through to the underlying list - @Override public int hashCode () { - return _impl.hashCode(); - } - - @Override public int size () { - return _impl.size(); - } - - @Override public boolean isEmpty () { - return _impl.isEmpty(); - } - - @Override public E get (int index) { - return _impl.get(index); - } - - @Override public int indexOf (Object element) { - return _impl.indexOf(element); - } - - @Override public int lastIndexOf (Object element) { - return _impl.lastIndexOf(element); - } - - @Override public boolean contains (Object object) { - return _impl.contains(object); - } - - @Override public boolean containsAll (Collection collection) { - return _impl.containsAll(collection); - } - - @Override public void clear () { - // clear in such a way as to emit events - while (!isEmpty()) remove(0); - } - - @Override public Object[] toArray () { - return _impl.toArray(); - } - - @Override public T[] toArray (T[] array) { - return _impl.toArray(array); - } - - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; - return p; - } - - // Non-list RList implementation - protected void emitAdd (int index, E elem) { - notify(ADD, index, elem, null); + iiter.set(elem); + emitSet(iiter.previousIndex(), elem, _current); + _current = elem; + } + protected E _current; // the element targeted by remove or set + }; + } + + @Override public boolean retainAll (Collection collection) { + boolean modified = false; + for (Iterator iter = iterator(); iter.hasNext(); ) { + if (!collection.contains(iter.next())) { + iter.remove(); + modified = true; + } + } + return modified; + } + + @Override public boolean removeAll (Collection collection) { + boolean modified = false; + for (Object o : collection) modified |= remove(o); + return modified; + } + + @Override public boolean remove (Object object) { + checkMutate(); + int index = _impl.indexOf(object); + if (index < 0) return false; + _impl.remove(index); + // the cast is safe if the element was removed + @SuppressWarnings("unchecked") E elem = (E)object; + emitRemove(index, elem); + return true; + } + + @Override public E remove (int index) { + checkMutate(); + E removed = _impl.remove(index); + emitRemove(index, removed); + return removed; + } + + @Override public E set (int index, E element) { + checkMutate(); + E removed = _impl.set(index, element); + emitSet(index, element, removed); + return removed; + } + + @Override public List subList (int fromIndex, int toIndex) { + return new RList(_impl.subList(fromIndex, toIndex)); + } + + @Override public boolean equals (Object other) { + return other == this || _impl.equals(other); + } + + @Override public String toString () { + return "RList(" + _impl + ")"; + } + + // List methods that purely pass through to the underlying list + @Override public int hashCode () { + return _impl.hashCode(); + } + + @Override public int size () { + return _impl.size(); + } + + @Override public boolean isEmpty () { + return _impl.isEmpty(); + } + + @Override public E get (int index) { + return _impl.get(index); + } + + @Override public int indexOf (Object element) { + return _impl.indexOf(element); + } + + @Override public int lastIndexOf (Object element) { + return _impl.lastIndexOf(element); + } + + @Override public boolean contains (Object object) { + return _impl.contains(object); + } + + @Override public boolean containsAll (Collection collection) { + return _impl.containsAll(collection); + } + + @Override public void clear () { + // clear in such a way as to emit events + while (!isEmpty()) remove(0); + } + + @Override public Object[] toArray () { + return _impl.toArray(); + } + + @Override public T[] toArray (T[] array) { + return _impl.toArray(array); + } + + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; + return p; + } + + // Non-list RList implementation + protected void emitAdd (int index, E elem) { + notify(ADD, index, elem, null); + } + + protected void emitSet (int index, E newElem, E oldElem) { + notify(SET, index, newElem, oldElem); + } + + protected void emitRemove (int index, E elem) { + notify(REMOVE, index, elem, null); + } + + /** Contains our underlying elements. */ + protected List _impl; + + protected static final Listener NOOP = new Listener() {}; + + @SuppressWarnings("unchecked") protected static final Notifier ADD = new Notifier() { + public void notify (Object lner, Object index, Object elem, Object ignored) { + ((Listener)lner).onAdd((Integer)index, elem); } - - protected void emitSet (int index, E newElem, E oldElem) { - notify(SET, index, newElem, oldElem); + }; + + @SuppressWarnings("unchecked") protected static final Notifier SET = new Notifier() { + public void notify (Object lner, Object index, Object newElem, Object oldElem) { + ((Listener)lner).onSet((Integer)index, newElem, oldElem); } + }; - protected void emitRemove (int index, E elem) { - notify(REMOVE, index, elem, null); + @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { + public void notify (Object lner, Object index, Object elem, Object ignored) { + ((Listener)lner).onRemove((Integer)index, elem); } - - /** Contains our underlying elements. */ - protected List _impl; - - protected static final Listener NOOP = new Listener() {}; - - @SuppressWarnings("unchecked") protected static final Notifier ADD = new Notifier() { - public void notify (Object lner, Object index, Object elem, Object ignored) { - ((Listener)lner).onAdd((Integer)index, elem); - } - }; - - @SuppressWarnings("unchecked") protected static final Notifier SET = new Notifier() { - public void notify (Object lner, Object index, Object newElem, Object oldElem) { - ((Listener)lner).onSet((Integer)index, newElem, oldElem); - } - }; - - @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { - public void notify (Object lner, Object index, Object elem, Object ignored) { - ((Listener)lner).onRemove((Integer)index, elem); - } - }; + }; } diff --git a/src/main/java/react/RMap.java b/src/main/java/react/RMap.java index 6326b0e..795417c 100644 --- a/src/main/java/react/RMap.java +++ b/src/main/java/react/RMap.java @@ -21,423 +21,418 @@ * #remove} will only generate a notification if a mapping for the specified key existed, use * {@link #removeForce} to force a notification. */ -public class RMap extends RCollection> implements Map -{ - /** An interface for publishing map events to listeners. */ - public interface Listener extends Reactor.RListener - { - /** - * Notifies listener of an added or updated mapping. This method will call the - * old-value-forgetting version ({@link #onPut(Object,Object)}) by default. - */ - default void onPut (K key, V value, V oldValue) { onPut(key, value); } - - /** Notifies listener of an added or updated mapping. */ - default void onPut (K key, V value) {} // noop - - /** - * Notifies listener of a removed mapping. This method will call the old-value-forgetting - * version ({@link #onRemove(Object)}) by default. - */ - default void onRemove (K key, V oldValue) { onRemove(key); } - - /** Notifies listener of a removed mapping. */ - default void onRemove (K key) {} // noop - } - - /** - * Creates a reactive map that uses a {@link HashMap} as its underlying implementation. - */ - public static RMap create () { - return create(new HashMap()); - } - - /** - * Creates a reactive map with the supplied underlying map implementation. - */ - public static RMap create (Map impl) { - return new RMap(impl); - } - - /** - * Creates a reactive map with the supplied underlying map implementation. - */ - public RMap (Map impl) { - _impl = impl; - } - - /** - * Connects the supplied listener to this map, such that it will be notified on puts and - * removes. - * @return a connection instance which can be used to cancel the connection. - */ - public Connection connect (Listener listener) { - return addConnection(listener); - } - - /** - * Invokes {@code onPut} for all existing entries and then connects {@code listener}. Note that - * the previous value supplied to the {@code onPut} calls will be null. - */ - public Connection connectNotify (Listener listener) { - for (Map.Entry entry : entrySet()) { - listener.onPut(entry.getKey(), entry.getValue(), null); - } - return connect(listener); - } - - /** - * Disconnects the supplied listener from this map if listen was called with it. - */ - public void disconnect (Listener listener) { - removeConnection(listener); - } - - /** - * Returns the mapping for {@code key} or {@code defaultValue} if there is no mapping for - * {@code key}. NOTE: this method assumes the map does not contain a mapping to {@code - * null}. A mapping to {@code null} will be treated as if the mapping does not exist. - */ - public V getOrElse (K key, V defaultValue) { - V value = _impl.get(key); - return (value == null) ? defaultValue : value; - } - - /** - * Updates the mapping with the supplied key and value, and notifies registered listeners - * regardless of whether the new value is equal to the old value. - * @return the previous value mapped to the supplied key, or null. - */ - public V putForce (K key, V value) { - checkMutate(); - V ovalue = _impl.put(key, value); - emitPut(key, value, ovalue); - return ovalue; - } - - /** - * Removes the mapping associated with the supplied key, and notifies registered listeners - * regardless of whether a previous mapping existed or not. - * @return the previous value mapped to the supplied key, or null. - */ - public V removeForce (K key) { - checkMutate(); - V ovalue = _impl.remove(key); - emitRemove(key, ovalue); - return ovalue; - } - - /** - * Returns a value view that models whether the specified key is contained in this map. The - * view will report a change when a mapping for the specified key is added or removed. Note: - * this view only works on maps that do not contain mappings to {@code null}. The view - * will retain a connection to this map for as long as it has connections of its own. - */ - public ValueView containsKeyView (K key) { - if (key == null) throw new NullPointerException("Must supply non-null 'key'."); - return new MappedValue() { - @Override public Boolean get () { - return containsKey(key); - } - @Override protected Connection connect () { - return RMap.this.connect(new RMap.Listener() { - @Override public void onPut (K pkey, V value, V ovalue) { - if (key.equals(pkey) && ovalue == null) notifyChange(true, false); - } - @Override public void onRemove (K rkey, V ovalue) { - if (key.equals(rkey)) notifyChange(false, true); - } - }); - } - }; - } - - /** - * Returns a value view that models the mapping of the specified key in this map. The view will - * report a change when the mapping for the specified key is changed or removed. The view will - * retain a connection to this map for as long as it has connections of its own. - */ - public ValueView getView (K key) { - if (key == null) throw new NullPointerException("Must supply non-null 'key'."); - return new MappedValue() { - @Override public V get () { - return RMap.this.get(key); - } - @Override protected Connection connect () { - return RMap.this.connect(new RMap.Listener() { - @Override public void onPut (K pkey, V value, V ovalue) { - if (key.equals(pkey)) notifyChange(value, ovalue); - } - @Override public void onRemove (K pkey, V ovalue) { - if (key.equals(pkey)) notifyChange(null, ovalue); - } - }); - } +public class RMap extends RCollection> implements Map { + + /** An interface for publishing map events to listeners. */ + public interface Listener extends Reactor.RListener { + /** Notifies listener of an added or updated mapping. */ + default void onPut (K key, V value) {} // noop + + /** Notifies listener of an added or updated mapping. This method will call the + * old-value-forgetting version ({@link #onPut(Object,Object)}) by default. */ + default void onPut (K key, V value, V oldValue) { onPut(key, value); } + + /** Notifies listener of a removed mapping. */ + default void onRemove (K key) {} // noop + + /** Notifies listener of a removed mapping. This method will call the old-value-forgetting + * version ({@link #onRemove(Object)}) by default. */ + default void onRemove (K key, V oldValue) { onRemove(key); } + } + + /** + * Creates a reactive map that uses a {@link HashMap} as its underlying implementation. + */ + public static RMap create () { + return create(new HashMap()); + } + + /** + * Creates a reactive map with the supplied underlying map implementation. + */ + public static RMap create (Map impl) { + return new RMap(impl); + } + + /** + * Creates a reactive map with the supplied underlying map implementation. + */ + public RMap (Map impl) { + _impl = impl; + } + + /** + * Connects the supplied listener to this map, such that it will be notified on puts and + * removes. + * @return a connection instance which can be used to cancel the connection. + */ + public Connection connect (Listener listener) { + return addConnection(listener); + } + + /** + * Invokes {@code onPut} for all existing entries and then connects {@code listener}. Note that + * the previous value supplied to the {@code onPut} calls will be null. + */ + public Connection connectNotify (Listener listener) { + for (Map.Entry entry : entrySet()) { + listener.onPut(entry.getKey(), entry.getValue(), null); + } + return connect(listener); + } + + /** + * Disconnects the supplied listener from this map if listen was called with it. + */ + public void disconnect (Listener listener) { + removeConnection(listener); + } + + /** + * Returns the mapping for {@code key} or {@code defaultValue} if there is no mapping for + * {@code key}. NOTE: this method assumes the map does not contain a mapping to {@code + * null}. A mapping to {@code null} will be treated as if the mapping does not exist. + */ + public V getOrElse (K key, V defaultValue) { + V value = _impl.get(key); + return (value == null) ? defaultValue : value; + } + + /** + * Updates the mapping with the supplied key and value, and notifies registered listeners + * regardless of whether the new value is equal to the old value. + * @return the previous value mapped to the supplied key, or null. + */ + public V putForce (K key, V value) { + checkMutate(); + V ovalue = _impl.put(key, value); + emitPut(key, value, ovalue); + return ovalue; + } + + /** + * Removes the mapping associated with the supplied key, and notifies registered listeners + * regardless of whether a previous mapping existed or not. + * @return the previous value mapped to the supplied key, or null. + */ + public V removeForce (K key) { + checkMutate(); + V ovalue = _impl.remove(key); + emitRemove(key, ovalue); + return ovalue; + } + + /** + * Returns a value view that models whether the specified key is contained in this map. The + * view will report a change when a mapping for the specified key is added or removed. Note: + * this view only works on maps that do not contain mappings to {@code null}. The view + * will retain a connection to this map for as long as it has connections of its own. + */ + public ValueView containsKeyView (K key) { + if (key == null) throw new NullPointerException("Must supply non-null 'key'."); + return new MappedValue() { + @Override public Boolean get () { + return containsKey(key); + } + @Override protected Connection connect () { + return RMap.this.connect(new RMap.Listener() { + @Override public void onPut (K pkey, V value, V ovalue) { + if (key.equals(pkey) && ovalue == null) notifyChange(true, false); + } + @Override public void onRemove (K rkey, V ovalue) { + if (key.equals(rkey)) notifyChange(false, true); + } + }); + } + }; + } + + /** + * Returns a value view that models the mapping of the specified key in this map. The view will + * report a change when the mapping for the specified key is changed or removed. The view will + * retain a connection to this map for as long as it has connections of its own. + */ + public ValueView getView (K key) { + if (key == null) throw new NullPointerException("Must supply non-null 'key'."); + return new MappedValue() { + @Override public V get () { + return RMap.this.get(key); + } + @Override protected Connection connect () { + return RMap.this.connect(new RMap.Listener() { + @Override public void onPut (K pkey, V value, V ovalue) { + if (key.equals(pkey)) notifyChange(value, ovalue); + } + @Override public void onRemove (K pkey, V ovalue) { + if (key.equals(pkey)) notifyChange(null, ovalue); + } + }); + } + }; + } + + // from interface Map + public int size () { + return _impl.size(); + } + + // from interface Map + public boolean isEmpty () { + return _impl.isEmpty(); + } + + // from interface Map + public boolean containsKey (Object key) { + return _impl.containsKey(key); + } + + // from interface Map + public boolean containsValue (Object value) { + return _impl.containsValue(value); + } + + @Override public int hashCode () { + return _impl.hashCode(); + } + + @Override public boolean equals (Object other) { + return other == this || _impl.equals(other); + } + + @Override public String toString () { + return "RMap" + _impl; + } + + // from interface Map + public V get (Object key) { + return _impl.get(key); + } + + // from interface Map + public V put (K key, V value) { + checkMutate(); + V ovalue = _impl.put(key, value); + if (!areEqual(value, ovalue)) { + emitPut(key, value, ovalue); + } + return ovalue; + } + + // from interface Map + public V remove (Object rawKey) { + checkMutate(); + + // avoid generating an event if no mapping exists for the supplied key + if (!_impl.containsKey(rawKey)) { + return null; + } + + @SuppressWarnings("unchecked") K key = (K)rawKey; + V ovalue = _impl.remove(key); + emitRemove(key, ovalue); + + return ovalue; + } + + // from interface Map + public void putAll (Map map) { + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + // from interface Map + public void clear () { + checkMutate(); + // generate removed events for our keys (do so on a copy of our set so that we can clear + // our underlying map before any of the published events are processed) + Set> entries = new HashSet>(_impl.entrySet()); + _impl.clear(); + for (Map.Entry entry : entries) emitRemove(entry.getKey(), entry.getValue()); + } + + // from interface Map + public Set keySet () { + Set iset = _impl.keySet(); + return new AbstractSet() { + public Iterator iterator () { + final Iterator iiter = iset.iterator(); + return new Iterator() { + public boolean hasNext () { + return iiter.hasNext(); + } + public K next () { + return (_current = iiter.next()); + } + public void remove () { + checkMutate(); + if (_current == null) throw new IllegalStateException(); + V ovalue = RMap.this.get(_current); + iiter.remove(); + emitRemove(_current, ovalue); + _current = null; + } + protected K _current; }; - } - - // from interface Map - public int size () { - return _impl.size(); - } - - // from interface Map - public boolean isEmpty () { - return _impl.isEmpty(); - } - - // from interface Map - public boolean containsKey (Object key) { - return _impl.containsKey(key); - } - - // from interface Map - public boolean containsValue (Object value) { - return _impl.containsValue(value); - } - - @Override public int hashCode () { - return _impl.hashCode(); - } - - @Override public boolean equals (Object other) { - return other == this || _impl.equals(other); - } - - @Override public String toString () { - return "RMap" + _impl; - } - - // from interface Map - public V get (Object key) { - return _impl.get(key); - } - - // from interface Map - public V put (K key, V value) { + } + public int size () { + return RMap.this.size(); + } + public boolean remove (Object o) { checkMutate(); - V ovalue = _impl.put(key, value); - if (!areEqual(value, ovalue)) { - emitPut(key, value, ovalue); + V ovalue = RMap.this.get(o); + boolean modified = iset.remove(o); + if (modified) { + @SuppressWarnings("unchecked") K key = (K)o; + emitRemove(key, ovalue); } - return ovalue; - } - - // from interface Map - public V remove (Object rawKey) { - checkMutate(); - - // avoid generating an event if no mapping exists for the supplied key - if (!_impl.containsKey(rawKey)) { - return null; - } - - @SuppressWarnings("unchecked") K key = (K)rawKey; - V ovalue = _impl.remove(key); - emitRemove(key, ovalue); - - return ovalue; - } - - // from interface Map - public void putAll (Map map) { - for (Map.Entry entry : map.entrySet()) { - put(entry.getKey(), entry.getValue()); - } - } - - // from interface Map - public void clear () { - checkMutate(); - // generate removed events for our keys (do so on a copy of our set so that we can clear - // our underlying map before any of the published events are processed) - Set> entries = new HashSet>(_impl.entrySet()); - _impl.clear(); - for (Map.Entry entry : entries) emitRemove(entry.getKey(), entry.getValue()); - } - - // from interface Map - public Set keySet () { - Set iset = _impl.keySet(); - return new AbstractSet() { - public Iterator iterator () { - final Iterator iiter = iset.iterator(); - return new Iterator() { - public boolean hasNext () { - return iiter.hasNext(); - } - public K next () { - return (_current = iiter.next()); - } - public void remove () { - checkMutate(); - if (_current == null) throw new IllegalStateException(); - V ovalue = RMap.this.get(_current); - iiter.remove(); - emitRemove(_current, ovalue); - _current = null; - } - protected K _current; - }; - } - public int size () { - return RMap.this.size(); - } - public boolean remove (Object o) { - checkMutate(); - V ovalue = RMap.this.get(o); - boolean modified = iset.remove(o); - if (modified) { - @SuppressWarnings("unchecked") K key = (K)o; - emitRemove(key, ovalue); - } - return modified; - } - public void clear () { - RMap.this.clear(); - } - }; - } - - // from interface Map - public Collection values () { - Collection> iset = _impl.entrySet(); - return new AbstractCollection() { - public Iterator iterator () { - final Iterator> iiter = iset.iterator(); - return new Iterator() { - public boolean hasNext () { - return iiter.hasNext(); - } - public V next () { - return (_current = iiter.next()).getValue(); - } - public void remove () { - checkMutate(); - iiter.remove(); - emitRemove(_current.getKey(), _current.getValue()); - _current = null; - } - protected Map.Entry _current; - }; - } - public int size () { - return RMap.this.size(); - } - public boolean contains (Object o) { - return RMap.this.containsValue(o); - } - public void clear () { - RMap.this.clear(); - } + return modified; + } + public void clear () { + RMap.this.clear(); + } + }; + } + + // from interface Map + public Collection values () { + Collection> iset = _impl.entrySet(); + return new AbstractCollection() { + public Iterator iterator () { + final Iterator> iiter = iset.iterator(); + return new Iterator() { + public boolean hasNext () { + return iiter.hasNext(); + } + public V next () { + return (_current = iiter.next()).getValue(); + } + public void remove () { + checkMutate(); + iiter.remove(); + emitRemove(_current.getKey(), _current.getValue()); + _current = null; + } + protected Map.Entry _current; }; - } - - // from interface Map - public Set> entrySet () { - Set> iset = _impl.entrySet(); - return new AbstractSet>() { - public Iterator> iterator () { - final Iterator> iiter = iset.iterator(); - return new Iterator>() { - public boolean hasNext () { - return iiter.hasNext(); - } - public Map.Entry next () { - _current = iiter.next(); - return new Map.Entry() { - public K getKey () { - return _ientry.getKey(); - } - public V getValue () { - return _ientry.getValue(); - } - public V setValue (V value) { - checkMutate(); - if (!iset.contains(this)) throw new IllegalStateException( - "Cannot update removed map entry."); - V ovalue = _ientry.setValue(value); - if (!areEqual(value, ovalue)) { - emitPut(_ientry.getKey(), value, ovalue); - } - return ovalue; - } - // it's safe to pass these through because Map.Entry's - // implementations operate solely on getKey/getValue - public boolean equals (Object o) { - return _ientry.equals(o); - } - public int hashCode () { - return _ientry.hashCode(); - } - protected Map.Entry _ientry = _current; - }; - } - public void remove () { - checkMutate(); - iiter.remove(); - emitRemove(_current.getKey(), _current.getValue()); - _current = null; - } - protected Map.Entry _current; - }; - } - public boolean contains (Object o) { - return iset.contains(o); - } - public boolean remove (Object o) { + } + public int size () { + return RMap.this.size(); + } + public boolean contains (Object o) { + return RMap.this.containsValue(o); + } + public void clear () { + RMap.this.clear(); + } + }; + } + + // from interface Map + public Set> entrySet () { + Set> iset = _impl.entrySet(); + return new AbstractSet>() { + public Iterator> iterator () { + final Iterator> iiter = iset.iterator(); + return new Iterator>() { + public boolean hasNext () { + return iiter.hasNext(); + } + public Map.Entry next () { + _current = iiter.next(); + return new Map.Entry() { + public K getKey () { + return _ientry.getKey(); + } + public V getValue () { + return _ientry.getValue(); + } + public V setValue (V value) { checkMutate(); - boolean modified = iset.remove(o); - if (modified) { - @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry)o; - emitRemove(entry.getKey(), entry.getValue()); + if (!iset.contains(this)) throw new IllegalStateException( + "Cannot update removed map entry."); + V ovalue = _ientry.setValue(value); + if (!areEqual(value, ovalue)) { + emitPut(_ientry.getKey(), value, ovalue); } - return modified; - } - public int size () { - return RMap.this.size(); - } - public void clear () { - RMap.this.clear(); - } + return ovalue; + } + // it's safe to pass these through because Map.Entry's + // implementations operate solely on getKey/getValue + public boolean equals (Object o) { + return _ientry.equals(o); + } + public int hashCode () { + return _ientry.hashCode(); + } + protected Map.Entry _ientry = _current; + }; + } + public void remove () { + checkMutate(); + iiter.remove(); + emitRemove(_current.getKey(), _current.getValue()); + _current = null; + } + protected Map.Entry _current; }; - } + } + public boolean contains (Object o) { + return iset.contains(o); + } + public boolean remove (Object o) { + checkMutate(); + boolean modified = iset.remove(o); + if (modified) { + @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry)o; + emitRemove(entry.getKey(), entry.getValue()); + } + return modified; + } + public int size () { + return RMap.this.size(); + } + public void clear () { + RMap.this.clear(); + } + }; + } - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; - return p; - } + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; + return p; + } - protected void emitPut (K key, V value, V oldValue) { - notifyPut(key, value, oldValue); - } + protected void emitPut (K key, V value, V oldValue) { + notifyPut(key, value, oldValue); + } - protected void notifyPut (K key, V value, V oldValue) { - notify(PUT, key, value, oldValue); - } + protected void notifyPut (K key, V value, V oldValue) { + notify(PUT, key, value, oldValue); + } - protected void emitRemove (K key, V oldValue) { - notifyRemove(key, oldValue); - } + protected void emitRemove (K key, V oldValue) { + notifyRemove(key, oldValue); + } - protected void notifyRemove (K key, V oldValue) { - notify(REMOVE, key, oldValue, null); - } + protected void notifyRemove (K key, V oldValue) { + notify(REMOVE, key, oldValue, null); + } - /** Contains our underlying mappings. */ - protected Map _impl; + /** Contains our underlying mappings. */ + protected Map _impl; - protected static final Listener NOOP = new Listener() {}; + protected static final Listener NOOP = new Listener() {}; - @SuppressWarnings("unchecked") protected static final Notifier PUT = new Notifier() { - public void notify (Object lner, Object key, Object value, Object oldValue) { - ((Listener)lner).onPut(key, value, oldValue); - } - }; + @SuppressWarnings("unchecked") protected static final Notifier PUT = new Notifier() { + public void notify (Object lner, Object key, Object value, Object oldValue) { + ((Listener)lner).onPut(key, value, oldValue); + } + }; - @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { - public void notify (Object lner, Object key, Object oldValue, Object ignored) { - ((Listener)lner).onRemove(key, oldValue); - } - }; + @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { + public void notify (Object lner, Object key, Object oldValue, Object ignored) { + ((Listener)lner).onRemove(key, oldValue); + } + }; } diff --git a/src/main/java/react/RPromise.java b/src/main/java/react/RPromise.java index b8de4e8..d9ebbfc 100644 --- a/src/main/java/react/RPromise.java +++ b/src/main/java/react/RPromise.java @@ -17,41 +17,41 @@ */ public class RPromise extends RFuture { - /** Creates a new, uncompleted, promise. */ - public static RPromise create () { - return new RPromise(); + /** Creates a new, uncompleted, promise. */ + public static RPromise create () { + return new RPromise(); + } + + /** Causes this promise to be completed with {@code result}. */ + public void complete (Try result) { + if (_result != null) throw new IllegalStateException("Already completed"); + _result = result; + try { + notify(COMPLETE, result, null, null); + } finally { + clearConnections(); } + } - /** Causes this promise to be completed with {@code result}. */ - public void complete (Try result) { - if (_result != null) throw new IllegalStateException("Already completed"); - _result = result; - try { - notify(COMPLETE, result, null, null); - } finally { - clearConnections(); - } - } - - /** Causes this promise to be completed successfully with {@code value}. */ - public void succeed (T value) { - complete(Try.success(value)); - } + /** Causes this promise to be completed successfully with {@code value}. */ + public void succeed (T value) { + complete(Try.success(value)); + } - /** Causes this promise to be completed with failure caused by {@code cause}. */ - public void fail (Throwable cause) { - complete(Try.failure(cause)); - } + /** Causes this promise to be completed with failure caused by {@code cause}. */ + public void fail (Throwable cause) { + complete(Try.failure(cause)); + } - @Override public Try result () { - return _result; - } + @Override public Try result () { + return _result; + } - protected Try _result; + protected Try _result; - @SuppressWarnings("unchecked") protected static final Notifier COMPLETE = new Notifier() { - public void notify (Object lner, Object value, Object i0, Object i1) { - ((SignalView.Listener>)lner).onEmit((Try)value); - } - }; + @SuppressWarnings("unchecked") protected static final Notifier COMPLETE = new Notifier() { + public void notify (Object lner, Object value, Object i0, Object i1) { + ((SignalView.Listener>)lner).onEmit((Try)value); + } + }; } diff --git a/src/main/java/react/RQueue.java b/src/main/java/react/RQueue.java index 6767883..36a9e02 100644 --- a/src/main/java/react/RQueue.java +++ b/src/main/java/react/RQueue.java @@ -15,207 +15,205 @@ * do not support removing arbitrary items from the queue; only the head of the queue may be * removed. */ -public class RQueue extends RCollection implements Queue -{ - /** Publishes queue events to listeners. */ - public interface Listener extends Reactor.RListener - { - /** Notifies listener of an offered (added) element. */ - default void onOffer (E elem) {} // noop - - /** Notifies listener of a polled (removed) element. */ - default void onPoll (E elem) {} // noop - } - - /** - * Creates a reactive queue backed by an {@link LinkedList}. - */ - public static RQueue create () { - return create(new LinkedList()); - } - - /** - * Creates a reactive queue with the supplied underlying queue implementation. - */ - public static RQueue create (Queue impl) { - return new RQueue(impl); - } - - /** - * Creates a reactive queue with the supplied underlying queue implementation. - */ - public RQueue (Queue impl) { - _impl = impl; - } - - /** - * Connects the supplied listener to this queue, such that it will be notified on offers and - * polls. - * @return a connection instance which can be used to cancel the connection. - */ - public Connection connect (Listener listener) { - return addConnection(listener); - } - - /** - * Invokes {@code onOffer} for all existing queue elements, then connects {@code listener}. - */ - public Connection connectNotify (Listener listener) { - for (E elem : _impl) listener.onOffer(elem); - return connect(listener); - } - - /** - * Disconnects the supplied listener from this queue if listen was called with it. - */ - public void disconnect (Listener listener) { - removeConnection(listener); - } - - // Queue methods that perform reactive functions in addition to calling through - @Override public boolean offer (E element) { - checkMutate(); - if (!_impl.offer(element)) return false; - emitOffer(element); - return true; - } - - @Override public boolean add (E element) { - checkMutate(); - _impl.add(element); // throws on failure - emitOffer(element); - return true; - } - - @Override public boolean addAll (Collection elements) { - checkMutate(); - // call add instead of calling _impl.addAll so if a listener throws an exception on - // emission, we don't have elements added without a corresponding emission - for (E elem : elements) add(elem); - return true; - } +public class RQueue extends RCollection implements Queue { + + /** Publishes queue events to listeners. */ + public interface Listener extends Reactor.RListener { + /** Notifies listener of an offered (added) element. */ + default void onOffer (E elem) {} // noop + + /** Notifies listener of a polled (removed) element. */ + default void onPoll (E elem) {} // noop + } + + /** + * Creates a reactive queue backed by an {@link LinkedList}. + */ + public static RQueue create () { + return create(new LinkedList()); + } + + /** + * Creates a reactive queue with the supplied underlying queue implementation. + */ + public static RQueue create (Queue impl) { + return new RQueue(impl); + } + + /** + * Creates a reactive queue with the supplied underlying queue implementation. + */ + public RQueue (Queue impl) { + _impl = impl; + } + + /** + * Connects the supplied listener to this queue, such that it will be notified on offers and + * polls. + * @return a connection instance which can be used to cancel the connection. + */ + public Connection connect (Listener listener) { + return addConnection(listener); + } + + /** + * Invokes {@code onOffer} for all existing queue elements, then connects {@code listener}. + */ + public Connection connectNotify (Listener listener) { + for (E elem : _impl) listener.onOffer(elem); + return connect(listener); + } + + /** + * Disconnects the supplied listener from this queue if listen was called with it. + */ + public void disconnect (Listener listener) { + removeConnection(listener); + } + + // Queue methods that perform reactive functions in addition to calling through + @Override public boolean offer (E element) { + checkMutate(); + if (!_impl.offer(element)) return false; + emitOffer(element); + return true; + } + + @Override public boolean add (E element) { + checkMutate(); + _impl.add(element); // throws on failure + emitOffer(element); + return true; + } + + @Override public boolean addAll (Collection elements) { + checkMutate(); + // call add instead of calling _impl.addAll so if a listener throws an exception on + // emission, we don't have elements added without a corresponding emission + for (E elem : elements) add(elem); + return true; + } + + @Override public E poll () { + checkMutate(); + E elem = _impl.poll(); + if (elem != null) emitPoll(elem); + return elem; + } + + @Override public E remove () { + checkMutate(); + E elem = _impl.remove(); // throws on empty + emitPoll(elem); + return elem; + } + + @Override public void clear () { + // clear in such a way as to emit events + while (!isEmpty()) remove(); + } + + @Override public Iterator iterator () { + return new Iterator () { + private final Iterator _iter = _impl.iterator(); + public boolean hasNext () { + return _iter.hasNext(); + } + public E next () { + return _iter.next(); + } + public void remove () { + throw new UnsupportedOperationException(); + } + }; + } - @Override public E poll () { - checkMutate(); - E elem = _impl.poll(); - if (elem != null) emitPoll(elem); - return elem; - } + @Override public boolean equals (Object other) { + return other == this || _impl.equals(other); + } - @Override public E remove () { - checkMutate(); - E elem = _impl.remove(); // throws on empty - emitPoll(elem); - return elem; - } + @Override public String toString () { + return "RQueue(" + _impl + ")"; + } - @Override public void clear () { - // clear in such a way as to emit events - while (!isEmpty()) remove(); - } + // Unsupported Queue methods + @Override public boolean retainAll (Collection collection) { + throw new UnsupportedOperationException(); + } - @Override public Iterator iterator () { - return new Iterator () { - private final Iterator _iter = _impl.iterator(); - public boolean hasNext () { - return _iter.hasNext(); - } - public E next () { - return _iter.next(); - } - public void remove () { - throw new UnsupportedOperationException(); - } - }; - } + @Override public boolean removeAll (Collection collection) { + throw new UnsupportedOperationException(); + } - @Override public boolean equals (Object other) { - return other == this || _impl.equals(other); - } + @Override public boolean remove (Object object) { + throw new UnsupportedOperationException(); + } - @Override public String toString () { - return "RQueue(" + _impl + ")"; - } + // Queue methods that purely pass through to the underlying queue + @Override public int hashCode () { + return _impl.hashCode(); + } - // Unsupported Queue methods - @Override public boolean retainAll (Collection collection) { - throw new UnsupportedOperationException(); - } + @Override public int size () { + return _impl.size(); + } - @Override public boolean removeAll (Collection collection) { - throw new UnsupportedOperationException(); - } + @Override public boolean isEmpty () { + return _impl.isEmpty(); + } - @Override public boolean remove (Object object) { - throw new UnsupportedOperationException(); - } + @Override public E peek () { + return _impl.peek(); + } - // Queue methods that purely pass through to the underlying queue - @Override public int hashCode () { - return _impl.hashCode(); - } + @Override public E element () { + return _impl.element(); + } - @Override public int size () { - return _impl.size(); - } + @Override public boolean contains (Object object) { + return _impl.contains(object); + } - @Override public boolean isEmpty () { - return _impl.isEmpty(); - } + @Override public boolean containsAll (Collection collection) { + return _impl.containsAll(collection); + } - @Override public E peek () { - return _impl.peek(); - } + @Override public Object[] toArray () { + return _impl.toArray(); + } - @Override public E element () { - return _impl.element(); - } + @Override public T[] toArray (T[] array) { + return _impl.toArray(array); + } - @Override public boolean contains (Object object) { - return _impl.contains(object); - } + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; + return p; + } - @Override public boolean containsAll (Collection collection) { - return _impl.containsAll(collection); - } + // Non-list RQueue implementation + protected void emitOffer (E elem) { + notify(OFFER, elem, null, null); + } - @Override public Object[] toArray () { - return _impl.toArray(); - } + protected void emitPoll (E elem) { + notify(POLL, elem, null, null); + } - @Override public T[] toArray (T[] array) { - return _impl.toArray(array); - } + /** Contains our underlying elements. */ + protected Queue _impl; - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; - return p; - } + protected static final Listener NOOP = new Listener() {}; - // Non-list RQueue implementation - protected void emitOffer (E elem) { - notify(OFFER, elem, null, null); + @SuppressWarnings("unchecked") protected static final Notifier OFFER = new Notifier() { + public void notify (Object lner, Object elem, Object ignored0, Object ignored1) { + ((Listener)lner).onOffer(elem); } + }; - protected void emitPoll (E elem) { - notify(POLL, elem, null, null); + @SuppressWarnings("unchecked") protected static final Notifier POLL = new Notifier() { + public void notify (Object lner, Object elem, Object ignored0, Object ignored1) { + ((Listener)lner).onPoll(elem); } - - /** Contains our underlying elements. */ - protected Queue _impl; - - protected static final Listener NOOP = new Listener() {}; - - @SuppressWarnings("unchecked") protected static final Notifier OFFER = new Notifier() { - public void notify (Object lner, Object elem, Object ignored0, Object ignored1) { - ((Listener)lner).onOffer(elem); - } - }; - - @SuppressWarnings("unchecked") protected static final Notifier POLL = new Notifier() { - public void notify (Object lner, Object elem, Object ignored0, Object ignored1) { - ((Listener)lner).onPoll(elem); - } - }; - + }; } diff --git a/src/main/java/react/RSet.java b/src/main/java/react/RSet.java index 179ca18..0e3a034 100644 --- a/src/main/java/react/RSet.java +++ b/src/main/java/react/RSet.java @@ -19,265 +19,264 @@ * #remove} will only generate a notification if an element was actually removed, use {@link * #removeForce} to force a notification. */ -public class RSet extends RCollection implements Set -{ - /** An interface for publishing set events to listeners. */ - public interface Listener extends Reactor.RListener - { - /** Notifies listener of an added element. */ - default void onAdd (E elem) {} // noop - - /** Notifies listener of a removed element. */ - default void onRemove (E elem) {} // noop - } - - /** - * Creates a reactive set backed by a @{link HashSet}. - */ - public static RSet create () { - return create(new HashSet()); - } - - /** - * Creates a reactive set with the supplied underlying set implementation. - */ - public static RSet create (Set impl) { - return new RSet(impl); - } - - /** - * Creates a reactive set with the supplied underlying set implementation. - */ - public RSet (Set impl) { - _impl = impl; - } - - /** - * Connects the supplied listener to this set, such that it will be notified on adds and - * removes. - * @return a connection instance which can be used to cancel the connection. - */ - public Connection connect (Listener listener) { - return addConnection(listener); - } - - /** - * Invokes {@code onAdd} for all existing elements, and then connects {@code listener}. - */ - public Connection connectNotify (Listener listener) { - for (E elem : this) listener.onAdd(elem); - return connect(listener); - } - - /** - * Disconnects the supplied listener from this set if listen was called with it. - */ - public void disconnect (Listener listener) { - removeConnection(listener); - } - - /** - * Adds the supplied element to the set, forcing a notification to the listeners regardless of - * whether the element was already in the set or not. - * @return true if the element was added, false if it was already in the set. - */ - public boolean addForce (E elem) { - checkMutate(); - boolean added = _impl.add(elem); - emitAdd(elem); - return added; - } - - /** - * Removes the supplied element from the set, forcing a notification to the listeners - * regardless of whether the element was already in the set or not. - * @return true if the element was in the set and was removed, false if it was not. - */ - public boolean removeForce (E elem) { - checkMutate(); - boolean removed = _impl.remove(elem); - emitRemove(elem); - return removed; - } - - /** - * Returns a value that models whether the specified element is contained in this map. The - * value will report a change when the specified element is added or removed. Note that {@link - * #addForce} or {@link #removeForce} will cause this view to trigger and incorrectly report - * that the element was not or was previously contained in the set. Caveat user. - */ - public ValueView containsView (E elem) { - if (elem == null) throw new NullPointerException("Must supply non-null 'elem'."); - return new MappedValue() { - @Override public Boolean get () { - return contains(elem); - } - @Override protected Connection connect () { - return RSet.this.connect(new RSet.Listener() { - @Override public void onAdd (E aelem) { - if (elem.equals(aelem)) notifyChange(true, false); - } - @Override public void onRemove (E relem) { - if (elem.equals(relem)) notifyChange(false, true); - } - }); - } - }; - } - - // from interface Set - public int size () { - return _impl.size(); - } - - // from interface Set - public boolean isEmpty () { - return _impl.isEmpty(); +public class RSet extends RCollection implements Set { + + /** An interface for publishing set events to listeners. */ + public interface Listener extends Reactor.RListener { + /** Notifies listener of an added element. */ + default void onAdd (E elem) {} // noop + + /** Notifies listener of a removed element. */ + default void onRemove (E elem) {} // noop + } + + /** + * Creates a reactive set backed by a @{link HashSet}. + */ + public static RSet create () { + return create(new HashSet()); + } + + /** + * Creates a reactive set with the supplied underlying set implementation. + */ + public static RSet create (Set impl) { + return new RSet(impl); + } + + /** + * Creates a reactive set with the supplied underlying set implementation. + */ + public RSet (Set impl) { + _impl = impl; + } + + /** + * Connects the supplied listener to this set, such that it will be notified on adds and + * removes. + * @return a connection instance which can be used to cancel the connection. + */ + public Connection connect (Listener listener) { + return addConnection(listener); + } + + /** + * Invokes {@code onAdd} for all existing elements, and then connects {@code listener}. + */ + public Connection connectNotify (Listener listener) { + for (E elem : this) listener.onAdd(elem); + return connect(listener); + } + + /** + * Disconnects the supplied listener from this set if listen was called with it. + */ + public void disconnect (Listener listener) { + removeConnection(listener); + } + + /** + * Adds the supplied element to the set, forcing a notification to the listeners regardless of + * whether the element was already in the set or not. + * @return true if the element was added, false if it was already in the set. + */ + public boolean addForce (E elem) { + checkMutate(); + boolean added = _impl.add(elem); + emitAdd(elem); + return added; + } + + /** + * Removes the supplied element from the set, forcing a notification to the listeners + * regardless of whether the element was already in the set or not. + * @return true if the element was in the set and was removed, false if it was not. + */ + public boolean removeForce (E elem) { + checkMutate(); + boolean removed = _impl.remove(elem); + emitRemove(elem); + return removed; + } + + /** + * Returns a value that models whether the specified element is contained in this map. The + * value will report a change when the specified element is added or removed. Note that {@link + * #addForce} or {@link #removeForce} will cause this view to trigger and incorrectly report + * that the element was not or was previously contained in the set. Caveat user. + */ + public ValueView containsView (E elem) { + if (elem == null) throw new NullPointerException("Must supply non-null 'elem'."); + return new MappedValue() { + @Override public Boolean get () { + return contains(elem); + } + @Override protected Connection connect () { + return RSet.this.connect(new RSet.Listener() { + @Override public void onAdd (E aelem) { + if (elem.equals(aelem)) notifyChange(true, false); + } + @Override public void onRemove (E relem) { + if (elem.equals(relem)) notifyChange(false, true); + } + }); + } + }; + } + + // from interface Set + public int size () { + return _impl.size(); + } + + // from interface Set + public boolean isEmpty () { + return _impl.isEmpty(); + } + + // from interface Set + public boolean contains (Object key) { + return _impl.contains(key); + } + + // from interface Set + public boolean add (E elem) { + checkMutate(); + if (!_impl.add(elem)) return false; + emitAdd(elem); + return true; + } + + // from interface Set + public boolean remove (Object rawElem) { + checkMutate(); + if (!_impl.remove(rawElem)) return false; + @SuppressWarnings("unchecked") E elem = (E)rawElem; + emitRemove(elem); + return true; + } + + // from interface Set + public boolean containsAll (Collection coll) { + return _impl.containsAll(coll); + } + + // from interface Set + public boolean addAll (Collection coll) { + boolean modified = false; + for (E elem : coll) { + modified |= add(elem); } - - // from interface Set - public boolean contains (Object key) { - return _impl.contains(key); + return modified; + } + + // from interface Set + public boolean retainAll (Collection coll) { + boolean modified = false; + for (Iterator iter = iterator(); iter.hasNext(); ) { + if (!coll.contains(iter.next())) { + iter.remove(); + modified = true; + } } - - // from interface Set - public boolean add (E elem) { - checkMutate(); - if (!_impl.add(elem)) return false; - emitAdd(elem); - return true; + return modified; + } + + // from interface Set + public boolean removeAll (Collection coll) { + boolean modified = false; + for (Iterator iter = coll.iterator(); iter.hasNext(); ) { + modified |= remove(iter.next()); } - - // from interface Set - public boolean remove (Object rawElem) { + return modified; + } + + // from interface Set + public void clear () { + checkMutate(); + // generate removed events for our elemens (do so on a copy of our set so that we can clear + // our underlying set before any of the published events are processed) + List elems = new ArrayList(_impl); + _impl.clear(); + for (E elem : elems) emitRemove(elem); + } + + // from interface Set + public Iterator iterator () { + Iterator iiter = _impl.iterator(); + return new Iterator() { + public boolean hasNext () { + return iiter.hasNext(); + } + public E next () { + return (_current = iiter.next()); + } + public void remove () { checkMutate(); - if (!_impl.remove(rawElem)) return false; - @SuppressWarnings("unchecked") E elem = (E)rawElem; - emitRemove(elem); - return true; - } + iiter.remove(); + emitRemove(_current); + } + protected E _current; + }; + } - // from interface Set - public boolean containsAll (Collection coll) { - return _impl.containsAll(coll); - } + // from interface Set + public Object[] toArray () { + return _impl.toArray(); + } - // from interface Set - public boolean addAll (Collection coll) { - boolean modified = false; - for (E elem : coll) { - modified |= add(elem); - } - return modified; - } + // from interface Set + public T[] toArray (T[] array) { + return _impl.toArray(array); + } - // from interface Set - public boolean retainAll (Collection coll) { - boolean modified = false; - for (Iterator iter = iterator(); iter.hasNext(); ) { - if (!coll.contains(iter.next())) { - iter.remove(); - modified = true; - } - } - return modified; - } + @Override public int hashCode () { + return _impl.hashCode(); + } - // from interface Set - public boolean removeAll (Collection coll) { - boolean modified = false; - for (Iterator iter = coll.iterator(); iter.hasNext(); ) { - modified |= remove(iter.next()); - } - return modified; - } + @Override public boolean equals (Object other) { + return other == this || _impl.equals(other); + } - // from interface Set - public void clear () { - checkMutate(); - // generate removed events for our elemens (do so on a copy of our set so that we can clear - // our underlying set before any of the published events are processed) - List elems = new ArrayList(_impl); - _impl.clear(); - for (E elem : elems) emitRemove(elem); - } + @Override public String toString () { + return "RSet" + _impl; + } - // from interface Set - public Iterator iterator () { - Iterator iiter = _impl.iterator(); - return new Iterator() { - public boolean hasNext () { - return iiter.hasNext(); - } - public E next () { - return (_current = iiter.next()); - } - public void remove () { - checkMutate(); - iiter.remove(); - emitRemove(_current); - } - protected E _current; - }; - } + @Override Listener placeholderListener () { + @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; + return p; + } - // from interface Set - public Object[] toArray () { - return _impl.toArray(); - } + protected void emitAdd (E elem) { + notifyAdd(elem); + } - // from interface Set - public T[] toArray (T[] array) { - return _impl.toArray(array); - } + protected void notifyAdd (E elem) { + notify(ADD, elem, null, null); + } - @Override public int hashCode () { - return _impl.hashCode(); - } + protected void emitRemove (E elem) { + notifyRemove(elem); + } - @Override public boolean equals (Object other) { - return other == this || _impl.equals(other); - } - - @Override public String toString () { - return "RSet" + _impl; - } + protected void notifyRemove (E elem) { + notify(REMOVE, elem, null, null); + } - @Override Listener placeholderListener () { - @SuppressWarnings("unchecked") Listener p = (Listener)NOOP; - return p; - } + /** Contains our underlying elements. */ + protected Set _impl; - protected void emitAdd (E elem) { - notifyAdd(elem); - } - - protected void notifyAdd (E elem) { - notify(ADD, elem, null, null); - } + protected static final Listener NOOP = new Listener() {}; - protected void emitRemove (E elem) { - notifyRemove(elem); + @SuppressWarnings("unchecked") protected static final Notifier ADD = new Notifier() { + public void notify (Object lner, Object elem, Object _1, Object _2) { + ((Listener)lner).onAdd(elem); } + }; - protected void notifyRemove (E elem) { - notify(REMOVE, elem, null, null); + @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { + public void notify (Object lner, Object elem, Object _1, Object _2) { + ((Listener)lner).onRemove(elem); } - - /** Contains our underlying elements. */ - protected Set _impl; - - protected static final Listener NOOP = new Listener() {}; - - @SuppressWarnings("unchecked") protected static final Notifier ADD = new Notifier() { - public void notify (Object lner, Object elem, Object _1, Object _2) { - ((Listener)lner).onAdd(elem); - } - }; - - @SuppressWarnings("unchecked") protected static final Notifier REMOVE = new Notifier() { - public void notify (Object lner, Object elem, Object _1, Object _2) { - ((Listener)lner).onRemove(elem); - } - }; + }; } diff --git a/src/main/java/react/Reactor.java b/src/main/java/react/Reactor.java index 03cf1c9..64d9144 100644 --- a/src/main/java/react/Reactor.java +++ b/src/main/java/react/Reactor.java @@ -9,196 +9,196 @@ * A base class for all reactive classes. This is an implementation detail, but is public so that * third parties may use it to create their own reactive classes, if desired. */ -public abstract class Reactor -{ - /** The base class for all reactor listeners. */ - public abstract interface RListener {} - - /** - * Returns true if this reactor has at least one connection. - */ - public boolean hasConnections () { - return _listeners != null; - } - - /** - * Clears all connections from this reactor. This is not used in normal circumstances, but is - * made available for libraries which build on react and need a way to forcibly disconnect all - * connections to reactive state. - * - * @throws IllegalStateException if this reactor is in the middle of dispatching an event. - */ - public synchronized void clearConnections () { - if (isDispatching()) throw new IllegalStateException( - "Cannot clear connections while dispatching."); - assert _pendingRuns == null; - _listeners = null; - } - - /** Returns the listener to be used when a weakly held listener is discovered to have been - * collected while dispatching. This listener should NOOP when signaled. */ - abstract RListener placeholderListener (); - - protected synchronized Cons addConnection (RListener listener) { - if (listener == null) throw new NullPointerException("Null listener"); - return addCons(new Cons(this, listener)); - } - - protected synchronized Cons addCons (final Cons cons) { - if (isDispatching()) { - _pendingRuns = append(_pendingRuns, new Runs() { - public void run () { - _listeners = Cons.insert(_listeners, cons); - connectionAdded(); - } - }); - } else { - _listeners = Cons.insert(_listeners, cons); - connectionAdded(); +public abstract class Reactor { + + /** The base class for all reactor listeners. */ + public abstract interface RListener {} + + /** + * Returns true if this reactor has at least one connection. + */ + public boolean hasConnections () { + return _listeners != null; + } + + /** + * Clears all connections from this reactor. This is not used in normal circumstances, but is + * made available for libraries which build on react and need a way to forcibly disconnect all + * connections to reactive state. + * + * @throws IllegalStateException if this reactor is in the middle of dispatching an event. + */ + public synchronized void clearConnections () { + if (isDispatching()) throw new IllegalStateException( + "Cannot clear connections while dispatching."); + assert _pendingRuns == null; + _listeners = null; + } + + /** Returns the listener to be used when a weakly held listener is discovered to have been + * collected while dispatching. This listener should NOOP when signaled. */ + abstract RListener placeholderListener (); + + protected synchronized Cons addConnection (RListener listener) { + if (listener == null) throw new NullPointerException("Null listener"); + return addCons(new Cons(this, listener)); + } + + protected synchronized Cons addCons (final Cons cons) { + if (isDispatching()) { + _pendingRuns = append(_pendingRuns, new Runs() { + public void run () { + _listeners = Cons.insert(_listeners, cons); + connectionAdded(); } - return cons; - } - - protected synchronized void disconnect (final Cons cons) { - if (isDispatching()) { - _pendingRuns = append(_pendingRuns, new Runs() { - public void run () { - _listeners = Cons.remove(_listeners, cons); - connectionRemoved(); - } - }); - } else { - _listeners = Cons.remove(_listeners, cons); - connectionRemoved(); + }); + } else { + _listeners = Cons.insert(_listeners, cons); + connectionAdded(); + } + return cons; + } + + protected synchronized void disconnect (final Cons cons) { + if (isDispatching()) { + _pendingRuns = append(_pendingRuns, new Runs() { + public void run () { + _listeners = Cons.remove(_listeners, cons); + connectionRemoved(); } - } - - protected synchronized void removeConnection (final RListener listener) { - if (isDispatching()) { - _pendingRuns = append(_pendingRuns, new Runs() { - public void run () { - _listeners = Cons.removeAll(_listeners, listener); - connectionRemoved(); - } - }); - } else { - _listeners = Cons.removeAll(_listeners, listener); - connectionRemoved(); + }); + } else { + _listeners = Cons.remove(_listeners, cons); + connectionRemoved(); + } + } + + protected synchronized void removeConnection (final RListener listener) { + if (isDispatching()) { + _pendingRuns = append(_pendingRuns, new Runs() { + public void run () { + _listeners = Cons.removeAll(_listeners, listener); + connectionRemoved(); } - } - - /** - * Called prior to mutating any underlying model; allows subclasses to reject mutation. - */ - protected void checkMutate () { - // noop - } - - /** - * Called when a connection has been added to this reactor. - */ - protected void connectionAdded () { - // noop - } - - /** - * Called when a connection may have been removed from this reactor. - */ - protected void connectionRemoved () { - // noop - } - - /** - * Emits the supplied event to all connected slots. We omit a bunch of generic type shenanigans - * here and force the caller to just cast things, because this is all under the hood where - * there's zero chance of fucking up and this results in simpler, easier to read code. - */ - protected void notify (final Notifier notifier, final Object a1, final Object a2, - final Object a3) { - Cons lners; - synchronized (this) { - // if we're currently dispatching, defer this notification until we're done - if (_listeners == DISPATCHING) { - _pendingRuns = append(_pendingRuns, new Runs() { - public void run () { - Reactor.this.notify(notifier, a1, a2, a3); - } - }); - return; - } - lners = _listeners; - Cons sentinel = DISPATCHING; - _listeners = sentinel; - } - - RuntimeException exn = null; + }); + } else { + _listeners = Cons.removeAll(_listeners, listener); + connectionRemoved(); + } + } + + /** + * Called prior to mutating any underlying model; allows subclasses to reject mutation. + */ + protected void checkMutate () { + // noop + } + + /** + * Called when a connection has been added to this reactor. + */ + protected void connectionAdded () { + // noop + } + + /** + * Called when a connection may have been removed from this reactor. + */ + protected void connectionRemoved () { + // noop + } + + /** + * Emits the supplied event to all connected slots. We omit a bunch of generic type shenanigans + * here and force the caller to just cast things, because this is all under the hood where + * there's zero chance of fucking up and this results in simpler, easier to read code. + */ + protected void notify (final Notifier notifier, final Object a1, final Object a2, + final Object a3) { + Cons lners; + synchronized (this) { + // if we're currently dispatching, defer this notification until we're done + if (_listeners == DISPATCHING) { + _pendingRuns = append(_pendingRuns, new Runs() { + public void run () { + Reactor.this.notify(notifier, a1, a2, a3); + } + }); + return; + } + lners = _listeners; + Cons sentinel = DISPATCHING; + _listeners = sentinel; + } + + RuntimeException exn = null; + try { + // perform this dispatch, catching and accumulating any errors + for (Cons cons = lners; cons != null; cons = cons.next) { try { - // perform this dispatch, catching and accumulating any errors - for (Cons cons = lners; cons != null; cons = cons.next) { - try { - notifier.notify(cons.listener(), a1, a2, a3); - } catch (RuntimeException ex) { - if (exn != null) exn.addSuppressed(ex); - else exn = ex; - } - if (cons.oneShot()) cons.close(); - } - - } finally { - // note that we're no longer dispatching - synchronized (this) { _listeners = lners; } - - // perform any operations that were deferred while we were dispatching - Runs run; - while ((run = nextRun()) != null) { - try { - run.run(); - } catch (RuntimeException ex) { - if (exn != null) exn.addSuppressed(ex); - else exn = ex; - } - } + notifier.notify(cons.listener(), a1, a2, a3); + } catch (RuntimeException ex) { + if (exn != null) exn.addSuppressed(ex); + else exn = ex; } + if (cons.oneShot()) cons.close(); + } - // finally throw any exception(s) that occurred during dispatch - if (exn != null) throw exn; - } - - private synchronized Runs nextRun () { - Runs run = _pendingRuns; - if (run != null) _pendingRuns = run.next; - return run; - } - - // always called while lock is held on this reactor - private final boolean isDispatching () { - return _listeners == DISPATCHING; - } - - protected Cons _listeners; - protected Runs _pendingRuns; - - /** - * Returns true if both values are null, reference the same instance, or are - * {@link Object#equals}. - */ - protected static boolean areEqual (T o1, T o2) { - return (o1 == o2 || (o1 != null && o1.equals(o2))); - } - - protected static Runs append (Runs head, Runs action) { - if (head == null) return action; - head.next = append(head.next, action); - return head; - } - - protected static abstract class Runs implements Runnable { - public Runs next; - } + } finally { + // note that we're no longer dispatching + synchronized (this) { _listeners = lners; } - protected static abstract class Notifier { - public abstract void notify (Object listener, Object a1, Object a2, Object a3); - } - - protected static final Cons DISPATCHING = new Cons(null, null); + // perform any operations that were deferred while we were dispatching + Runs run; + while ((run = nextRun()) != null) { + try { + run.run(); + } catch (RuntimeException ex) { + if (exn != null) exn.addSuppressed(ex); + else exn = ex; + } + } + } + + // finally throw any exception(s) that occurred during dispatch + if (exn != null) throw exn; + } + + private synchronized Runs nextRun () { + Runs run = _pendingRuns; + if (run != null) _pendingRuns = run.next; + return run; + } + + // always called while lock is held on this reactor + private final boolean isDispatching () { + return _listeners == DISPATCHING; + } + + protected Cons _listeners; + protected Runs _pendingRuns; + + /** + * Returns true if both values are null, reference the same instance, or are + * {@link Object#equals}. + */ + protected static boolean areEqual (T o1, T o2) { + return (o1 == o2 || (o1 != null && o1.equals(o2))); + } + + protected static Runs append (Runs head, Runs action) { + if (head == null) return action; + head.next = append(head.next, action); + return head; + } + + protected static abstract class Runs implements Runnable { + public Runs next; + } + + protected static abstract class Notifier { + public abstract void notify (Object listener, Object a1, Object a2, Object a3); + } + + protected static final Cons DISPATCHING = new Cons(null, null); } diff --git a/src/main/java/react/Signal.java b/src/main/java/react/Signal.java index cf2e9f5..441e928 100644 --- a/src/main/java/react/Signal.java +++ b/src/main/java/react/Signal.java @@ -9,41 +9,41 @@ * A signal that emits events of type {@code T}. {@link Slot}s may be connected to a signal to be * notified upon event emission. */ -public class Signal extends AbstractSignal -{ - /** - * A signal that emits an event with no associated data. It can be used like so: - */ - public static class Unit extends Signal { - /** Connects a zero-argument listener to this signal. */ - public Connection connect (Runnable slot) { - return connect(v -> slot.run()); - } +public class Signal extends AbstractSignal { - /** Causes this signal to emit an event to its connected slots. */ - public void emit () { - notifyEmit(null); - } + /** + * A signal that emits an event with no associated data. It can be used like so: + */ + public static class Unit extends Signal { + /** Connects a zero-argument listener to this signal. */ + public Connection connect (Runnable slot) { + return connect(v -> slot.run()); } - /** - * Convenience method for creating a signal without repeating the type parameter. - */ - public static Signal create () { - return new Signal(); + /** Causes this signal to emit an event to its connected slots. */ + public void emit () { + notifyEmit(null); } + } - /** - * Convenience method for creating a {@link Unit} signal. - */ - public static Signal.Unit createUnit () { - return new Signal.Unit(); - } + /** + * Convenience method for creating a signal without repeating the type parameter. + */ + public static Signal create () { + return new Signal(); + } - /** - * Causes this signal to emit the supplied event to connected slots. - */ - public void emit (T event) { - notifyEmit(event); - } + /** + * Convenience method for creating a {@link Unit} signal. + */ + public static Signal.Unit createUnit () { + return new Signal.Unit(); + } + + /** + * Causes this signal to emit the supplied event to connected slots. + */ + public void emit (T event) { + notifyEmit(event); + } } diff --git a/src/main/java/react/SignalView.java b/src/main/java/react/SignalView.java index 21e9c6e..799248e 100644 --- a/src/main/java/react/SignalView.java +++ b/src/main/java/react/SignalView.java @@ -12,55 +12,57 @@ * is generally used to provide signal-like views of changing entities. See {@link AbstractValue} * for an example. */ -public interface SignalView -{ - /** Used to observe events from a signal. Normally one uses {@link Slot} rather than this - * listener, but this interface exists to allow Java 8 lambdas to be used as well. */ - interface Listener extends Reactor.RListener { - /** - * Called when a signal to which this slot is connected has emitted an event. - * @param event the event emitted by the signal. - */ - void onEmit (T event); - } +public interface SignalView { + /** + * Used to observe events from a signal. Normally one uses {@link Slot} rather than this + * listener, but this interface exists to allow Java 8 lambdas to be used as well. + */ + interface Listener extends Reactor.RListener { /** - * Creates a signal that maps this signal via a function. When this signal emits a value, the - * mapped signal will emit that value as transformed by the supplied function. The mapped - * signal will retain a connection to this signal for as long as it has connections of its own. + * Called when a signal to which this slot is connected has emitted an event. + * @param event the event emitted by the signal. */ - SignalView map (Function func); + void onEmit (T event); + } - /** - * Creates a signal that emits a value only when the supplied filter function returns true. The - * filtered signal will retain a connection to this signal for as long as it has connections of - * its own. - */ - SignalView filter (Function pred); + /** + * Creates a signal that maps this signal via a function. When this signal emits a value, the + * mapped signal will emit that value as transformed by the supplied function. The mapped + * signal will retain a connection to this signal for as long as it has connections of its own. + */ + SignalView map (Function func); - /** - * Creates a signal that maps the values emitted by this signal through {@code collector} and - * emits only the non-null values that are returned. This allows you to perform a type-test on - * the values emitted by a signal and only emit values of the appropriate subtype. - */ - SignalView collect (Function collector); + /** + * Creates a signal that emits a value only when the supplied filter function returns true. The + * filtered signal will retain a connection to this signal for as long as it has connections of + * its own. + */ + SignalView filter (Function pred); - /** - * Returns a future that is completed with the next value from this signal. - */ - RFuture next (); + /** + * Creates a signal that maps the values emitted by this signal through {@code collector} and + * emits only the non-null values that are returned. This allows you to perform a type-test on + * the values emitted by a signal and only emit values of the appropriate subtype. + */ + SignalView collect (Function collector); - /** - * Connects this signal to the supplied slot, such that when an event is emitted from this - * signal, the slot will be notified. - * - * @return a connection instance which can be used to cancel the connection. - */ - Connection connect (Listener slot); + /** + * Returns a future that is completed with the next value from this signal. + */ + RFuture next (); - /** - * Disconnects the supplied slot from this signal if connect was called with it. If the slot has - * been connected multiple times, all connections are cancelled. - */ - void disconnect (Listener slot); + /** + * Connects this signal to the supplied slot, such that when an event is emitted from this + * signal, the slot will be notified. + * + * @return a connection instance which can be used to cancel the connection. + */ + Connection connect (Listener slot); + + /** + * Disconnects the supplied slot from this signal if connect was called with it. If the slot has + * been connected multiple times, all connections are cancelled. + */ + void disconnect (Listener slot); } diff --git a/src/main/java/react/Slot.java b/src/main/java/react/Slot.java index ad81c15..c690ed1 100644 --- a/src/main/java/react/Slot.java +++ b/src/main/java/react/Slot.java @@ -13,49 +13,49 @@ */ public interface Slot extends SignalView.Listener, ValueView.Listener { - /** A slot that does nothing. Useful when you don't want to fiddle with null checks. */ - public static Slot NOOP = v -> {}; // noop! - - /** - * Returns a slot that logs the supplied message (via {@link System#err}) with the emitted - * value appended to it before passing the emitted value on to {@code slot}. Useful for - * debugging. - */ - public static Slot trace (String message, Slot slot) { - return value -> { - System.err.println(message + value); - slot.onEmit(value); - }; - } - - /** - * Returns a slot that maps values via {@code f} and then passes them to this slot. - * This is essentially function composition in that {@code slot.compose(f)} means - * {@code slot(f(value)))} where this slot is treated as a side effecting void function. - */ - default Slot compose (Function f) { - return value -> this.onEmit(f.apply(value)); - } - - /** - * Returns a slot that is only notified when the signal to which this slot is connected emits a - * value which causes {@code pred} to return true. - */ - default Slot filtered (Function pred) { - return value -> { - if (pred.apply(value)) this.onEmit(value); - }; - } - - /** - * Returns a new slot that invokes this slot and then evokes {@code after}. - */ - default Slot andThen (Slot after) { - return value -> { - this.onEmit(value); - after.onEmit(value); - }; - } - - default void onChange (T newValue, T oldValue) { onEmit(newValue); } + /** A slot that does nothing. Useful when you don't want to fiddle with null checks. */ + public static Slot NOOP = v -> {}; // noop! + + /** + * Returns a slot that logs the supplied message (via {@link System#err}) with the emitted + * value appended to it before passing the emitted value on to {@code slot}. Useful for + * debugging. + */ + public static Slot trace (String message, Slot slot) { + return value -> { + System.err.println(message + value); + slot.onEmit(value); + }; + } + + /** + * Returns a slot that maps values via {@code f} and then passes them to this slot. + * This is essentially function composition in that {@code slot.compose(f)} means + * {@code slot(f(value)))} where this slot is treated as a side effecting void function. + */ + default Slot compose (Function f) { + return value -> this.onEmit(f.apply(value)); + } + + /** + * Returns a slot that is only notified when the signal to which this slot is connected emits a + * value which causes {@code pred} to return true. + */ + default Slot filtered (Function pred) { + return value -> { + if (pred.apply(value)) this.onEmit(value); + }; + } + + /** + * Returns a new slot that invokes this slot and then evokes {@code after}. + */ + default Slot andThen (Slot after) { + return value -> { + this.onEmit(value); + after.onEmit(value); + }; + } + + default void onChange (T newValue, T oldValue) { onEmit(newValue); } } diff --git a/src/main/java/react/Try.java b/src/main/java/react/Try.java index ca263c2..7762f4f 100644 --- a/src/main/java/react/Try.java +++ b/src/main/java/react/Try.java @@ -15,113 +15,113 @@ */ public abstract class Try { - /** Represents a successful try. Contains the successful result. */ - public static final class Success extends Try { - public final T value; - public Success (T value) { - this.value = value; - } - - @Override public T get () { return value; } - @Override public Throwable getFailure () { throw new IllegalStateException(); } - @Override public boolean isSuccess () { return true; } - @Override public Try map (Function func) { - try { - return success(func.apply(value)); - } catch (Throwable t) { - return failure(t); - } - } - @Override public Try recover (Function func) { - return this; - } - @Override public Try flatMap (Function> func) { - try { - return func.apply(value); - } catch (Throwable t) { - return failure(t); - } - } - - @Override public String toString () { return "Success(" + value + ")"; } + /** Represents a successful try. Contains the successful result. */ + public static final class Success extends Try { + public final T value; + public Success (T value) { + this.value = value; } - /** Represents a failed try. Contains the cause of failure. */ - public static final class Failure extends Try { - public final Throwable cause; - public Failure (Throwable cause) { - this.cause = cause; - } - - @Override public T get () { - if (cause instanceof RuntimeException) { - throw (RuntimeException)cause; - } else if (cause instanceof Error) { - throw (Error)cause; - } else { - throw (RuntimeException)new RuntimeException().initCause(cause); - } - } - @Override public Throwable getFailure () { return cause; } - @Override public boolean isSuccess () { return false; } - @Override public Try map (Function func) { - return this.casted(); - } - @Override public Try recover (Function func) { - try { - return success(func.apply(cause)); - } catch (Throwable t) { - return failure(t); - } - } - @Override public Try flatMap (Function> func) { - return this.casted(); - } - - @Override public String toString () { return "Failure(" + cause + ")"; } - - @SuppressWarnings("unchecked") private Try casted () { return (Try)this; } + @Override public T get () { return value; } + @Override public Throwable getFailure () { throw new IllegalStateException(); } + @Override public boolean isSuccess () { return true; } + @Override public Try map (Function func) { + try { + return success(func.apply(value)); + } catch (Throwable t) { + return failure(t); + } + } + @Override public Try recover (Function func) { + return this; + } + @Override public Try flatMap (Function> func) { + try { + return func.apply(value); + } catch (Throwable t) { + return failure(t); + } } - /** Creates a successful try. */ - public static Try success (T value) { return new Success(value); } + @Override public String toString () { return "Success(" + value + ")"; } + } - /** Creates a failed try. */ - public static Try failure (Throwable cause) { return new Failure(cause); } + /** Represents a failed try. Contains the cause of failure. */ + public static final class Failure extends Try { + public final Throwable cause; + public Failure (Throwable cause) { + this.cause = cause; + } - /** Lifts {@code func}, a function on values, to a function on tries. */ - public static Function,Try> lift (final Function func) { - return new Function,Try>() { - public Try apply (Try result) { return result.map(func); } - }; + @Override public T get () { + if (cause instanceof RuntimeException) { + throw (RuntimeException)cause; + } else if (cause instanceof Error) { + throw (Error)cause; + } else { + throw (RuntimeException)new RuntimeException().initCause(cause); + } + } + @Override public Throwable getFailure () { return cause; } + @Override public boolean isSuccess () { return false; } + @Override public Try map (Function func) { + return this.casted(); + } + @Override public Try recover (Function func) { + try { + return success(func.apply(cause)); + } catch (Throwable t) { + return failure(t); + } + } + @Override public Try flatMap (Function> func) { + return this.casted(); } - /** Returns the value associated with a successful try, or rethrows the exception if the try - * failed. If the exception is a checked exception, it will be thrown as a the {@code cause} of - * a newly constructed {@link RuntimeException}. */ - public abstract T get (); + @Override public String toString () { return "Failure(" + cause + ")"; } + + @SuppressWarnings("unchecked") private Try casted () { return (Try)this; } + } + + /** Creates a successful try. */ + public static Try success (T value) { return new Success(value); } + + /** Creates a failed try. */ + public static Try failure (Throwable cause) { return new Failure(cause); } + + /** Lifts {@code func}, a function on values, to a function on tries. */ + public static Function,Try> lift (final Function func) { + return new Function,Try>() { + public Try apply (Try result) { return result.map(func); } + }; + } + + /** Returns the value associated with a successful try, or rethrows the exception if the try + * failed. If the exception is a checked exception, it will be thrown as a the {@code cause} of + * a newly constructed {@link RuntimeException}. */ + public abstract T get (); - /** Returns the cause of failure for a failed try. Throws {@link IllegalStateException} if - * called on a successful try. */ - public abstract Throwable getFailure (); + /** Returns the cause of failure for a failed try. Throws {@link IllegalStateException} if + * called on a successful try. */ + public abstract Throwable getFailure (); - /** Returns try if this is a successful try, false if it is a failed try. */ - public abstract boolean isSuccess (); + /** Returns try if this is a successful try, false if it is a failed try. */ + public abstract boolean isSuccess (); - /** Returns try if this is a failed try, false if it is a successful try. */ - public boolean isFailure () { return !isSuccess(); } + /** Returns try if this is a failed try, false if it is a successful try. */ + public boolean isFailure () { return !isSuccess(); } - /** Maps successful tries through {@code func}, passees failure through as is. */ - public abstract Try map (Function func); + /** Maps successful tries through {@code func}, passees failure through as is. */ + public abstract Try map (Function func); - /** Maps failed tries through {@code func}, passes success through as is. Note: if {@code func} - * throws an exception, you will get back a failure try with the new failure. Ideally one - * could generalize the type {@code T} here but Java doesn't allow type parameters with lower - * bounds. */ - public abstract Try recover (Function func); + /** Maps failed tries through {@code func}, passes success through as is. Note: if {@code func} + * throws an exception, you will get back a failure try with the new failure. Ideally one + * could generalize the type {@code T} here but Java doesn't allow type parameters with lower + * bounds. */ + public abstract Try recover (Function func); - /** Maps successful tries through {@code func}, passes failure through as is. */ - public abstract Try flatMap (Function> func); + /** Maps successful tries through {@code func}, passes failure through as is. */ + public abstract Try flatMap (Function> func); - private Try () {} + private Try () {} } diff --git a/src/main/java/react/Value.java b/src/main/java/react/Value.java index 873a0e0..b9058e4 100644 --- a/src/main/java/react/Value.java +++ b/src/main/java/react/Value.java @@ -10,48 +10,48 @@ */ public class Value extends AbstractValue { - /** - * Convenience method for creating an instance with the supplied starting value. - */ - public static Value create (T value) { - return new Value(value); - } - - /** - * Creates an instance with the supplied starting value. - */ - public Value (T value) { - // we can't have any listeners at this point, so no need to notify - _value = value; - } - - /** - * Updates this instance with the supplied value. Registered listeners are notified only if the - * value differs from the current value, as determined via {@link Object#equals}. - * @return the previous value contained by this instance. - */ - public T update (T value) { - return updateAndNotifyIf(value); - } - - /** - * Updates this instance with the supplied value. Registered listeners are notified regardless - * of whether the new value is equal to the old value. - * @return the previous value contained by this instance. - */ - public T updateForce (T value) { - return updateAndNotify(value); - } - - @Override public T get () { - return _value; - } - - @Override protected T updateLocal (T value) { - T oldValue = _value; - _value = value; - return oldValue; - } - - protected T _value; + /** + * Convenience method for creating an instance with the supplied starting value. + */ + public static Value create (T value) { + return new Value(value); + } + + /** + * Creates an instance with the supplied starting value. + */ + public Value (T value) { + // we can't have any listeners at this point, so no need to notify + _value = value; + } + + /** + * Updates this instance with the supplied value. Registered listeners are notified only if the + * value differs from the current value, as determined via {@link Object#equals}. + * @return the previous value contained by this instance. + */ + public T update (T value) { + return updateAndNotifyIf(value); + } + + /** + * Updates this instance with the supplied value. Registered listeners are notified regardless + * of whether the new value is equal to the old value. + * @return the previous value contained by this instance. + */ + public T updateForce (T value) { + return updateAndNotify(value); + } + + @Override public T get () { + return _value; + } + + @Override protected T updateLocal (T value) { + T oldValue = _value; + _value = value; + return oldValue; + } + + protected T _value; } diff --git a/src/main/java/react/ValueView.java b/src/main/java/react/ValueView.java index 2fb1357..3926341 100644 --- a/src/main/java/react/ValueView.java +++ b/src/main/java/react/ValueView.java @@ -15,87 +15,87 @@ */ public interface ValueView { - /** Used to observe changes to a value. */ - interface Listener extends Reactor.RListener { - /** Called when the value to which this listener is bound has changed. */ - void onChange (T value, T oldValue); - } + /** Used to observe changes to a value. */ + interface Listener extends Reactor.RListener { + /** Called when the value to which this listener is bound has changed. */ + void onChange (T value, T oldValue); + } - /** - * Returns the current value. - */ - T get (); + /** + * Returns the current value. + */ + T get (); - /** - * Creates a value that maps this value via a function. When this value changes, the mapped - * listeners will be notified, regardless of whether the new and old mapped values differ. The - * mapped value will retain a connection to this value for as long as it has connections of its - * own. - */ - ValueView map (Function func); + /** + * Creates a value that maps this value via a function. When this value changes, the mapped + * listeners will be notified, regardless of whether the new and old mapped values differ. The + * mapped value will retain a connection to this value for as long as it has connections of its + * own. + */ + ValueView map (Function func); - /** - * Creates a value that flat maps (monadic binds) this value via a function. When this value - * changes, the mapping function is called to obtain a new reactive value. All of the listeners - * to the flat mapped value are "transferred" to the new reactive value. The mapped value will - * retain a connection to the most recent reactive value for as long as it has connections of - * its own. - */ - ValueView flatMap (Function> func); + /** + * Creates a value that flat maps (monadic binds) this value via a function. When this value + * changes, the mapping function is called to obtain a new reactive value. All of the listeners + * to the flat mapped value are "transferred" to the new reactive value. The mapped value will + * retain a connection to the most recent reactive value for as long as it has connections of + * its own. + */ + ValueView flatMap (Function> func); - /** - * Returns a signal that is emitted whenever this value changes. - */ - SignalView changes (); + /** + * Returns a signal that is emitted whenever this value changes. + */ + SignalView changes (); - /** - * Returns a future which is completed with this value when the value meeds {@code cond}. If - * the value meets {@code cond} now, the future will be completed immediately, otherwise the - * future will be completed when the value changes to a value which meets {@code cond}. - */ - RFuture when (Function cond); + /** + * Returns a future which is completed with this value when the value meeds {@code cond}. If + * the value meets {@code cond} now, the future will be completed immediately, otherwise the + * future will be completed when the value changes to a value which meets {@code cond}. + */ + RFuture when (Function cond); - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. The listener is held by a strong reference, so it's held in memory by virtue of - * being connected. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connect (Listener listener); + /** + * Connects the supplied listener to this value, such that it will be notified when this value + * changes. The listener is held by a strong reference, so it's held in memory by virtue of + * being connected. + * @return a connection instance which can be used to cancel the connection. + */ + Connection connect (Listener listener); - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. Also immediately notifies the listener of the current value. Note that the previous - * value supplied with this notification will be null. If the notification triggers an - * unchecked exception, the slot will automatically be disconnected and the caller need not - * worry about cleaning up after itself. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connectNotify (Listener listener); + /** + * Connects the supplied listener to this value, such that it will be notified when this value + * changes. Also immediately notifies the listener of the current value. Note that the previous + * value supplied with this notification will be null. If the notification triggers an + * unchecked exception, the slot will automatically be disconnected and the caller need not + * worry about cleaning up after itself. + * @return a connection instance which can be used to cancel the connection. + */ + Connection connectNotify (Listener listener); - /** - * Disconnects the supplied listener from this value if it's connected. If the listener has been - * connected multiple times, all connections are cancelled. - */ - void disconnect (Listener listener); + /** + * Disconnects the supplied listener from this value if it's connected. If the listener has been + * connected multiple times, all connections are cancelled. + */ + void disconnect (Listener listener); - // these methods exist to let javac know that it can synthesize a Slot when a single argument - // lambda is passed to connect/connectNotify + // these methods exist to let javac know that it can synthesize a Slot when a single argument + // lambda is passed to connect/connectNotify - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. The listener is held by a strong reference, so it's held in memory by virtue of - * being connected. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connect (Slot listener); + /** + * Connects the supplied listener to this value, such that it will be notified when this value + * changes. The listener is held by a strong reference, so it's held in memory by virtue of + * being connected. + * @return a connection instance which can be used to cancel the connection. + */ + Connection connect (Slot listener); - /** - * Connects the supplied listener to this value, such that it will be notified when this value - * changes. Also immediately notifies the listener of the current value. If the notification - * triggers an unchecked exception, the slot will automatically be disconnected and the caller - * need not worry about cleaning up after itself. - * @return a connection instance which can be used to cancel the connection. - */ - Connection connectNotify (Slot listener); + /** + * Connects the supplied listener to this value, such that it will be notified when this value + * changes. Also immediately notifies the listener of the current value. If the notification + * triggers an unchecked exception, the slot will automatically be disconnected and the caller + * need not worry about cleaning up after itself. + * @return a connection instance which can be used to cancel the connection. + */ + Connection connectNotify (Slot listener); } diff --git a/src/main/java/react/Values.java b/src/main/java/react/Values.java index d4c4bcb..024fdc8 100644 --- a/src/main/java/react/Values.java +++ b/src/main/java/react/Values.java @@ -16,222 +16,222 @@ */ public class Values { - /** Used by {@link #join(ValueView,ValueView)}. */ - public static class T2 { - public final A a; - public final B b; - public T2 (A a, B b) { - this.a = a; - this.b = b; - } - - @Override public String toString () { - return "T2(" + a + ", " + b + ")"; - } - @Override public int hashCode () { - return Objects.hashCode(a) ^ Objects.hashCode(b); - } - @Override public boolean equals (Object other) { - if (!(other instanceof T2)) return false; - T2 ot = (T2)other; - return Objects.equals(a, ot.a) && Objects.equals(b, ot.b); - } + /** Used by {@link #join(ValueView,ValueView)}. */ + public static class T2 { + public final A a; + public final B b; + public T2 (A a, B b) { + this.a = a; + this.b = b; } - /** Used by {@link #join(ValueView,ValueView,ValueView)}. */ - public static class T3 { - public final A a; - public final B b; - public final C c; - public T3 (A a, B b, C c) { - this.a = a; - this.b = b; - this.c = c; - } - - @Override public String toString () { - return "T3(" + a + ", " + b + ", " + c + ")"; - } - @Override public int hashCode () { - return Objects.hashCode(a) ^ Objects.hashCode(b); - } - @Override public boolean equals (Object other) { - if (!(other instanceof T3)) return false; - T3 ot = (T3)other; - return Objects.equals(a, ot.a) && Objects.equals(b, ot.b) && Objects.equals(c, ot.c); - } + @Override public String toString () { + return "T2(" + a + ", " + b + ")"; } - - /** - * Returns a reactive value which is triggered when either of {@code a, b} emits an event. The - * mapped value will retain connections to {@code a+b} only while it itself has connections. - */ - public static ValueView> join (ValueView a, ValueView b) { - return new MappedValue>() { - @Override public T2 get () { - return _current; - } - @Override protected Connection connect () { - return Connection.join(a.connect(_trigger), b.connect(_trigger)); - } - protected T2 _current = new T2(a.get(), b.get()); - protected final Slot _trigger = value -> { - T2 ovalue = _current; - _current = new T2(a.get(), b.get()); - notifyChange(_current, ovalue); - }; - }; - } - - /** - * Returns a reactive value which is triggered when either of {@code a, b, c} emits an event. - * The mapped value will retain connections to {@code a+b+c} only while it itself has - * connections. - */ - public static ValueView> join ( - ValueView a, ValueView b, ValueView c) { - - return new MappedValue>() { - @Override public T3 get () { - return _current; - } - @Override protected Connection connect () { - return Connection.join(a.connect(_trigger), b.connect(_trigger), - c.connect(_trigger)); - } - protected T3 _current = new T3<>(a.get(), b.get(), c.get()); - protected final Slot _trigger = value -> { - T3 ovalue = _current; - _current = new T3(a.get(), b.get(), c.get()); - notifyChange(_current, ovalue); - }; - }; - } - - /** - * Creates a boolean value that is toggled every time the supplied signal fires. - * - * @param signal the signal that will trigger the toggling. - * @param initial the initial value of the to be toggled value. - */ - public static ValueView toggler (SignalView signal, boolean initial) { - return new MappedValue() { - @Override public Boolean get () { - return _current; - } - @Override protected Connection connect () { - return signal.connect(value -> { - boolean old = _current; - notifyChange(_current = !old, old); - }); - } - protected boolean _current = initial; - }; - } - - /** - * Returns a value which is the logical AND of the supplied values. - */ - public static ValueView and (ValueView one, ValueView two) { - return and(Arrays.asList(one, two)); - } - - /** - * Returns a value which is the logical AND of the supplied values. - */ - @SafeVarargs @SuppressWarnings("varargs") - public static ValueView and (ValueView... values) { - return and(Arrays.asList(values)); - } - - /** - * Returns a value which is the logical AND of the supplied values. - */ - public static ValueView and (Collection> values) { - return aggValue(values, Values::computeAnd); + @Override public int hashCode () { + return Objects.hashCode(a) ^ Objects.hashCode(b); } - - /** - * Returns a value which is the logical OR of the supplied values. - */ - public static ValueView or (ValueView one, ValueView two) { - return or(Arrays.asList(one, two)); + @Override public boolean equals (Object other) { + if (!(other instanceof T2)) return false; + T2 ot = (T2)other; + return Objects.equals(a, ot.a) && Objects.equals(b, ot.b); } - - /** - * Returns a value which is the logical OR of the supplied values. - */ - @SafeVarargs @SuppressWarnings("varargs") - public static ValueView or (ValueView... values) { - return or(Arrays.asList(values)); + } + + /** Used by {@link #join(ValueView,ValueView,ValueView)}. */ + public static class T3 { + public final A a; + public final B b; + public final C c; + public T3 (A a, B b, C c) { + this.a = a; + this.b = b; + this.c = c; } - /** - * Returns a value which is the logical OR of the supplied values. - */ - public static ValueView or (Collection> values) { - return aggValue(values, Values::computeOr); + @Override public String toString () { + return "T3(" + a + ", " + b + ", " + c + ")"; } - - /** - * Returns a view of the supplied signal as a value. It will contain the value {@code initial} - * until the signal fires, at which time the value will be updated with the emitted value. - */ - public static ValueView asValue (SignalView signal, T initial) { - return new MappedValue() { - @Override public T get () { - return _value; - } - @Override protected T updateLocal (T value) { - T ovalue = _value; - _value = value; - return ovalue; - } - @Override protected Connection connect () { - return signal.connect(value -> updateAndNotifyIf(value)); - } - protected T _value = initial; - }; + @Override public int hashCode () { + return Objects.hashCode(a) ^ Objects.hashCode(b); } - - protected static final ValueView aggValue ( - Collection> values, - Function>,Boolean> aggOp) { - - return new MappedValue() { - @Override public Boolean get () { - return aggOp.apply(values); - } - - @Override protected Connection connect () { - Connection[] conns = new Connection[values.size()]; - Iterator> iter = values.iterator(); - for (int ii = 0; ii < conns.length; ii++) conns[ii] = iter.next().connect(_trigger); - return Connection.join(conns); - } - - protected boolean _current = aggOp.apply(values); - protected final Slot _trigger = value -> { - boolean ovalue = _current; - _current = aggOp.apply(values); - notifyChange(_current, ovalue); - }; - }; + @Override public boolean equals (Object other) { + if (!(other instanceof T3)) return false; + T3 ot = (T3)other; + return Objects.equals(a, ot.a) && Objects.equals(b, ot.b) && Objects.equals(c, ot.c); } - - protected static boolean computeAnd (Iterable> values) { - for (ValueView value : values) { - if (!value.get()) return false; - } - return true; + } + + /** + * Returns a reactive value which is triggered when either of {@code a, b} emits an event. The + * mapped value will retain connections to {@code a+b} only while it itself has connections. + */ + public static ValueView> join (ValueView a, ValueView b) { + return new MappedValue>() { + @Override public T2 get () { + return _current; + } + @Override protected Connection connect () { + return Connection.join(a.connect(_trigger), b.connect(_trigger)); + } + protected T2 _current = new T2(a.get(), b.get()); + protected final Slot _trigger = value -> { + T2 ovalue = _current; + _current = new T2(a.get(), b.get()); + notifyChange(_current, ovalue); + }; + }; + } + + /** + * Returns a reactive value which is triggered when either of {@code a, b, c} emits an event. + * The mapped value will retain connections to {@code a+b+c} only while it itself has + * connections. + */ + public static ValueView> join ( + ValueView a, ValueView b, ValueView c) { + + return new MappedValue>() { + @Override public T3 get () { + return _current; + } + @Override protected Connection connect () { + return Connection.join(a.connect(_trigger), b.connect(_trigger), + c.connect(_trigger)); + } + protected T3 _current = new T3<>(a.get(), b.get(), c.get()); + protected final Slot _trigger = value -> { + T3 ovalue = _current; + _current = new T3(a.get(), b.get(), c.get()); + notifyChange(_current, ovalue); + }; + }; + } + + /** + * Creates a boolean value that is toggled every time the supplied signal fires. + * + * @param signal the signal that will trigger the toggling. + * @param initial the initial value of the to be toggled value. + */ + public static ValueView toggler (SignalView signal, boolean initial) { + return new MappedValue() { + @Override public Boolean get () { + return _current; + } + @Override protected Connection connect () { + return signal.connect(value -> { + boolean old = _current; + notifyChange(_current = !old, old); + }); + } + protected boolean _current = initial; + }; + } + + /** + * Returns a value which is the logical AND of the supplied values. + */ + public static ValueView and (ValueView one, ValueView two) { + return and(Arrays.asList(one, two)); + } + + /** + * Returns a value which is the logical AND of the supplied values. + */ + @SafeVarargs @SuppressWarnings("varargs") + public static ValueView and (ValueView... values) { + return and(Arrays.asList(values)); + } + + /** + * Returns a value which is the logical AND of the supplied values. + */ + public static ValueView and (Collection> values) { + return aggValue(values, Values::computeAnd); + } + + /** + * Returns a value which is the logical OR of the supplied values. + */ + public static ValueView or (ValueView one, ValueView two) { + return or(Arrays.asList(one, two)); + } + + /** + * Returns a value which is the logical OR of the supplied values. + */ + @SafeVarargs @SuppressWarnings("varargs") + public static ValueView or (ValueView... values) { + return or(Arrays.asList(values)); + } + + /** + * Returns a value which is the logical OR of the supplied values. + */ + public static ValueView or (Collection> values) { + return aggValue(values, Values::computeOr); + } + + /** + * Returns a view of the supplied signal as a value. It will contain the value {@code initial} + * until the signal fires, at which time the value will be updated with the emitted value. + */ + public static ValueView asValue (SignalView signal, T initial) { + return new MappedValue() { + @Override public T get () { + return _value; + } + @Override protected T updateLocal (T value) { + T ovalue = _value; + _value = value; + return ovalue; + } + @Override protected Connection connect () { + return signal.connect(value -> updateAndNotifyIf(value)); + } + protected T _value = initial; + }; + } + + protected static final ValueView aggValue ( + Collection> values, + Function>,Boolean> aggOp) { + + return new MappedValue() { + @Override public Boolean get () { + return aggOp.apply(values); + } + + @Override protected Connection connect () { + Connection[] conns = new Connection[values.size()]; + Iterator> iter = values.iterator(); + for (int ii = 0; ii < conns.length; ii++) conns[ii] = iter.next().connect(_trigger); + return Connection.join(conns); + } + + protected boolean _current = aggOp.apply(values); + protected final Slot _trigger = value -> { + boolean ovalue = _current; + _current = aggOp.apply(values); + notifyChange(_current, ovalue); + }; + }; + } + + protected static boolean computeAnd (Iterable> values) { + for (ValueView value : values) { + if (!value.get()) return false; } + return true; + } - protected static boolean computeOr (Iterable> values) { - for (ValueView value : values) { - if (value.get()) return true; - } - return false; + protected static boolean computeOr (Iterable> values) { + for (ValueView value : values) { + if (value.get()) return true; } + return false; + } - private Values () {} // no constructski + private Values () {} // no constructski } From e0acddd18e84b230a8153fa1cb86cf2ffa21dbe2 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 10 Nov 2017 08:19:11 -0800 Subject: [PATCH 3/5] Added AUTHORS file, updated headers. --- AUTHORS | 6 ++++++ etc/SOURCE_HEADER | 2 +- src/main/java/react/AbstractSignal.java | 4 ++-- src/main/java/react/AbstractValue.java | 4 ++-- src/main/java/react/Closeable.java | 2 +- src/main/java/react/Connection.java | 4 ++-- src/main/java/react/Cons.java | 4 ++-- src/main/java/react/IntValue.java | 2 +- src/main/java/react/MappedSignal.java | 4 ++-- src/main/java/react/MappedValue.java | 4 ++-- src/main/java/react/MultiFailureException.java | 4 ++-- src/main/java/react/RCollection.java | 2 +- src/main/java/react/RFuture.java | 2 +- src/main/java/react/RList.java | 4 ++-- src/main/java/react/RMap.java | 4 ++-- src/main/java/react/RPromise.java | 2 +- src/main/java/react/RQueue.java | 2 +- src/main/java/react/RSet.java | 4 ++-- src/main/java/react/Reactor.java | 4 ++-- src/main/java/react/Signal.java | 4 ++-- src/main/java/react/SignalView.java | 4 ++-- src/main/java/react/Slot.java | 4 ++-- src/main/java/react/Try.java | 2 +- src/main/java/react/Value.java | 4 ++-- src/main/java/react/ValueView.java | 4 ++-- src/main/java/react/Values.java | 4 ++-- 26 files changed, 48 insertions(+), 42 deletions(-) create mode 100644 AUTHORS diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..1a82341 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +Michael Bayne +Charlie Groves +Tim Conkling +David Hoover +Bruno Garcia +Ray J. Greenwell diff --git a/etc/SOURCE_HEADER b/etc/SOURCE_HEADER index 88615ff..b310379 100644 --- a/etc/SOURCE_HEADER +++ b/etc/SOURCE_HEADER @@ -1,5 +1,5 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2015, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE diff --git a/src/main/java/react/AbstractSignal.java b/src/main/java/react/AbstractSignal.java index 9ce052c..773b4c3 100644 --- a/src/main/java/react/AbstractSignal.java +++ b/src/main/java/react/AbstractSignal.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/AbstractValue.java b/src/main/java/react/AbstractValue.java index 9301b30..43720d0 100644 --- a/src/main/java/react/AbstractValue.java +++ b/src/main/java/react/AbstractValue.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Closeable.java b/src/main/java/react/Closeable.java index 8662c1c..e5dfcfb 100644 --- a/src/main/java/react/Closeable.java +++ b/src/main/java/react/Closeable.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2015, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Connection.java b/src/main/java/react/Connection.java index 7212550..e4c192e 100644 --- a/src/main/java/react/Connection.java +++ b/src/main/java/react/Connection.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Cons.java b/src/main/java/react/Cons.java index a4508ac..7bd43d9 100644 --- a/src/main/java/react/Cons.java +++ b/src/main/java/react/Cons.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/IntValue.java b/src/main/java/react/IntValue.java index 5577059..19c8287 100644 --- a/src/main/java/react/IntValue.java +++ b/src/main/java/react/IntValue.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/MappedSignal.java b/src/main/java/react/MappedSignal.java index ec3f953..2ebadd5 100644 --- a/src/main/java/react/MappedSignal.java +++ b/src/main/java/react/MappedSignal.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/MappedValue.java b/src/main/java/react/MappedValue.java index bf11a39..fa36552 100644 --- a/src/main/java/react/MappedValue.java +++ b/src/main/java/react/MappedValue.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/MultiFailureException.java b/src/main/java/react/MultiFailureException.java index 25c46a5..4474fc2 100644 --- a/src/main/java/react/MultiFailureException.java +++ b/src/main/java/react/MultiFailureException.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RCollection.java b/src/main/java/react/RCollection.java index 537efaf..cc5e240 100644 --- a/src/main/java/react/RCollection.java +++ b/src/main/java/react/RCollection.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2015, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RFuture.java b/src/main/java/react/RFuture.java index 4f414a2..945327d 100644 --- a/src/main/java/react/RFuture.java +++ b/src/main/java/react/RFuture.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2013, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RList.java b/src/main/java/react/RList.java index 8d8a7fe..043813d 100644 --- a/src/main/java/react/RList.java +++ b/src/main/java/react/RList.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RMap.java b/src/main/java/react/RMap.java index 795417c..ea33cb1 100644 --- a/src/main/java/react/RMap.java +++ b/src/main/java/react/RMap.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RPromise.java b/src/main/java/react/RPromise.java index d9ebbfc..e7c8618 100644 --- a/src/main/java/react/RPromise.java +++ b/src/main/java/react/RPromise.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2013, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RQueue.java b/src/main/java/react/RQueue.java index 36a9e02..f9b0da2 100644 --- a/src/main/java/react/RQueue.java +++ b/src/main/java/react/RQueue.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2015, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/RSet.java b/src/main/java/react/RSet.java index 0e3a034..336806b 100644 --- a/src/main/java/react/RSet.java +++ b/src/main/java/react/RSet.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Reactor.java b/src/main/java/react/Reactor.java index 64d9144..69f5574 100644 --- a/src/main/java/react/Reactor.java +++ b/src/main/java/react/Reactor.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Signal.java b/src/main/java/react/Signal.java index 441e928..41e049b 100644 --- a/src/main/java/react/Signal.java +++ b/src/main/java/react/Signal.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/SignalView.java b/src/main/java/react/SignalView.java index 799248e..d1eb056 100644 --- a/src/main/java/react/SignalView.java +++ b/src/main/java/react/SignalView.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Slot.java b/src/main/java/react/Slot.java index c690ed1..d6c28c8 100644 --- a/src/main/java/react/Slot.java +++ b/src/main/java/react/Slot.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Try.java b/src/main/java/react/Try.java index 7762f4f..498f3c7 100644 --- a/src/main/java/react/Try.java +++ b/src/main/java/react/Try.java @@ -1,6 +1,6 @@ // // React - a library for functional-reactive-like programming -// Copyright (c) 2013, Three Rings Design, Inc. - All rights reserved. +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Value.java b/src/main/java/react/Value.java index b9058e4..2442661 100644 --- a/src/main/java/react/Value.java +++ b/src/main/java/react/Value.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/ValueView.java b/src/main/java/react/ValueView.java index 3926341..1ec4fb2 100644 --- a/src/main/java/react/ValueView.java +++ b/src/main/java/react/ValueView.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; diff --git a/src/main/java/react/Values.java b/src/main/java/react/Values.java index 024fdc8..226683e 100644 --- a/src/main/java/react/Values.java +++ b/src/main/java/react/Values.java @@ -1,6 +1,6 @@ // -// React - a library for functional-reactive-like programming in Java -// Copyright (c) 2011, Three Rings Design, Inc. - All rights reserved. +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors // http://github.com/threerings/react/blob/master/LICENSE package react; From f56cd92fb93048a9a02c91f16472518d9ffff210 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Fri, 10 Nov 2017 19:50:38 -0800 Subject: [PATCH 4/5] Drop openjdk7 Travis build. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ba44962..da3b368 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: java sudo: false jdk: - - openjdk7 - oraclejdk8 cache: From 5159186b3787d803fd65e2257a13b2261fe62aee Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Sun, 17 Dec 2017 10:26:30 -0800 Subject: [PATCH 5/5] Use bespoke Function interface. Android requires API 24 for the java.util.function package and RoboVM doesn't include it at all. We'll have to forgo that particular slice of the future. --- src/main/java/react/AbstractSignal.java | 2 -- src/main/java/react/AbstractValue.java | 2 -- src/main/java/react/Function.java | 18 ++++++++++++++++++ src/main/java/react/RFuture.java | 1 - src/main/java/react/SignalView.java | 2 -- src/main/java/react/Slot.java | 2 -- src/main/java/react/Try.java | 2 -- src/main/java/react/ValueView.java | 2 -- src/main/java/react/Values.java | 1 - src/test/java/react/RFutureTest.java | 1 - src/test/java/react/SignalTest.java | 1 - src/test/java/react/ValueTest.java | 1 - 12 files changed, 18 insertions(+), 17 deletions(-) create mode 100644 src/main/java/react/Function.java diff --git a/src/main/java/react/AbstractSignal.java b/src/main/java/react/AbstractSignal.java index 773b4c3..4d62298 100644 --- a/src/main/java/react/AbstractSignal.java +++ b/src/main/java/react/AbstractSignal.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * Handles the machinery of connecting slots to a signal and emitting events to them, without * exposing a public interface for emitting events. This can be used by entities which wish to diff --git a/src/main/java/react/AbstractValue.java b/src/main/java/react/AbstractValue.java index 43720d0..d194e89 100644 --- a/src/main/java/react/AbstractValue.java +++ b/src/main/java/react/AbstractValue.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * Handles the machinery of connecting listeners to a value and notifying them, without exposing a * public interface for updating the value. This can be used by libraries which wish to provide diff --git a/src/main/java/react/Function.java b/src/main/java/react/Function.java new file mode 100644 index 0000000..6a1fa5b --- /dev/null +++ b/src/main/java/react/Function.java @@ -0,0 +1,18 @@ +// +// React - a library for functional-reactive-like programming +// Copyright (c) 2011-present, React Authors +// http://github.com/threerings/react/blob/master/LICENSE + +package react; + +/** + * Models a single argument function. + */ +public interface Function { + + /** + * Applies this function to the supplied input value. A function is expected to have no side + * effects; violate that assumption at your peril. + */ + T apply (F input); +} diff --git a/src/main/java/react/RFuture.java b/src/main/java/react/RFuture.java index 945327d..13f1ab9 100644 --- a/src/main/java/react/RFuture.java +++ b/src/main/java/react/RFuture.java @@ -12,7 +12,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.function.Function; /** * Represents an asynchronous result. Unlike standard Java futures, you cannot block on this diff --git a/src/main/java/react/SignalView.java b/src/main/java/react/SignalView.java index d1eb056..c264b3c 100644 --- a/src/main/java/react/SignalView.java +++ b/src/main/java/react/SignalView.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * A view of a {@link Signal}, on which slots may listen, but to which one cannot emit events. This * is generally used to provide signal-like views of changing entities. See {@link AbstractValue} diff --git a/src/main/java/react/Slot.java b/src/main/java/react/Slot.java index d6c28c8..9a88a0c 100644 --- a/src/main/java/react/Slot.java +++ b/src/main/java/react/Slot.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * A synonym for {@link SignalView.Listener} and {@link ValueView.Listener} (ignoring the old * value) name. Also provides some filtering and composition methods. diff --git a/src/main/java/react/Try.java b/src/main/java/react/Try.java index 498f3c7..d873089 100644 --- a/src/main/java/react/Try.java +++ b/src/main/java/react/Try.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * Represents a computation that either provided a result, or failed with an exception. Monadic * methods are provided that allow one to map and compose tries in ways that propagate failure. diff --git a/src/main/java/react/ValueView.java b/src/main/java/react/ValueView.java index 1ec4fb2..6cf91ba 100644 --- a/src/main/java/react/ValueView.java +++ b/src/main/java/react/ValueView.java @@ -5,8 +5,6 @@ package react; -import java.util.function.Function; - /** * A view of a {@link Value}, to which listeners may be added, but which one cannot update. This * can be used in combination with {@link AbstractValue} to provide {@link Value} semantics to an diff --git a/src/main/java/react/Values.java b/src/main/java/react/Values.java index 226683e..31c5d4c 100644 --- a/src/main/java/react/Values.java +++ b/src/main/java/react/Values.java @@ -9,7 +9,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.Objects; -import java.util.function.Function; /** * Provides utility methods for {@link Value}s. diff --git a/src/test/java/react/RFutureTest.java b/src/test/java/react/RFutureTest.java index 09c2a24..31ef991 100644 --- a/src/test/java/react/RFutureTest.java +++ b/src/test/java/react/RFutureTest.java @@ -9,7 +9,6 @@ import java.util.Collection; import java.util.Collections; import java.util.ArrayList; -import java.util.function.Function; import org.junit.*; import static org.junit.Assert.*; diff --git a/src/test/java/react/SignalTest.java b/src/test/java/react/SignalTest.java index 9270dd4..789d2e4 100644 --- a/src/test/java/react/SignalTest.java +++ b/src/test/java/react/SignalTest.java @@ -9,7 +9,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.Function; import org.junit.*; import static org.junit.Assert.*; diff --git a/src/test/java/react/ValueTest.java b/src/test/java/react/ValueTest.java index 497b205..d24ebd5 100644 --- a/src/test/java/react/ValueTest.java +++ b/src/test/java/react/ValueTest.java @@ -8,7 +8,6 @@ import org.junit.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; import static org.junit.Assert.*;