001package arez;
002
003import arez.spy.Spy;
004import grim.annotations.OmitSymbol;
005import java.util.Objects;
006import javax.annotation.Nonnull;
007import javax.annotation.Nullable;
008import static org.realityforge.braincheck.Guards.*;
009
010/**
011 * A node within an Arez dependency graph.
012 * The node is a named element within a specific Arez system that forms part of the
013 * dependency graph.
014 *
015 * <p>The Node class can be extended by classes outside the Arez core package. Typically this is
016 * done when a collection of primitive types (i.e. Observables, Observers, ComputableValues etc) are
017 * aggregated to form a single abstraction within the reactive system.</p>
018 */
019public abstract class Node
020  implements Disposable
021{
022  /**
023   * Reference to the context to which this node belongs.
024   */
025  @OmitSymbol( unless = "arez.enable_zones" )
026  @Nullable
027  private final ArezContext _context;
028  /**
029   * A human consumable name for node. It should be non-null if {@link Arez#areNamesEnabled()} returns
030   * true and <code>null</code> otherwise.
031   */
032  @Nullable
033  @OmitSymbol( unless = "arez.enable_names" )
034  private final String _name;
035
036  Node( @Nullable final ArezContext context, @Nullable final String name )
037  {
038    if ( Arez.shouldCheckApiInvariants() )
039    {
040      apiInvariant( () -> Arez.areZonesEnabled() || null == context,
041                    () -> "Arez-0180: Node passed a context but Arez.areZonesEnabled() is false" );
042      apiInvariant( () -> Arez.areNamesEnabled() || null == name,
043                    () -> "Arez-0052: Node passed a name '" + name + "' but Arez.areNamesEnabled() is false" );
044    }
045    _context = Arez.areZonesEnabled() ? Objects.requireNonNull( context ) : null;
046    _name = Arez.areNamesEnabled() ? Objects.requireNonNull( name ) : null;
047  }
048
049  /**
050   * Return the name of the node.
051   * This method should NOT be invoked unless {@link Arez#areNamesEnabled()} returns <code>true</code>.
052   *
053   * @return the name of the node.
054   */
055  @Nonnull
056  public final String getName()
057  {
058    if ( Arez.shouldCheckApiInvariants() )
059    {
060      apiInvariant( Arez::areNamesEnabled,
061                    () -> "Arez-0053: Node.getName() invoked when Arez.areNamesEnabled() is false" );
062    }
063    assert null != _name;
064    return _name;
065  }
066
067  /**
068   * Return the context that the node is associated with.
069   *
070   * @return the associated ArezContext.
071   */
072  @Nonnull
073  public final ArezContext getContext()
074  {
075    return Arez.areZonesEnabled() ? Objects.requireNonNull( _context ) : Arez.context();
076  }
077
078  @Nonnull
079  @Override
080  public final String toString()
081  {
082    if ( Arez.areNamesEnabled() )
083    {
084      return getName();
085    }
086    else
087    {
088      return super.toString();
089    }
090  }
091
092  /**
093   * Return true if spy events will be propagated.
094   * This means spies are enabled and there is at least one spy event handler present.
095   *
096   * @return true if spy events will be propagated, false otherwise.
097   */
098  final boolean willPropagateSpyEvents()
099  {
100    return Arez.areSpiesEnabled() && getSpy().willPropagateSpyEvents();
101  }
102
103  /**
104   * Return the spy associated with context.
105   * This method should not be invoked unless {@link Arez#areSpiesEnabled()} returns true.
106   *
107   * @return the spy associated with context.
108   */
109  @Nonnull
110  final Spy getSpy()
111  {
112    return getContext().getSpy();
113  }
114
115  /**
116   * Report a spy event.
117   *
118   * @param event the event that occurred.
119   */
120  final void reportSpyEvent( @Nonnull final Object event )
121  {
122    getSpy().reportSpyEvent( event );
123  }
124}