001package arez.component;
002
003import arez.Arez;
004import arez.Disposable;
005import arez.SafeProcedure;
006import arez.annotations.CascadeDispose;
007import javax.annotation.Nonnull;
008import static org.realityforge.braincheck.Guards.*;
009
010/**
011 * Interface implemented by a component if it supports notifying listeners
012 * when the component is disposed. This notification occurs using a call-back
013 * and occurs within the dispose transaction (after {@link arez.annotations.PreDispose}
014 * is invoked if present) using a callback. Contrast this with the strategy used by
015 * {@link ComponentObservable} which uses standard Arez observables to track when
016 * a component is disposed.
017 */
018public interface DisposeNotifier
019{
020  /**
021   * Add the listener to the notify list under key.
022   * This method MUST NOT be invoked after the component has been disposed.
023   * This method should not be invoked if another listener has been added with the same key without
024   * being removed.
025   *
026   * <p>If the key implements {@link Disposable} and {@link Disposable#isDisposed()} returns <code>true</code>
027   * when invoking the calback then the callback will be skipped. This rare situation only occurs when there is
028   * circular dependency in the object model usually involving {@link CascadeDispose}.</p>
029   *
030   * @param key              the key to uniquely identify listener.
031   * @param action           the listener callback.
032   * @param errorIfDuplicate generate an assertion error if duplicate key inserted.
033   */
034  void addOnDisposeListener( @Nonnull Object key, @Nonnull SafeProcedure action, boolean errorIfDuplicate );
035
036  /**
037   * Add the listener to the notify list under key.
038   * This method MUST NOT be invoked after the component has been disposed.
039   * This method should not be invoked if another listener has been added with the same key without
040   * being removed.
041   *
042   * <p>If the key implements {@link Disposable} and {@link Disposable#isDisposed()} returns <code>true</code>
043   * when invoking the calback then the callback will be skipped. This rare situation only occurs when there is
044   * circular dependency in the object model usually involving {@link CascadeDispose}.</p>
045   *
046   * @param key    the key to uniquely identify listener.
047   * @param action the listener callback.
048   */
049  default void addOnDisposeListener( @Nonnull final Object key, @Nonnull final SafeProcedure action )
050  {
051    addOnDisposeListener( key, action, true );
052  }
053
054  /**
055   * Remove the listener with the specified key from the notify list.
056   * This method should only be invoked when a listener has been added for specific key using
057   * {@link #addOnDisposeListener(Object, SafeProcedure, boolean)} and has not been removed by another
058   * call to this method.
059   *
060   * @param key            the key under which the listener was previously added.
061   * @param errorIfMissing generate an assertion error if no such key exists.
062   */
063  void removeOnDisposeListener( @Nonnull Object key, boolean errorIfMissing );
064
065  /**
066   * Remove the listener with the specified key from the notify list.
067   * This method should only be invoked when a listener has been added for specific key using
068   * {@link #addOnDisposeListener(Object, SafeProcedure, boolean)} and has not been removed by another
069   * call to this method.
070   *
071   * @param key the key under which the listener was previously added.
072   */
073  default void removeOnDisposeListener( @Nonnull final Object key )
074  {
075    removeOnDisposeListener( key, true );
076  }
077
078  /**
079   * Cast the specified object to an instance of DisposeNotifier.
080   * Invariant checks will verify that the cast is valid before proceeding.
081   *
082   * @param object the object.
083   * @return the object cast to DisposeNotifier.
084   */
085  @Nonnull
086  static DisposeNotifier asDisposeNotifier( @Nonnull final Object object )
087  {
088    if ( Arez.shouldCheckApiInvariants() )
089    {
090      apiInvariant( () -> object instanceof DisposeNotifier,
091                    () -> "Arez-0178: Object passed to asDisposeNotifier does not implement " +
092                          "DisposeNotifier. Object: " + object );
093    }
094    return (DisposeNotifier) object;
095  }
096}