package org.leumasjaffe.observer; import java.lang.ref.WeakReference; import java.util.UUID; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.Multimap; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.experimental.FieldDefaults; import lombok.experimental.UtilityClass; /** * The master object that associates observale objects with listeners and invocations * TODO: May crash if updating an object where a Listener was destroyed without unsub * TODO: Potentially leaks memory if Listeners go out of scope without unsub * TODO: No way to drop Observable objects as they go out of scope */ @UtilityClass @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) public class ObserverDispatch { @AllArgsConstructor @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true) private static class Pair { WeakReference obj; Subscriber sub; } Multimap observers = LinkedListMultimap.create(); /** * Register a listener callback * @param target The object we're observing. Make sure that it's an observable and not just * some random UUID someone came up with to harass me. * @param src The Listener object that we are using, used for invalidation in case we * forget to invoke {@see unsubscribeAll} when destroying an object. * @param sub A callback function with a signature of void() that will be triggered when * we request an update. */ public void subscribe(Observable target, Object src, Subscriber sub) { observers.put(target.observableId(), new Pair(new WeakReference<>(src), sub)); } /** * Remove every callback that this listener is associated with * @param src A Listener object that was generally provided in a previous {@see subscribe} * call. */ public void unsubscribeAll(Object src) { if (src == null) return; observers.entries().removeIf( e -> e.getValue().obj.get() == src ); } /** * Dispatch notices of an update to an observable object that comes from an owning * {@see IndirectObservableListener}. * @param target The Observable that was updated by our caller * @param src An owning Listener that does not need to be updated. */ void notifySubscribers(Observable target, IndirectObservableListener src) { observers.get(target.observableId()).stream().filter( p -> p.obj.get() != src).forEach( p -> p.sub.notifyUpdate() ); } /** * Dispatch notices of an update to an observable object. * @param target The Observable that was updated by our caller */ public void notifySubscribers(Observable target) { observers.get(target.observableId()).stream().forEach( p -> p.sub.notifyUpdate() ); } }