ObserverDispatch.java 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. package org.leumasjaffe.observer;
  2. import java.lang.ref.WeakReference;
  3. import java.util.UUID;
  4. import com.google.common.collect.LinkedListMultimap;
  5. import com.google.common.collect.Multimap;
  6. import lombok.AccessLevel;
  7. import lombok.AllArgsConstructor;
  8. import lombok.experimental.FieldDefaults;
  9. import lombok.experimental.UtilityClass;
  10. /**
  11. * The master object that associates observale objects with listeners and invocations
  12. * TODO: May crash if updating an object where a Listener was destroyed without unsub
  13. * TODO: Potentially leaks memory if Listeners go out of scope without unsub
  14. * TODO: No way to drop Observable objects as they go out of scope
  15. */
  16. @UtilityClass
  17. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  18. public class ObserverDispatch {
  19. @AllArgsConstructor
  20. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  21. private static class Pair {
  22. WeakReference<Object> obj;
  23. Subscriber sub;
  24. }
  25. Multimap<UUID, Pair> observers = LinkedListMultimap.create();
  26. /**
  27. * Register a listener callback
  28. * @param target The object we're observing. Make sure that it's an observable and not just
  29. * some random UUID someone came up with to harass me.
  30. * @param src The Listener object that we are using, used for invalidation in case we
  31. * forget to invoke {@see unsubscribeAll} when destroying an object.
  32. * @param sub A callback function with a signature of void() that will be triggered when
  33. * we request an update.
  34. */
  35. public void subscribe(Observable target, Object src, Subscriber sub) {
  36. observers.put(target.observableId(), new Pair(new WeakReference<>(src), sub));
  37. }
  38. /**
  39. * Remove every callback that this listener is associated with
  40. * @param src A Listener object that was generally provided in a previous {@see subscribe}
  41. * call.
  42. */
  43. public void unsubscribeAll(Object src) {
  44. if (src == null) return;
  45. observers.entries().removeIf( e -> e.getValue().obj.get() == src );
  46. }
  47. /**
  48. * Dispatch notices of an update to an observable object that comes from an owning
  49. * {@see IndirectObservableListener}.
  50. * @param target The Observable that was updated by our caller
  51. * @param src An owning Listener that does not need to be updated.
  52. */
  53. void notifySubscribers(Observable target, IndirectObservableListener<?, ?> src) {
  54. observers.get(target.observableId()).stream().filter(
  55. p -> p.obj.get() != src).forEach( p -> p.sub.notifyUpdate() );
  56. }
  57. /**
  58. * Dispatch notices of an update to an observable object.
  59. * @param target The Observable that was updated by our caller
  60. */
  61. public void notifySubscribers(Observable target) {
  62. observers.get(target.observableId()).stream().forEach( p -> p.sub.notifyUpdate() );
  63. }
  64. }