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}