001package arez;
002
003import arez.spy.ActionCompleteEvent;
004import arez.spy.ActionStartEvent;
005import arez.spy.ComponentCreateStartEvent;
006import arez.spy.ObservableValueCreateEvent;
007import arez.spy.ObserveScheduleEvent;
008import arez.spy.ObserverErrorEvent;
009import arez.spy.PropertyAccessor;
010import arez.spy.PropertyMutator;
011import arez.spy.Spy;
012import grim.annotations.OmitSymbol;
013import java.util.Collection;
014import java.util.Collections;
015import java.util.HashMap;
016import java.util.Map;
017import java.util.Objects;
018import javax.annotation.Nonnull;
019import javax.annotation.Nullable;
020import org.intellij.lang.annotations.MagicConstant;
021import static org.realityforge.braincheck.Guards.*;
022
023/**
024 * The ArezContext defines the top level container of interconnected observables and observers.
025 * The context also provides the mechanism for creating transactions to read and write state
026 * within the system.
027 */
028@SuppressWarnings( { "Duplicates" } )
029public final class ArezContext
030{
031  /**
032   * ID of the next node to be created.
033   * This is only used if {@link Arez#areNamesEnabled()} returns true but no name has been supplied.
034   */
035  private int _nextNodeId = 1;
036  /**
037   * ID of the next transaction to be created.
038   * This needs to start at 1 as {@link ObservableValue#NOT_IN_CURRENT_TRACKING} is used
039   * to optimize dependency tracking in transactions.
040   */
041  private int _nextTransactionId = 1;
042  /**
043   * Zone associated with the context. This should be null unless {@link Arez#areZonesEnabled()} returns <code>true</code>.
044   */
045  @OmitSymbol( unless = "arez.enable_zones" )
046  @Nullable
047  private final Zone _zone;
048  /**
049   * Tasks scheduled but yet to be run.
050   */
051  @Nonnull
052  private final TaskQueue _taskQueue = new TaskQueue( Task.Flags.PRIORITY_COUNT, 100 );
053  /**
054   * Executor responsible for executing tasks.
055   */
056  @Nonnull
057  private final RoundBasedTaskExecutor _executor = new RoundBasedTaskExecutor( _taskQueue, 100 );
058  /**
059   * Support infrastructure for propagating observer errors.
060   */
061  @OmitSymbol( unless = "arez.enable_observer_error_handlers" )
062  @Nullable
063  private final ObserverErrorHandlerSupport _observerErrorHandlerSupport =
064    Arez.areObserverErrorHandlersEnabled() ? new ObserverErrorHandlerSupport() : null;
065  /**
066   * Support infrastructure for spy events.
067   */
068  @OmitSymbol( unless = "arez.enable_spies" )
069  @Nullable
070  private final SpyImpl _spy = Arez.areSpiesEnabled() ? new SpyImpl( Arez.areZonesEnabled() ? this : null ) : null;
071  /**
072   * Support infrastructure for components.
073   */
074  @OmitSymbol( unless = "arez.enable_native_components" )
075  @Nullable
076  private final Map<String, Map<Object, Component>> _components =
077    Arez.areNativeComponentsEnabled() ? new HashMap<>() : null;
078  /**
079   * Registry of top level observables.
080   * These are all the Observables instances not contained within a component.
081   */
082  @OmitSymbol( unless = "arez.enable_registries" )
083  @Nullable
084  private final Map<String, ObservableValue<?>> _observableValues =
085    Arez.areRegistriesEnabled() ? new HashMap<>() : null;
086  /**
087   * Registry of top level computable values.
088   * These are all the ComputableValue instances not contained within a component.
089   */
090  @OmitSymbol( unless = "arez.enable_registries" )
091  @Nullable
092  private final Map<String, ComputableValue<?>> _computableValues =
093    Arez.areRegistriesEnabled() ? new HashMap<>() : null;
094  /**
095   * Registry of all active tasks.
096   */
097  @OmitSymbol( unless = "arez.enable_registries" )
098  @Nullable
099  private final Map<String, Task> _tasks = Arez.areRegistriesEnabled() ? new HashMap<>() : null;
100  /**
101   * Registry of top level observers.
102   * These are all the Observer instances not contained within a component.
103   */
104  @OmitSymbol( unless = "arez.enable_registries" )
105  @Nullable
106  private final Map<String, Observer> _observers = Arez.areRegistriesEnabled() ? new HashMap<>() : null;
107  /**
108   * Locator used to resolve references.
109   */
110  @OmitSymbol( unless = "arez.enable_references" )
111  @Nullable
112  private final AggregateLocator _locator = Arez.areReferencesEnabled() ? new AggregateLocator() : null;
113  /**
114   * Flag indicating whether the scheduler should run next time it is triggered.
115   * This should be active only when there is no uncommitted transaction for context.
116   */
117  private boolean _schedulerEnabled = true;
118  /**
119   * The number of un-released locks on the scheduler.
120   */
121  private int _schedulerLockCount;
122  /**
123   * Flag indicating whether the scheduler is currently active.
124   */
125  private boolean _schedulerActive;
126  /**
127   * Cached copy of action to execute tasks.
128   */
129  @OmitSymbol( unless = "arez.enable_task_interceptor" )
130  @Nullable
131  private final SafeProcedure _taskExecuteAction = Arez.isTaskInterceptorEnabled() ? _executor::runTasks : null;
132  /**
133   * Interceptor that wraps all task executions.
134   */
135  @OmitSymbol( unless = "arez.enable_task_interceptor" )
136  @Nullable
137  private TaskInterceptor _taskInterceptor;
138
139  /**
140   * Arez context should not be created directly but only accessed via Arez.
141   */
142  ArezContext( @Nullable final Zone zone )
143  {
144    _zone = Arez.areZonesEnabled() ? Objects.requireNonNull( zone ) : null;
145  }
146
147  /**
148   * Return the map for components of specified type.
149   *
150   * @param type the component type.
151   * @return the map for components of specified type.
152   */
153  @Nonnull
154  private Map<Object, Component> getComponentByTypeMap( @Nonnull final String type )
155  {
156    assert null != _components;
157    return _components.computeIfAbsent( type, t -> new HashMap<>() );
158  }
159
160  /**
161   * Return true if the component identified by type and id has been defined in context.
162   *
163   * @param type the component type.
164   * @param id   the component id.
165   * @return true if component is defined in context.
166   */
167  @OmitSymbol( unless = "arez.enable_native_components" )
168  public boolean isComponentPresent( @Nonnull final String type, @Nonnull final Object id )
169  {
170    apiInvariant( Arez::areNativeComponentsEnabled,
171                  () -> "Arez-0135: ArezContext.isComponentPresent() invoked when Arez.areNativeComponentsEnabled() returns false." );
172    return getComponentByTypeMap( type ).containsKey( id );
173  }
174
175  /**
176   * Create a component with the specified parameters and return it.
177   * This method should only be invoked if {@link Arez#areNativeComponentsEnabled()} returns true.
178   * This method should not be invoked if {@link #isComponentPresent(String, Object)} returns true for
179   * the parameters. The caller should invoke {@link Component#complete()} on the returned component as
180   * soon as the component definition has completed.
181   *
182   * @param type the component type.
183   * @param id   the component id.
184   * @return the created component.
185   */
186  @OmitSymbol( unless = "arez.enable_native_components" )
187  @Nonnull
188  public Component component( @Nonnull final String type, @Nonnull final Object id )
189  {
190    return component( type, id, Arez.areNamesEnabled() ? type + "@" + id : null );
191  }
192
193  /**
194   * Create a component with the specified parameters and return it.
195   * This method should only be invoked if {@link Arez#areNativeComponentsEnabled()} returns true.
196   * This method should not be invoked if {@link #isComponentPresent(String, Object)} returns true for
197   * the parameters. The caller should invoke {@link Component#complete()} on the returned component as
198   * soon as the component definition has completed.
199   *
200   * @param type the component type.
201   * @param id   the component id.
202   * @param name the name of the component. Should be null if {@link Arez#areNamesEnabled()} returns false.
203   * @return the created component.
204   */
205  @OmitSymbol( unless = "arez.enable_native_components" )
206  @Nonnull
207  public Component component( @Nonnull final String type, @Nonnull final Object id, @Nullable final String name )
208  {
209    return component( type, id, name, null );
210  }
211
212  /**
213   * Create a component with the specified parameters and return it.
214   * This method should only be invoked if {@link Arez#areNativeComponentsEnabled()} returns true.
215   * This method should not be invoked if {@link #isComponentPresent(String, Object)} returns true for
216   * the parameters. The caller should invoke {@link Component#complete()} on the returned component as
217   * soon as the component definition has completed.
218   *
219   * @param type       the component type.
220   * @param id         the component id.
221   * @param name       the name of the component. Should be null if {@link Arez#areNamesEnabled()} returns false.
222   * @param preDispose the hook action called just before the Component is disposed. The hook method is called from within the dispose transaction.
223   * @return the created component.
224   */
225  @OmitSymbol( unless = "arez.enable_native_components" )
226  @Nonnull
227  public Component component( @Nonnull final String type,
228                              @Nonnull final Object id,
229                              @Nullable final String name,
230                              @Nullable final SafeProcedure preDispose )
231  {
232    return component( type, id, name, preDispose, null );
233  }
234
235  /**
236   * Create a component with the specified parameters and return it.
237   * This method should only be invoked if {@link Arez#areNativeComponentsEnabled()} returns true.
238   * This method should not be invoked if {@link #isComponentPresent(String, Object)} returns true for
239   * the parameters. The caller should invoke {@link Component#complete()} on the returned component as
240   * soon as the component definition has completed.
241   *
242   * @param type        the component type.
243   * @param id          the component id.
244   * @param name        the name of the component. Should be null if {@link Arez#areNamesEnabled()} returns false.
245   * @param preDispose  the hook action called just before the Component is disposed. The hook method is called from within the dispose transaction.
246   * @param postDispose the hook action called just after the Component is disposed. The hook method is called from within the dispose transaction.
247   * @return the created component.
248   */
249  @OmitSymbol( unless = "arez.enable_native_components" )
250  @Nonnull
251  public Component component( @Nonnull final String type,
252                              @Nonnull final Object id,
253                              @Nullable final String name,
254                              @Nullable final SafeProcedure preDispose,
255                              @Nullable final SafeProcedure postDispose )
256  {
257    if ( Arez.shouldCheckApiInvariants() )
258    {
259      apiInvariant( Arez::areNativeComponentsEnabled,
260                    () -> "Arez-0008: ArezContext.component() invoked when Arez.areNativeComponentsEnabled() returns false." );
261    }
262    final Map<Object, Component> map = getComponentByTypeMap( type );
263    if ( Arez.shouldCheckApiInvariants() )
264    {
265      apiInvariant( () -> !map.containsKey( id ),
266                    () -> "Arez-0009: ArezContext.component() invoked for type '" + type + "' and id '" +
267                          id + "' but a component already exists for specified type+id." );
268    }
269    final Component component =
270      new Component( Arez.areZonesEnabled() ? this : null, type, id, name, preDispose, postDispose );
271    map.put( id, component );
272    if ( willPropagateSpyEvents() )
273    {
274      getSpy().reportSpyEvent( new ComponentCreateStartEvent( getSpy().asComponentInfo( component ) ) );
275    }
276    return component;
277  }
278
279  /**
280   * Invoked by the component during it's dispose to release resources associated with the component.
281   *
282   * @param component the component.
283   */
284  @OmitSymbol( unless = "arez.enable_native_components" )
285  void deregisterComponent( @Nonnull final Component component )
286  {
287    if ( Arez.shouldCheckInvariants() )
288    {
289      invariant( Arez::areNativeComponentsEnabled,
290                 () -> "Arez-0006: ArezContext.deregisterComponent() invoked when Arez.areNativeComponentsEnabled() returns false." );
291    }
292    final String type = component.getType();
293    final Map<Object, Component> map = getComponentByTypeMap( type );
294    final Component removed = map.remove( component.getId() );
295    if ( Arez.shouldCheckInvariants() )
296    {
297      invariant( () -> component == removed,
298                 () -> "Arez-0007: ArezContext.deregisterComponent() invoked for '" + component + "' but was " +
299                       "unable to remove specified component from registry. Actual component removed: " + removed );
300    }
301    if ( map.isEmpty() )
302    {
303      assert _components != null;
304      _components.remove( type );
305    }
306  }
307
308  /**
309   * Return component with specified type and id if component exists.
310   *
311   * @param type the component type.
312   * @param id   the component id.
313   * @return the component or null.
314   */
315  @OmitSymbol( unless = "arez.enable_native_components" )
316  @Nullable
317  Component findComponent( @Nonnull final String type, @Nonnull final Object id )
318  {
319    if ( Arez.shouldCheckInvariants() )
320    {
321      invariant( Arez::areNativeComponentsEnabled,
322                 () -> "Arez-0010: ArezContext.findComponent() invoked when Arez.areNativeComponentsEnabled() returns false." );
323    }
324    assert null != _components;
325    final Map<Object, Component> map = _components.get( type );
326    if ( null != map )
327    {
328      return map.get( id );
329    }
330    else
331    {
332      return null;
333    }
334  }
335
336  /**
337   * Return all the components with specified type.
338   *
339   * @param type the component type.
340   * @return the components for type.
341   */
342  @OmitSymbol( unless = "arez.enable_native_components" )
343  @Nonnull
344  Collection<Component> findAllComponentsByType( @Nonnull final String type )
345  {
346    if ( Arez.shouldCheckInvariants() )
347    {
348      invariant( Arez::areNativeComponentsEnabled,
349                 () -> "Arez-0011: ArezContext.findAllComponentsByType() invoked when Arez.areNativeComponentsEnabled() returns false." );
350    }
351    assert null != _components;
352    final Map<Object, Component> map = _components.get( type );
353    if ( null != map )
354    {
355      return map.values();
356    }
357    else
358    {
359      return Collections.emptyList();
360    }
361  }
362
363  /**
364   * Return all the component types as a collection.
365   *
366   * @return the component types.
367   */
368  @OmitSymbol( unless = "arez.enable_native_components" )
369  @Nonnull
370  Collection<String> findAllComponentTypes()
371  {
372    if ( Arez.shouldCheckInvariants() )
373    {
374      invariant( Arez::areNativeComponentsEnabled,
375                 () -> "Arez-0012: ArezContext.findAllComponentTypes() invoked when Arez.areNativeComponentsEnabled() returns false." );
376    }
377    assert null != _components;
378    return _components.keySet();
379  }
380
381  /**
382   * Create a ComputableValue with specified parameters.
383   *
384   * @param <T>      the type of the computable value.
385   * @param function the function that computes the value.
386   * @return the ComputableValue instance.
387   */
388  @Nonnull
389  public <T> ComputableValue<T> computable( @Nonnull final SafeFunction<T> function )
390  {
391    return computable( function, 0 );
392  }
393
394  /**
395   * Create a ComputableValue with specified parameters.
396   *
397   * @param <T>      the type of the computable value.
398   * @param function the function that computes the value.
399   * @param flags    the flags used to create the ComputableValue. The acceptable flags are defined in {@link ComputableValue.Flags}.
400   * @return the ComputableValue instance.
401   */
402  @Nonnull
403  public <T> ComputableValue<T> computable( @Nonnull final SafeFunction<T> function,
404                                            @MagicConstant( flagsFromClass = ComputableValue.Flags.class ) final int flags )
405  {
406    return computable( null, function, flags );
407  }
408
409  /**
410   * Create a ComputableValue with specified parameters.
411   *
412   * @param <T>      the type of the computable value.
413   * @param name     the name of the ComputableValue.
414   * @param function the function that computes the value.
415   * @return the ComputableValue instance.
416   */
417  @Nonnull
418  public <T> ComputableValue<T> computable( @Nullable final String name, @Nonnull final SafeFunction<T> function )
419  {
420    return computable( name, function, 0 );
421  }
422
423  /**
424   * Create a ComputableValue with specified parameters.
425   *
426   * @param <T>      the type of the computable value.
427   * @param name     the name of the ComputableValue.
428   * @param function the function that computes the value.
429   * @param flags    the flags used to create the ComputableValue. The acceptable flags are defined in {@link ComputableValue.Flags}.
430   * @return the ComputableValue instance.
431   */
432  @Nonnull
433  public <T> ComputableValue<T> computable( @Nullable final String name,
434                                            @Nonnull final SafeFunction<T> function,
435                                            @MagicConstant( flagsFromClass = ComputableValue.Flags.class ) final int flags )
436  {
437    return computable( null, name, function, flags );
438  }
439
440  /**
441   * Create a ComputableValue with specified parameters.
442   *
443   * @param <T>       the type of the computable value.
444   * @param component the component that contains the ComputableValue if any. Must be null unless {@link Arez#areNativeComponentsEnabled()} returns true.
445   * @param name      the name of the ComputableValue.
446   * @param function  the function that computes the value.
447   * @return the ComputableValue instance.
448   */
449  @Nonnull
450  public <T> ComputableValue<T> computable( @Nullable final Component component,
451                                            @Nullable final String name,
452                                            @Nonnull final SafeFunction<T> function )
453  {
454    return computable( component, name, function, 0 );
455  }
456
457  /**
458   * Create a ComputableValue with specified parameters.
459   *
460   * @param <T>       the type of the computable value.
461   * @param component the component that contains the ComputableValue if any. Must be null unless {@link Arez#areNativeComponentsEnabled()} returns true.
462   * @param name      the name of the ComputableValue.
463   * @param function  the function that computes the value.
464   * @param flags     the flags used to create the ComputableValue. The acceptable flags are defined in {@link ComputableValue.Flags}.
465   * @return the ComputableValue instance.
466   */
467  @Nonnull
468  public <T> ComputableValue<T> computable( @Nullable final Component component,
469                                            @Nullable final String name,
470                                            @Nonnull final SafeFunction<T> function,
471                                            @MagicConstant( flagsFromClass = ComputableValue.Flags.class ) final int flags )
472  {
473    return new ComputableValue<>( Arez.areZonesEnabled() ? this : null,
474                                  component,
475                                  generateName( "ComputableValue", name ),
476                                  function,
477                                  flags );
478  }
479
480  /**
481   * Build name for node.
482   * If {@link Arez#areNamesEnabled()} returns false then this method will return null, otherwise the specified
483   * name will be returned or a name synthesized from the prefix and a running number if no name is specified.
484   *
485   * @param prefix the prefix used if this method needs to generate name.
486   * @param name   the name specified by the user.
487   * @return the name.
488   */
489  @Nullable
490  String generateName( @Nonnull final String prefix, @Nullable final String name )
491  {
492    return Arez.areNamesEnabled() ?
493           null != name ? name : prefix + "@" + _nextNodeId++ :
494           null;
495  }
496
497  /**
498   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
499   *
500   * @param observe the executable observed by the observer.
501   * @return the new Observer.
502   */
503  @Nonnull
504  public Observer observer( @Nonnull final Procedure observe )
505  {
506    return observer( observe, 0 );
507  }
508
509  /**
510   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
511   *
512   * @param observe the executable observed by the observer.
513   * @param flags   the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
514   * @return the new Observer.
515   */
516  @Nonnull
517  public Observer observer( @Nonnull final Procedure observe,
518                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
519  {
520    return observer( (String) null, observe, flags );
521  }
522
523  /**
524   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
525   *
526   * @param name    the name of the observer.
527   * @param observe the executable observed by the observer.
528   * @return the new Observer.
529   */
530  @Nonnull
531  public Observer observer( @Nullable final String name, @Nonnull final Procedure observe )
532  {
533    return observer( name, observe, 0 );
534  }
535
536  /**
537   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
538   *
539   * @param name    the name of the observer.
540   * @param observe the executable observed by the observer.
541   * @param flags   the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
542   * @return the new Observer.
543   */
544  @Nonnull
545  public Observer observer( @Nullable final String name,
546                            @Nonnull final Procedure observe,
547                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
548  {
549    return observer( null, name, observe, flags );
550  }
551
552  /**
553   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
554   *
555   * @param component the component containing the observer if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
556   * @param name      the name of the observer.
557   * @param observe   the executable observed by the observer.
558   * @return the new Observer.
559   */
560  @Nonnull
561  public Observer observer( @Nullable final Component component,
562                            @Nullable final String name,
563                            @Nonnull final Procedure observe )
564  {
565    return observer( component, name, observe, 0 );
566  }
567
568  /**
569   * Create an "autorun" observer that reschedules observed procedure when dependency updates occur.
570   *
571   * @param component the component containing the observer if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
572   * @param name      the name of the observer.
573   * @param observe   the executable observed by the observer.
574   * @param flags     the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
575   * @return the new Observer.
576   */
577  @Nonnull
578  public Observer observer( @Nullable final Component component,
579                            @Nullable final String name,
580                            @Nonnull final Procedure observe,
581                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
582  {
583    return observer( component, name, Objects.requireNonNull( observe ), null, flags );
584  }
585
586  /**
587   * Create an observer.
588   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> parameter.
589   *
590   * @param component    the component containing the observer if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
591   * @param name         the name of the observer.
592   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
593   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
594   * @return the new Observer.
595   */
596  @Nonnull
597  public Observer observer( @Nullable final Component component,
598                            @Nullable final String name,
599                            @Nullable final Procedure observe,
600                            @Nullable final Procedure onDepsChange )
601  {
602    return observer( component, name, observe, onDepsChange, 0 );
603  }
604
605  /**
606   * Create an observer.
607   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> or both parameters.
608   *
609   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
610   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
611   * @return the new Observer.
612   */
613  @Nonnull
614  public Observer observer( @Nullable final Procedure observe, @Nullable final Procedure onDepsChange )
615  {
616    return observer( observe, onDepsChange, 0 );
617  }
618
619  /**
620   * Create an observer.
621   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> or both parameters.
622   *
623   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
624   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
625   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
626   * @return the new Observer.
627   */
628  @Nonnull
629  public Observer observer( @Nullable final Procedure observe,
630                            @Nullable final Procedure onDepsChange,
631                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
632  {
633    return observer( null, observe, onDepsChange, flags );
634  }
635
636  /**
637   * Create an observer.
638   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> or both parameters.
639   *
640   * @param name         the name of the observer.
641   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
642   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
643   * @return the new Observer.
644   */
645  @Nonnull
646  public Observer observer( @Nullable final String name,
647                            @Nullable final Procedure observe,
648                            @Nullable final Procedure onDepsChange )
649  {
650    return observer( name, observe, onDepsChange, 0 );
651  }
652
653  /**
654   * Create an observer.
655   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> or both parameters.
656   *
657   * @param name         the name of the observer.
658   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
659   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
660   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
661   * @return the new Observer.
662   */
663  @Nonnull
664  public Observer observer( @Nullable final String name,
665                            @Nullable final Procedure observe,
666                            @Nullable final Procedure onDepsChange,
667                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
668  {
669    return observer( null, name, observe, onDepsChange, flags );
670  }
671
672  /**
673   * Create an observer.
674   * The user must pass either the <code>observe</code> or <code>onDepsChange</code> or both parameters.
675   *
676   * @param component    the component containing the observer if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
677   * @param name         the name of the observer.
678   * @param observe      the executable observed by the observer. May be null if observer is externally scheduled.
679   * @param onDepsChange the hook invoked when dependencies changed. If this is non-null then it is expected that hook will manually schedule the observer by calling {@link Observer#schedule()} at some point.
680   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
681   * @return the new Observer.
682   */
683  @Nonnull
684  public Observer observer( @Nullable final Component component,
685                            @Nullable final String name,
686                            @Nullable final Procedure observe,
687                            @Nullable final Procedure onDepsChange,
688                            @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
689  {
690    return new Observer( Arez.areZonesEnabled() ? this : null,
691                         component,
692                         generateName( "Observer", name ),
693                         observe,
694                         onDepsChange,
695                         flags );
696  }
697
698  /**
699   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
700   * dependencies in the observe function are updated. Application code is responsible for executing the
701   * observe function by invoking a observe method such as {@link #observe(Observer, Function)}.
702   *
703   * @param onDepsChange the hook invoked when dependencies changed.
704   * @return the new Observer.
705   */
706  @Nonnull
707  public Observer tracker( @Nonnull final Procedure onDepsChange )
708  {
709    return tracker( onDepsChange, 0 );
710  }
711
712  /**
713   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
714   * dependencies in the observe function are updated. Application code is responsible for executing the
715   * observe function by invoking a observe method such as {@link #observe(Observer, Function)}.
716   *
717   * @param onDepsChange the hook invoked when dependencies changed.
718   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
719   * @return the new Observer.
720   */
721  @Nonnull
722  public Observer tracker( @Nonnull final Procedure onDepsChange,
723                           @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
724  {
725    return tracker( null, onDepsChange, flags );
726  }
727
728  /**
729   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
730   * dependencies in the observe function are updated. Application code is responsible for executing the
731   * observe function by invoking a observe method such as {@link #observe(Observer, Function)}.
732   *
733   * @param name         the name of the observer.
734   * @param onDepsChange the hook invoked when dependencies changed.
735   * @return the new Observer.
736   */
737  @Nonnull
738  public Observer tracker( @Nullable final String name, @Nonnull final Procedure onDepsChange )
739  {
740    return tracker( name, onDepsChange, 0 );
741  }
742
743  /**
744   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
745   * dependencies in the observe function are updated. Application code is responsible for executing the
746   * observe function by invoking a observe method such as {@link #observe(Observer, Function)}.
747   *
748   * @param name         the name of the observer.
749   * @param onDepsChange the hook invoked when dependencies changed.
750   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
751   * @return the new Observer.
752   */
753  @Nonnull
754  public Observer tracker( @Nullable final String name,
755                           @Nonnull final Procedure onDepsChange,
756                           @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
757  {
758    return tracker( null, name, onDepsChange, flags );
759  }
760
761  /**
762   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
763   * dependencies in the observe function are updated. Application code is responsible for executing the
764   * observe function by invoking a observe method such as {@link #observe(Observer, Function)}.
765   *
766   * @param component    the component containing the observer if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
767   * @param name         the name of the observer.
768   * @param onDepsChange the hook invoked when dependencies changed.
769   * @return the new Observer.
770   */
771  @Nonnull
772  public Observer tracker( @Nullable final Component component,
773                           @Nullable final String name,
774                           @Nonnull final Procedure onDepsChange )
775  {
776    return tracker( component, name, onDepsChange, 0 );
777  }
778
779  /**
780   * Create a tracking observer. The tracking observer triggers the onDepsChange hook function when
781   * dependencies in the observe function are updated. Application code is responsible for executing the
782   * observe function by invoking a observe method such as {@link #observe(Observer, Procedure)}.
783   *
784   * @param component    the component containing the observer, if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
785   * @param name         the name of the observer.
786   * @param onDepsChange the hook invoked when dependencies changed.
787   * @param flags        the flags used to create the observer. The acceptable flags are defined in {@link Observer.Flags}.
788   * @return the new Observer.
789   */
790  @Nonnull
791  public Observer tracker( @Nullable final Component component,
792                           @Nullable final String name,
793                           @Nonnull final Procedure onDepsChange,
794                           @MagicConstant( flagsFromClass = Observer.Flags.class ) final int flags )
795  {
796    return observer( component, name, null, Objects.requireNonNull( onDepsChange ), flags );
797  }
798
799  /**
800   * Create an ObservableValue synthesizing name if required.
801   *
802   * @param <T> the type of observable.
803   * @return the new ObservableValue.
804   */
805  @Nonnull
806  public <T> ObservableValue<T> observable()
807  {
808    return observable( null );
809  }
810
811  /**
812   * Create an ObservableValue with the specified name.
813   *
814   * @param name the name of the ObservableValue. Should be non-null if {@link Arez#areNamesEnabled()} returns true, null otherwise.
815   * @param <T>  the type of observable.
816   * @return the new ObservableValue.
817   */
818  @Nonnull
819  public <T> ObservableValue<T> observable( @Nullable final String name )
820  {
821    return observable( name, null, null );
822  }
823
824  /**
825   * Create an ObservableValue.
826   *
827   * @param name     the name of the observable. Should be non-null if {@link Arez#areNamesEnabled()} returns true, null otherwise.
828   * @param accessor the accessor for observable. Should be null if {@link Arez#arePropertyIntrospectorsEnabled()} returns false, may be non-null otherwise.
829   * @param mutator  the mutator for observable. Should be null if {@link Arez#arePropertyIntrospectorsEnabled()} returns false, may be non-null otherwise.
830   * @param <T>      the type of observable.
831   * @return the new ObservableValue.
832   */
833  @Nonnull
834  public <T> ObservableValue<T> observable( @Nullable final String name,
835                                            @Nullable final PropertyAccessor<T> accessor,
836                                            @Nullable final PropertyMutator<T> mutator )
837  {
838    return observable( null, name, accessor, mutator );
839  }
840
841  /**
842   * Create an ObservableValue.
843   *
844   * @param <T>       The type of the value that is observable.
845   * @param component the component containing observable if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
846   * @param name      the name of the observable. Should be non-null if {@link Arez#areNamesEnabled()} returns true, null otherwise.
847   * @return the new ObservableValue.
848   */
849  @Nonnull
850  public <T> ObservableValue<T> observable( @Nullable final Component component,
851                                            @Nullable final String name )
852  {
853    return observable( component, name, null );
854  }
855
856  /**
857   * Create an ObservableValue.
858   *
859   * @param <T>       The type of the value that is observable.
860   * @param component the component containing observable if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
861   * @param name      the name of the observable. Should be non-null if {@link Arez#areNamesEnabled()} returns true, null otherwise.
862   * @param accessor  the accessor for observable. Should be null if {@link Arez#arePropertyIntrospectorsEnabled()} returns false, may be non-null otherwise.
863   * @return the new ObservableValue.
864   */
865  @Nonnull
866  public <T> ObservableValue<T> observable( @Nullable final Component component,
867                                            @Nullable final String name,
868                                            @Nullable final PropertyAccessor<T> accessor )
869  {
870    return observable( component, name, accessor, null );
871  }
872
873  /**
874   * Create an ObservableValue.
875   *
876   * @param <T>       The type of the value that is observable.
877   * @param component the component containing observable if any. Should be null if {@link Arez#areNativeComponentsEnabled()} returns false.
878   * @param name      the name of the observable. Should be non-null if {@link Arez#areNamesEnabled()} returns true, null otherwise.
879   * @param accessor  the accessor for observable. Should be null if {@link Arez#arePropertyIntrospectorsEnabled()} returns false, may be non-null otherwise.
880   * @param mutator   the mutator for observable. Should be null if {@link Arez#arePropertyIntrospectorsEnabled()} returns false, may be non-null otherwise.
881   * @return the new ObservableValue.
882   */
883  @Nonnull
884  public <T> ObservableValue<T> observable( @Nullable final Component component,
885                                            @Nullable final String name,
886                                            @Nullable final PropertyAccessor<T> accessor,
887                                            @Nullable final PropertyMutator<T> mutator )
888  {
889    final ObservableValue<T> observableValue =
890      new ObservableValue<>( Arez.areZonesEnabled() ? this : null,
891                             component,
892                             generateName( "ObservableValue", name ),
893                             null,
894                             accessor,
895                             mutator );
896    if ( willPropagateSpyEvents() )
897    {
898      getSpy().reportSpyEvent( new ObservableValueCreateEvent( observableValue.asInfo() ) );
899    }
900    return observableValue;
901  }
902
903  /**
904   * Pass the supplied observer to the scheduler.
905   * The observer should NOT be pending execution.
906   *
907   * @param observer the reaction to schedule.
908   */
909  void scheduleReaction( @Nonnull final Observer observer )
910  {
911    if ( willPropagateSpyEvents() )
912    {
913      getSpy().reportSpyEvent( new ObserveScheduleEvent( observer.asInfo() ) );
914    }
915    if ( Arez.shouldEnforceTransactionType() && isTransactionActive() && Arez.shouldCheckInvariants() )
916    {
917      invariant( () -> getTransaction().isMutation() || getTransaction().isComputableValueTracker(),
918                 () -> "Arez-0013: Observer named '" + observer.getName() + "' attempted to be scheduled during " +
919                       "read-only transaction." );
920      invariant( () -> getTransaction().getTracker() != observer ||
921                       getTransaction().isMutation(),
922                 () -> "Arez-0014: Observer named '" + observer.getName() + "' attempted to schedule itself during " +
923                       "read-only tracking transaction. Observers that are supporting ComputableValue instances " +
924                       "must not schedule self." );
925    }
926    _taskQueue.queueTask( observer.getTask() );
927  }
928
929  /**
930   * Create and queue a task to be executed by the runtime.
931   * If the scheduler is not running, then the scheduler will be triggered.
932   *
933   * @param work the representation of the task to execute.
934   * @return the new task.
935   */
936  @Nonnull
937  public Task task( @Nonnull final SafeProcedure work )
938  {
939    return task( null, work );
940  }
941
942  /**
943   * Create and queue a task to be executed by the runtime.
944   * If the scheduler is not running, then the scheduler will be triggered.
945   *
946   * @param name the name of the task. Must be null if {@link Arez#areNamesEnabled()} returns <code>false</code>.
947   * @param work the representation of the task to execute.
948   * @return the new task.
949   */
950  @Nonnull
951  public Task task( @Nullable final String name, @Nonnull final SafeProcedure work )
952  {
953    return task( name, work, Task.Flags.STATE_IDLE );
954  }
955
956  /**
957   * Create and queue a task to be executed by the runtime.
958   * If the scheduler is not running and the {@link Task.Flags#RUN_LATER} flag has not been supplied then the
959   * scheduler will be triggered.
960   *
961   * @param work  the representation of the task to execute.
962   * @param flags the flags to configure the task. Valid flags include PRIORITY_* flags, DISPOSE_ON_COMPLETE and RUN_* flags.
963   * @return the new task.
964   */
965  @Nonnull
966  public Task task( @Nonnull final SafeProcedure work,
967                    @MagicConstant( flagsFromClass = Task.Flags.class ) final int flags )
968  {
969    return task( null, work, flags );
970  }
971
972  /**
973   * Create and queue a task to be executed by the runtime.
974   * If the scheduler is not running and the {@link Task.Flags#RUN_LATER} flag has not been supplied then the
975   * scheduler will be triggered.
976   *
977   * @param name  the name of the task. Must be null if {@link Arez#areNamesEnabled()} returns <code>false</code>.
978   * @param work  the representation of the task to execute.
979   * @param flags the flags to configure task. Valid flags include PRIORITY_* flags, DISPOSE_ON_COMPLETE and RUN_* flags.
980   * @return the new task.
981   */
982  @Nonnull
983  public Task task( @Nullable final String name,
984                    @Nonnull final SafeProcedure work,
985                    @MagicConstant( flagsFromClass = Task.Flags.class ) final int flags )
986  {
987    final Task task = new Task( Arez.areZonesEnabled() ? this : null, generateName( "Task", name ), work, flags );
988    task.initialSchedule();
989    return task;
990  }
991
992  /**
993   * Return true if the scheduler is currently executing tasks.
994   *
995   * @return true if the scheduler is currently executing tasks.
996   */
997  public boolean isSchedulerActive()
998  {
999    return _schedulerActive;
1000  }
1001
1002  /**
1003   * Return true if there is a transaction in progress.
1004   *
1005   * @return true if there is a transaction in progress.
1006   */
1007  public boolean isTransactionActive()
1008  {
1009    return Transaction.isTransactionActive( this );
1010  }
1011
1012  /**
1013   * Return true if there is a tracking transaction in progress.
1014   * A tracking transaction is one created by an {@link Observer} via the {@link #observer(Procedure)}
1015   * or {@link #tracker(Procedure)} methods or a computable via the {@link #computable(SafeFunction)} function.
1016   *
1017   * @return true if there is a tracking transaction in progress.
1018   */
1019  public boolean isTrackingTransactionActive()
1020  {
1021    return Transaction.isTransactionActive( this ) && null != Transaction.current().getTracker();
1022  }
1023
1024  /**
1025   * Return true if there is a transaction in progress calculating a computable value.
1026   * The transaction is one created for an {@link ComputableValue} via the {@link #computable(SafeFunction)} functions.
1027   *
1028   * @return true, if there is a transaction in progress calculating a computable value.
1029   */
1030  public boolean isComputableTransactionActive()
1031  {
1032    if ( !Transaction.isTransactionActive( this ) )
1033    {
1034      return false;
1035    }
1036    else
1037    {
1038      final Observer tracker = Transaction.current().getTracker();
1039      return null != tracker && tracker.isComputableValue();
1040    }
1041  }
1042
1043  /**
1044   * Return true if there is a read-write transaction in progress.
1045   *
1046   * @return true if there is a read-write transaction in progress.
1047   */
1048  public boolean isReadWriteTransactionActive()
1049  {
1050    return Transaction.isTransactionActive( this ) &&
1051           ( !Arez.shouldEnforceTransactionType() || Transaction.current().isMutation() );
1052  }
1053
1054  /**
1055   * Return true if there is a read-only transaction in progress.
1056   *
1057   * @return true if there is a read-only transaction in progress.
1058   */
1059  public boolean isReadOnlyTransactionActive()
1060  {
1061    return Transaction.isTransactionActive( this ) &&
1062           ( !Arez.shouldEnforceTransactionType() || !Transaction.current().isMutation() );
1063  }
1064
1065  /**
1066   * Return the current transaction.
1067   * This method should not be invoked unless a transaction active and will throw an
1068   * exception if invariant checks are enabled.
1069   *
1070   * @return the current transaction.
1071   */
1072  @Nonnull
1073  Transaction getTransaction()
1074  {
1075    final Transaction current = Transaction.current();
1076    if ( Arez.shouldCheckInvariants() )
1077    {
1078      invariant( () -> !Arez.areZonesEnabled() || current.getContext() == this,
1079                 () -> "Arez-0015: Attempting to get current transaction but current transaction is for different context." );
1080    }
1081    return current;
1082  }
1083
1084  /**
1085   * Enable scheduler so that it will run pending observers next time it is triggered.
1086   */
1087  void enableScheduler()
1088  {
1089    _schedulerEnabled = true;
1090  }
1091
1092  /**
1093   * Disable scheduler so that it will not run pending observers next time it is triggered.
1094   */
1095  void disableScheduler()
1096  {
1097    _schedulerEnabled = false;
1098  }
1099
1100  /**
1101   * Return true if the scheduler enabled flag is true.
1102   * It is still possible that the scheduler has un-released locks so this
1103   * does not necessarily imply that the schedule will run.
1104   *
1105   * @return true if the scheduler enabled flag is true.
1106   */
1107  boolean isSchedulerEnabled()
1108  {
1109    return _schedulerEnabled;
1110  }
1111
1112  /**
1113   * Release a scheduler lock to enable scheduler to run again.
1114   * Trigger reactions if lock reaches 0 and no current transaction.
1115   */
1116  void releaseSchedulerLock()
1117  {
1118    _schedulerLockCount--;
1119    if ( Arez.shouldCheckInvariants() )
1120    {
1121      invariant( () -> _schedulerLockCount >= 0,
1122                 () -> "Arez-0016: releaseSchedulerLock() reduced schedulerLockCount below 0." );
1123    }
1124    triggerScheduler();
1125  }
1126
1127  /**
1128   * Return true if the scheduler is paused.
1129   * True means that {@link #pauseScheduler()} has been called one or more times and the lock not disposed.
1130   *
1131   * @return true if the scheduler is paused, false otherwise.
1132   */
1133  public boolean isSchedulerPaused()
1134  {
1135    return _schedulerLockCount != 0;
1136  }
1137
1138  /**
1139   * Pause scheduler so that it will not run any reactions next time {@link #triggerScheduler()} is invoked.
1140   * The scheduler will not resume scheduling reactions until the lock returned from this method is disposed.
1141   *
1142   * <p>The intention of this method is to allow the user to manually batch multiple actions, before
1143   * disposing the lock and allowing reactions to flow through the system. A typical use-case is when
1144   * a large network packet is received and processed over multiple ticks but you only want the
1145   * application to react once.</p>
1146   *
1147   * <p>If this is invoked from within a reaction then the current behaviour will continue to process any
1148   * pending reactions until there is none left. However this behaviour should not be relied upon as it may
1149   * result in an abort in the future.</p>
1150   *
1151   * <p>It should be noted that this is the one way where inconsistent state can creep into an Arez application.
1152   * If an external action can trigger while the scheduler is paused. i.e. In the browser when an
1153   * event-handler calls back from UI when the reactions have not run. Thus the event handler could be
1154   * based on stale data. If this can occur the developer should </p>
1155   *
1156   * @return a lock on scheduler.
1157   */
1158  @Nonnull
1159  public SchedulerLock pauseScheduler()
1160  {
1161    _schedulerLockCount++;
1162    return new SchedulerLock( Arez.areZonesEnabled() ? this : null );
1163  }
1164
1165  /**
1166   * Specify a interceptor to use to wrap task execution in.
1167   *
1168   * @param taskInterceptor interceptor used to wrap task execution.
1169   */
1170  @OmitSymbol( unless = "arez.enable_task_interceptor" )
1171  public void setTaskInterceptor( @Nullable final TaskInterceptor taskInterceptor )
1172  {
1173    if ( Arez.shouldCheckInvariants() )
1174    {
1175      invariant( Arez::isTaskInterceptorEnabled,
1176                 () -> "Arez-0039: setTaskInterceptor() invoked but Arez.isTaskInterceptorEnabled() returns false." );
1177    }
1178    _taskInterceptor = taskInterceptor;
1179  }
1180
1181  /**
1182   * Method invoked to trigger the scheduler to run any pending reactions. The scheduler will only be
1183   * triggered if there is no transaction active. This method is typically used after one or more Observers
1184   * have been created outside a transaction with the runImmediately flag set to false and the caller wants
1185   * to force the observers to react. Otherwise the Observers will not be schedule until the next top-level
1186   * transaction completes.
1187   */
1188  public void triggerScheduler()
1189  {
1190    if ( isSchedulerEnabled() && !isSchedulerPaused() )
1191    {
1192      // Each reaction creates a top level transaction that attempts to run call
1193      // this method when it completes. Rather than allow this if it is detected
1194      // that we are running reactions already then just abort and assume the top
1195      // most invocation of runPendingTasks will handle scheduling
1196      if ( !_schedulerActive )
1197      {
1198        _schedulerActive = true;
1199        try
1200        {
1201          if ( Arez.isTaskInterceptorEnabled() && null != _taskInterceptor )
1202          {
1203            assert null != _taskExecuteAction;
1204            do
1205            {
1206              _taskInterceptor.executeTasks( _taskExecuteAction );
1207            } while ( _executor.getPendingTaskCount() > 0 );
1208          }
1209          else
1210          {
1211            _executor.runTasks();
1212          }
1213        }
1214        finally
1215        {
1216          _schedulerActive = false;
1217        }
1218      }
1219    }
1220  }
1221
1222  /**
1223   * Register a hook for the current ComputedValue or Observer.
1224   *
1225   * <ul>
1226   * <li>If a hook with the same key was registered in the previous transaction, then this is effectively a noop.</li>
1227   * <li>If a new key is registered, then the OnActivate callback is invoked and the OnDeactivate callback will be
1228   * invoked when the observer is deactivated.</li>
1229   * <li>If the previous transaction had registered a hook and that hook is not registered in the current transaction,
1230   * then the OnDeactivate of the hook will be invoked.</li>
1231   * </ul>
1232   *
1233   * @param key          a unique string identifying the key.
1234   * @param onActivate   a lambda that is invoked immediately if they key is not active.
1235   * @param onDeactivate a lambda that is invoked when the hook deregisters, or the observer deactivates.
1236   */
1237  public void registerHook( @Nonnull final String key,
1238                            @Nullable final Procedure onActivate,
1239                            @Nullable final Procedure onDeactivate )
1240  {
1241    if ( Arez.shouldCheckInvariants() )
1242    {
1243      //noinspection ConstantValue
1244      invariant( () -> null != key, () -> "Arez-0125: registerHook() invoked with a null key." );
1245      invariant( () -> null != onActivate || null != onDeactivate,
1246                 () -> "Arez-0124: registerHook() invoked with null onActivate and onDeactivate callbacks." );
1247      invariant( this::isTransactionActive, () -> "Arez-0098: registerHook() invoked outside of a transaction." );
1248    }
1249    Transaction.current().registerHook( key, onActivate, onDeactivate );
1250  }
1251
1252  /**
1253   * Execute the supplied executable in a transaction.
1254   * The executable may throw an exception.
1255   *
1256   * @param <T>        the type of return value.
1257   * @param executable the executable.
1258   * @return the value returned from the executable.
1259   * @throws Exception if the executable throws an exception.
1260   */
1261  public <T> T action( @Nonnull final Function<T> executable )
1262    throws Throwable
1263  {
1264    return action( executable, 0 );
1265  }
1266
1267  /**
1268   * Execute the supplied executable in a transaction.
1269   * The executable may throw an exception.
1270   *
1271   * @param <T>        the type of return value.
1272   * @param executable the executable.
1273   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1274   * @return the value returned from the executable.
1275   * @throws Exception if the executable throws an exception.
1276   */
1277  public <T> T action( @Nonnull final Function<T> executable,
1278                       @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1279    throws Throwable
1280  {
1281    return action( null, executable, flags );
1282  }
1283
1284  /**
1285   * Execute the supplied executable in a transaction.
1286   * The executable may throw an exception.
1287   *
1288   * @param <T>        the type of return value.
1289   * @param name       the name of the action.
1290   * @param executable the executable.
1291   * @return the value returned from the executable.
1292   * @throws Exception if the executable throws an exception.
1293   */
1294  public <T> T action( @Nullable final String name,
1295                       @Nonnull final Function<T> executable )
1296    throws Throwable
1297  {
1298    return action( name, executable, 0 );
1299  }
1300
1301  /**
1302   * Execute the supplied executable in a transaction.
1303   * The executable may throw an exception.
1304   *
1305   * @param <T>        the type of return value.
1306   * @param name       the name of the action.
1307   * @param executable the executable.
1308   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1309   * @return the value returned from the executable.
1310   * @throws Exception if the executable throws an exception.
1311   */
1312  public <T> T action( @Nullable final String name,
1313                       @Nonnull final Function<T> executable,
1314                       @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1315    throws Throwable
1316  {
1317    return action( name, executable, flags, null );
1318  }
1319
1320  /**
1321   * Execute the supplied executable in a transaction.
1322   * The executable may throw an exception.
1323   *
1324   * @param <T>        the type of return value.
1325   * @param name       the name of the action.
1326   * @param executable the executable.
1327   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1328   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1329   * @return the value returned from the executable.
1330   * @throws Exception if the executable throws an exception.
1331   */
1332  public <T> T action( @Nullable final String name,
1333                       @Nonnull final Function<T> executable,
1334                       @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags,
1335                       @Nullable final Object[] parameters )
1336    throws Throwable
1337  {
1338    return _action( name, executable, flags, null, parameters, true );
1339  }
1340
1341  /**
1342   * Execute the observe function with the specified Observer.
1343   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1344   * The observe function may throw an exception.
1345   *
1346   * @param <T>      the type of return value.
1347   * @param observer the Observer.
1348   * @param observe  the observe function.
1349   * @return the value returned from the observe function.
1350   * @throws Exception if the observe function throws an exception.
1351   */
1352  public <T> T observe( @Nonnull final Observer observer, @Nonnull final Function<T> observe )
1353    throws Throwable
1354  {
1355    return observe( observer, observe, null );
1356  }
1357
1358  /**
1359   * Execute the observe function with the specified Observer.
1360   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1361   * The observe function may throw an exception.
1362   *
1363   * @param <T>        the type of return value.
1364   * @param observer   the Observer.
1365   * @param observe    the observe function.
1366   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1367   * @return the value returned from the observe function.
1368   * @throws Exception if the observe function throws an exception.
1369   */
1370  public <T> T observe( @Nonnull final Observer observer,
1371                        @Nonnull final Function<T> observe,
1372                        @Nullable final Object[] parameters )
1373    throws Throwable
1374  {
1375    if ( Arez.shouldCheckApiInvariants() )
1376    {
1377      apiInvariant( observer::isApplicationExecutor,
1378                    () -> "Arez-0017: Attempted to invoke observe(..) on observer named '" + observer.getName() +
1379                          "' but observer is not configured to use an application executor." );
1380    }
1381    return _action( observerToName( observer ),
1382                    observe,
1383                    trackerObserveFlags( observer ),
1384                    observer,
1385                    parameters,
1386                    true );
1387  }
1388
1389  /**
1390   * Execute the supplied executable.
1391   * The executable is should not throw an exception.
1392   *
1393   * @param <T>        the type of return value.
1394   * @param executable the executable.
1395   * @return the value returned from the executable.
1396   */
1397  public <T> T safeAction( @Nonnull final SafeFunction<T> executable )
1398  {
1399    return safeAction( executable, 0 );
1400  }
1401
1402  /**
1403   * Execute the supplied executable.
1404   * The executable is should not throw an exception.
1405   *
1406   * @param <T>        the type of return value.
1407   * @param executable the executable.
1408   * @param flags      the flags for the action.
1409   * @return the value returned from the executable.
1410   */
1411  public <T> T safeAction( @Nonnull final SafeFunction<T> executable,
1412                           @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1413  {
1414    return safeAction( null, executable, flags );
1415  }
1416
1417  /**
1418   * Execute the supplied executable.
1419   * The executable is should not throw an exception.
1420   *
1421   * @param <T>        the type of return value.
1422   * @param name       the name of the action.
1423   * @param executable the executable.
1424   * @return the value returned from the executable.
1425   */
1426  public <T> T safeAction( @Nullable final String name, @Nonnull final SafeFunction<T> executable )
1427  {
1428    return safeAction( name, executable, 0 );
1429  }
1430
1431  /**
1432   * Execute the supplied executable.
1433   * The executable is should not throw an exception.
1434   *
1435   * @param <T>        the type of return value.
1436   * @param name       the name of the action.
1437   * @param executable the executable.
1438   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1439   * @return the value returned from the executable.
1440   */
1441  public <T> T safeAction( @Nullable final String name,
1442                           @Nonnull final SafeFunction<T> executable,
1443                           @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1444  {
1445    return safeAction( name, executable, flags, null );
1446  }
1447
1448  /**
1449   * Execute the supplied executable.
1450   * The executable is should not throw an exception.
1451   *
1452   * @param <T>        the type of return value.
1453   * @param name       the name of the action.
1454   * @param executable the executable.
1455   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1456   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1457   * @return the value returned from the executable.
1458   */
1459  public <T> T safeAction( @Nullable final String name,
1460                           @Nonnull final SafeFunction<T> executable,
1461                           @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags,
1462                           @Nullable final Object[] parameters )
1463  {
1464    return _safeAction( name, executable, flags, null, parameters, true, true );
1465  }
1466
1467  /**
1468   * Execute the observe function with the specified Observer.
1469   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1470   * The observe function should not throw an exception.
1471   *
1472   * @param <T>      the type of return value.
1473   * @param observer the Observer.
1474   * @param observe  the observe function.
1475   * @return the value returned from the observe function.
1476   */
1477  public <T> T safeObserve( @Nonnull final Observer observer, @Nonnull final SafeFunction<T> observe )
1478  {
1479    return safeObserve( observer, observe, null );
1480  }
1481
1482  /**
1483   * Execute the observe function with the specified Observer.
1484   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1485   * The observe function should not throw an exception.
1486   *
1487   * @param <T>        the type of return value.
1488   * @param observer   the Observer.
1489   * @param observe    the observe function.
1490   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1491   * @return the value returned from the observe function.
1492   */
1493  public <T> T safeObserve( @Nonnull final Observer observer,
1494                            @Nonnull final SafeFunction<T> observe,
1495                            @Nullable final Object[] parameters )
1496  {
1497    if ( Arez.shouldCheckApiInvariants() )
1498    {
1499      apiInvariant( observer::isApplicationExecutor,
1500                    () -> "Arez-0018: Attempted to invoke safeObserve(..) on observer named '" + observer.getName() +
1501                          "' but observer is not configured to use an application executor." );
1502    }
1503    return _safeAction( observerToName( observer ),
1504                        observe,
1505                        trackerObserveFlags( observer ),
1506                        observer,
1507                        parameters,
1508                        true,
1509                        true );
1510  }
1511
1512  /**
1513   * Execute the supplied executable in a transaction.
1514   * The executable may throw an exception.
1515   *
1516   * @param executable the executable.
1517   * @throws Throwable if the procedure throws an exception.
1518   */
1519  public void action( @Nonnull final Procedure executable )
1520    throws Throwable
1521  {
1522    action( executable, 0 );
1523  }
1524
1525  /**
1526   * Execute the supplied executable in a transaction.
1527   * The executable may throw an exception.
1528   *
1529   * @param executable the executable.
1530   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1531   * @throws Throwable if the procedure throws an exception.
1532   */
1533  public void action( @Nonnull final Procedure executable,
1534                      @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1535    throws Throwable
1536  {
1537    action( null, executable, flags );
1538  }
1539
1540  /**
1541   * Execute the supplied executable in a transaction.
1542   * The executable may throw an exception.
1543   *
1544   * @param name       the name of the action.
1545   * @param executable the executable.
1546   * @throws Throwable if the procedure throws an exception.
1547   */
1548  public void action( @Nullable final String name, @Nonnull final Procedure executable )
1549    throws Throwable
1550  {
1551    action( name, executable, 0 );
1552  }
1553
1554  /**
1555   * Execute the supplied executable in a transaction.
1556   * The executable may throw an exception.
1557   *
1558   * @param name       the name of the action.
1559   * @param executable the executable.
1560   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1561   * @throws Throwable if the procedure throws an exception.
1562   */
1563  public void action( @Nullable final String name,
1564                      @Nonnull final Procedure executable,
1565                      @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1566    throws Throwable
1567  {
1568    action( name, executable, flags, null );
1569  }
1570
1571  /**
1572   * Execute the supplied executable in a transaction.
1573   * The executable may throw an exception.
1574   *
1575   * @param name       the name of the action.
1576   * @param executable the executable.
1577   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1578   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1579   * @throws Throwable if the procedure throws an exception.
1580   */
1581  public void action( @Nullable final String name,
1582                      @Nonnull final Procedure executable,
1583                      @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags,
1584                      @Nullable final Object[] parameters )
1585    throws Throwable
1586  {
1587    _action( name, procedureToFunction( executable ), flags, null, parameters, false );
1588  }
1589
1590  /**
1591   * Execute the observe function with the specified Observer.
1592   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1593   * The observe function may throw an exception.
1594   *
1595   * @param observer the Observer.
1596   * @param observe  the observe function.
1597   * @throws Exception if the observe function throws an exception.
1598   */
1599  public void observe( @Nonnull final Observer observer, @Nonnull final Procedure observe )
1600    throws Throwable
1601  {
1602    observe( observer, observe, null );
1603  }
1604
1605  /**
1606   * Execute the observe function with the specified Observer.
1607   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1608   * The observe function may throw an exception.
1609   *
1610   * @param observer   the Observer.
1611   * @param observe    the observe function.
1612   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1613   * @throws Exception if the observe function throws an exception.
1614   */
1615  public void observe( @Nonnull final Observer observer,
1616                       @Nonnull final Procedure observe,
1617                       @Nullable final Object[] parameters )
1618    throws Throwable
1619  {
1620    if ( Arez.shouldCheckApiInvariants() )
1621    {
1622      apiInvariant( observer::isApplicationExecutor,
1623                    () -> "Arez-0019: Attempted to invoke observe(..) on observer named '" + observer.getName() +
1624                          "' but observer is not configured to use an application executor." );
1625    }
1626    rawObserve( observer, observe, parameters );
1627  }
1628
1629  void rawObserve( @Nonnull final Observer observer,
1630                   @Nonnull final Procedure observe,
1631                   @Nullable final Object[] parameters )
1632    throws Throwable
1633  {
1634    _action( observerToName( observer ),
1635             procedureToFunction( observe ),
1636             trackerObserveFlags( observer ),
1637             observer,
1638             parameters,
1639             false );
1640  }
1641
1642  <T> T rawCompute( @Nonnull final ComputableValue<T> computableValue, @Nonnull final SafeFunction<T> action )
1643  {
1644    return _safeAction( Arez.areNamesEnabled() ? computableValue.getName() + ".wrapper" : null,
1645                        action,
1646                        ActionFlags.REQUIRE_NEW_TRANSACTION |
1647                        ActionFlags.NO_VERIFY_ACTION_REQUIRED |
1648                        ActionFlags.READ_ONLY,
1649                        null,
1650                        null,
1651                        false,
1652                        false );
1653  }
1654
1655  /**
1656   * Convert the specified procedure to a function.
1657   * This is done purely to reduce the compiled code-size under js.
1658   *
1659   * @param procedure the procedure.
1660   * @return the function.
1661   */
1662  @Nonnull
1663  private SafeFunction<Object> safeProcedureToFunction( @Nonnull final SafeProcedure procedure )
1664  {
1665    return () -> {
1666      procedure.call();
1667      return null;
1668    };
1669  }
1670
1671  /**
1672   * Convert the specified procedure to a function.
1673   * This is done purely to reduce the compiled code-size under js.
1674   *
1675   * @param procedure the procedure.
1676   * @return the function.
1677   */
1678  @Nonnull
1679  private Function<Object> procedureToFunction( @Nonnull final Procedure procedure )
1680  {
1681    return () -> {
1682      procedure.call();
1683      return null;
1684    };
1685  }
1686
1687  /**
1688   * Execute the supplied executable in a transaction.
1689   *
1690   * @param executable the executable.
1691   */
1692  public void safeAction( @Nonnull final SafeProcedure executable )
1693  {
1694    safeAction( executable, 0 );
1695  }
1696
1697  /**
1698   * Execute the supplied executable in a transaction.
1699   *
1700   * @param executable the executable.
1701   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1702   */
1703  public void safeAction( @Nonnull final SafeProcedure executable,
1704                          @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1705  {
1706    safeAction( null, executable, flags );
1707  }
1708
1709  /**
1710   * Execute the supplied executable in a transaction.
1711   *
1712   * @param name       the name of the action.
1713   * @param executable the executable.
1714   */
1715  public void safeAction( @Nullable final String name, @Nonnull final SafeProcedure executable )
1716  {
1717    safeAction( name, executable, 0 );
1718  }
1719
1720  /**
1721   * Execute the supplied executable in a transaction.
1722   *
1723   * @param name       the name of the action.
1724   * @param executable the executable.
1725   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1726   */
1727  public void safeAction( @Nullable final String name,
1728                          @Nonnull final SafeProcedure executable,
1729                          @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags )
1730  {
1731    safeAction( name, executable, flags, null );
1732  }
1733
1734  /**
1735   * Execute the supplied executable in a transaction.
1736   *
1737   * @param name       the name of the action.
1738   * @param executable the executable.
1739   * @param flags      the flags for the action. The acceptable flags are defined in {@link ActionFlags}.
1740   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1741   */
1742  public void safeAction( @Nullable final String name,
1743                          @Nonnull final SafeProcedure executable,
1744                          @MagicConstant( flagsFromClass = ActionFlags.class ) final int flags,
1745                          @Nullable final Object[] parameters )
1746  {
1747    _safeAction( name, safeProcedureToFunction( executable ), flags, null, parameters, false, true );
1748  }
1749
1750  /**
1751   * Execute the observe function with the specified Observer.
1752   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1753   * The observe function should not throw an exception.
1754   *
1755   * @param observer the Observer.
1756   * @param observe  the observe function.
1757   */
1758  public void safeObserve( @Nonnull final Observer observer, @Nonnull final SafeProcedure observe )
1759  {
1760    safeObserve( observer, observe, null );
1761  }
1762
1763  /**
1764   * Execute the observe function with the specified Observer.
1765   * The Observer must be created by the {@link #tracker(Procedure)} methods.
1766   * The observe function should not throw an exception.
1767   *
1768   * @param observer   the Observer.
1769   * @param observe    the observe function.
1770   * @param parameters the parameters if any. The parameters are only used to generate a spy event.
1771   */
1772  public void safeObserve( @Nonnull final Observer observer,
1773                           @Nonnull final SafeProcedure observe,
1774                           @Nullable final Object[] parameters )
1775  {
1776    if ( Arez.shouldCheckApiInvariants() )
1777    {
1778      apiInvariant( observer::isApplicationExecutor,
1779                    () -> "Arez-0020: Attempted to invoke safeObserve(..) on observer named '" + observer.getName() +
1780                          "' but observer is not configured to use an application executor." );
1781    }
1782    _safeAction( observerToName( observer ),
1783                 safeProcedureToFunction( observe ),
1784                 trackerObserveFlags( observer ),
1785                 observer,
1786                 parameters,
1787                 false,
1788                 true );
1789  }
1790
1791  private <T> T _safeAction( @Nullable final String specifiedName,
1792                             @Nonnull final SafeFunction<T> executable,
1793                             final int flags,
1794                             @Nullable final Observer observer,
1795                             @Nullable final Object[] parameters,
1796                             final boolean expectResult,
1797                             final boolean generateActionEvents )
1798  {
1799    final String name = generateName( "Action", specifiedName );
1800
1801    verifyActionFlags( name, flags );
1802
1803    final boolean observe = null != observer;
1804    Throwable t = null;
1805    boolean completed = false;
1806    long startedAt = 0L;
1807    T result;
1808    try
1809    {
1810      if ( Arez.areSpiesEnabled() && generateActionEvents )
1811      {
1812        startedAt = System.currentTimeMillis();
1813        if ( willPropagateSpyEvents() )
1814        {
1815          reportActionStarted( name, parameters, observe );
1816        }
1817      }
1818      verifyActionNestingAllowed( name, observer );
1819      if ( canImmediatelyInvokeAction( flags ) )
1820      {
1821        result = executable.call();
1822      }
1823      else
1824      {
1825        final Transaction transaction = newTransaction( name, flags, observer );
1826        try
1827        {
1828          result = executable.call();
1829          verifyActionDependencies( name, observer, flags, transaction );
1830        }
1831        finally
1832        {
1833          Transaction.commit( transaction );
1834        }
1835      }
1836      if ( willPropagateSpyEvents() && generateActionEvents )
1837      {
1838        completed = true;
1839        final boolean noReportResults = ( flags & ActionFlags.NO_REPORT_RESULT ) == ActionFlags.NO_REPORT_RESULT;
1840        reportActionCompleted( name,
1841                               parameters,
1842                               observe,
1843                               null,
1844                               startedAt,
1845                               expectResult,
1846                               noReportResults ? null : result );
1847      }
1848      return result;
1849    }
1850    catch ( final Throwable e )
1851    {
1852      t = e;
1853      throw e;
1854    }
1855    finally
1856    {
1857      if ( willPropagateSpyEvents() && generateActionEvents )
1858      {
1859        if ( !completed )
1860        {
1861          reportActionCompleted( name, parameters, observe, t, startedAt, expectResult, null );
1862        }
1863      }
1864      triggerScheduler();
1865    }
1866  }
1867
1868  private <T> T _action( @Nullable final String specifiedName,
1869                         @Nonnull final Function<T> executable,
1870                         final int flags,
1871                         @Nullable final Observer observer,
1872                         @Nullable final Object[] parameters,
1873                         final boolean expectResult )
1874    throws Throwable
1875  {
1876    final String name = generateName( "Action", specifiedName );
1877
1878    verifyActionFlags( name, flags );
1879    final boolean observed = null != observer;
1880    final boolean generateActionEvents = !observed || !observer.isComputableValue();
1881    Throwable t = null;
1882    boolean completed = false;
1883    long startedAt = 0L;
1884    T result;
1885    try
1886    {
1887      if ( Arez.areSpiesEnabled() && generateActionEvents )
1888      {
1889        startedAt = System.currentTimeMillis();
1890        if ( willPropagateSpyEvents() )
1891        {
1892          reportActionStarted( name, parameters, observed );
1893        }
1894      }
1895      verifyActionNestingAllowed( name, observer );
1896      if ( canImmediatelyInvokeAction( flags ) )
1897      {
1898        result = executable.call();
1899      }
1900      else
1901      {
1902        final Transaction transaction = newTransaction( name, flags, observer );
1903        try
1904        {
1905          result = executable.call();
1906          verifyActionDependencies( name, observer, flags, transaction );
1907        }
1908        finally
1909        {
1910          Transaction.commit( transaction );
1911        }
1912      }
1913      if ( willPropagateSpyEvents() && generateActionEvents )
1914      {
1915        completed = true;
1916        final boolean noReportResults = ( flags & ActionFlags.NO_REPORT_RESULT ) == ActionFlags.NO_REPORT_RESULT;
1917        reportActionCompleted( name,
1918                               parameters,
1919                               observed,
1920                               null,
1921                               startedAt,
1922                               expectResult,
1923                               noReportResults ? null : result );
1924      }
1925      return result;
1926    }
1927    catch ( final Throwable e )
1928    {
1929      t = e;
1930      throw e;
1931    }
1932    finally
1933    {
1934      if ( willPropagateSpyEvents() && generateActionEvents )
1935      {
1936        if ( !completed )
1937        {
1938          reportActionCompleted( name, parameters, observed, t, startedAt, expectResult, null );
1939        }
1940      }
1941      triggerScheduler();
1942    }
1943  }
1944
1945  private void verifyActionFlags( @Nullable final String name, final int flags )
1946  {
1947    if ( Arez.shouldCheckApiInvariants() )
1948    {
1949      final int nonActionFlags = flags & ~ActionFlags.CONFIG_FLAGS_MASK;
1950      apiInvariant( () -> 0 == nonActionFlags,
1951                    () -> "Arez-0212: Flags passed to action '" + name + "' include some unexpected " +
1952                          "flags set: " + nonActionFlags );
1953      apiInvariant( () -> !Arez.shouldEnforceTransactionType() ||
1954                          Transaction.Flags.isTransactionModeValid( Transaction.Flags.transactionMode( flags ) |
1955                                                                    flags ),
1956                    () -> "Arez-0126: Flags passed to action '" + name + "' include both READ_ONLY and READ_WRITE." );
1957      apiInvariant( () -> ActionFlags.isVerifyActionRuleValid( flags | ActionFlags.verifyActionRule( flags ) ),
1958                    () -> "Arez-0127: Flags passed to action '" + name + "' include both VERIFY_ACTION_REQUIRED " +
1959                          "and NO_VERIFY_ACTION_REQUIRED." );
1960    }
1961  }
1962
1963  private void verifyActionDependencies( @Nullable final String name,
1964                                         @Nullable final Observer observer,
1965                                         final int flags,
1966                                         @Nonnull final Transaction transaction )
1967  {
1968    if ( Arez.shouldCheckInvariants() )
1969    {
1970      if ( null == observer )
1971      {
1972        verifyActionRequired( transaction, flags );
1973      }
1974      else if ( Observer.Flags.AREZ_DEPENDENCIES == ( flags & Observer.Flags.AREZ_DEPENDENCIES ) )
1975      {
1976        final Transaction current = Transaction.current();
1977
1978        final FastList<ObservableValue<?>> observableValues = current.getObservableValues();
1979        invariant( () -> Objects.requireNonNull( current.getTracker() ).isDisposing() ||
1980                         ( null != observableValues && !observableValues.isEmpty() ),
1981                   () -> "Arez-0118: Observer named '" + name + "' completed observed function (executed by " +
1982                         "application) but is not observing any properties." );
1983      }
1984    }
1985  }
1986
1987  private void verifyActionRequired( @Nonnull final Transaction transaction, final int flags )
1988  {
1989    if ( Arez.shouldCheckInvariants() &&
1990         ActionFlags.NO_VERIFY_ACTION_REQUIRED != ( flags & ActionFlags.NO_VERIFY_ACTION_REQUIRED ) )
1991    {
1992      invariant( transaction::hasTransactionUseOccurred,
1993                 () -> "Arez-0185: Action named '" + transaction.getName() + "' completed but no reads, writes, " +
1994                       "schedules, reportStales or reportPossiblyChanged occurred within the scope of the action." );
1995    }
1996  }
1997
1998  @Nonnull
1999  private Transaction newTransaction( @Nullable final String name, final int flags, @Nullable final Observer observer )
2000  {
2001    final boolean mutation = Arez.shouldEnforceTransactionType() && 0 == ( flags & Transaction.Flags.READ_ONLY );
2002    return Transaction.begin( this, generateName( "Transaction", name ), mutation, observer );
2003  }
2004
2005  /**
2006   * Return true if the action can be immediately invoked, false if a transaction needs to be created.
2007   */
2008  private boolean canImmediatelyInvokeAction( final int flags )
2009  {
2010    return 0 == ( flags & ActionFlags.REQUIRE_NEW_TRANSACTION ) &&
2011           ( Arez.shouldEnforceTransactionType() &&
2012             ( ActionFlags.READ_ONLY == ( flags & ActionFlags.READ_ONLY ) ) ?
2013             isReadOnlyTransactionActive() :
2014             isReadWriteTransactionActive() );
2015  }
2016
2017  private void verifyActionNestingAllowed( @Nullable final String name, @Nullable final Observer observer )
2018  {
2019    if ( Arez.shouldEnforceTransactionType() )
2020    {
2021      final Transaction parentTransaction = Transaction.isTransactionActive( this ) ? Transaction.current() : null;
2022      if ( null != parentTransaction )
2023      {
2024        final Observer parent = parentTransaction.getTracker();
2025        apiInvariant( () -> null == parent ||
2026                            parent.nestedActionsAllowed() ||
2027                            ( null != observer && observer.isComputableValue() ),
2028                      () -> "Arez-0187: Attempting to nest action named '" + name + "' " +
2029                            "inside transaction named '" + parentTransaction.getName() + "' created by an " +
2030                            "observer that does not allow nested actions." );
2031      }
2032    }
2033  }
2034
2035  /**
2036   * Return next transaction id and increment internal counter.
2037   * The id is a monotonically increasing number starting at 1.
2038   *
2039   * @return the next transaction id.
2040   */
2041  int nextTransactionId()
2042  {
2043    return _nextTransactionId++;
2044  }
2045
2046  /**
2047   * Register an entity locator to use to resolve references.
2048   * The Locator must not already be registered.
2049   * This should not be invoked unless Arez.areReferencesEnabled() returns true.
2050   *
2051   * @param locator the Locator to register.
2052   * @return the disposable to dispose to deregister locator.
2053   */
2054  @OmitSymbol( unless = "arez.enable_references" )
2055  @Nonnull
2056  public Disposable registerLocator( @Nonnull final Locator locator )
2057  {
2058    if ( Arez.shouldCheckApiInvariants() )
2059    {
2060      apiInvariant( Arez::areReferencesEnabled,
2061                    () -> "Arez-0191: ArezContext.registerLocator invoked but Arez.areReferencesEnabled() returned false." );
2062    }
2063    assert null != _locator;
2064    return _locator.registerLocator( Objects.requireNonNull( locator ) );
2065  }
2066
2067  /**
2068   * Return the locator that can be used to resolve references.
2069   * This should not be invoked unless Arez.areReferencesEnabled() returns true.
2070   *
2071   * @return the Locator.
2072   */
2073  @OmitSymbol( unless = "arez.enable_references" )
2074  @Nonnull
2075  public Locator locator()
2076  {
2077    if ( Arez.shouldCheckApiInvariants() )
2078    {
2079      apiInvariant( Arez::areReferencesEnabled,
2080                    () -> "Arez-0192: ArezContext.locator() invoked but Arez.areReferencesEnabled() returned false." );
2081    }
2082    assert null != _locator;
2083    return _locator;
2084  }
2085
2086  /**
2087   * Add error handler to the list of error handlers called.
2088   * The handler should not already be in the list. This method should NOT be called if
2089   * {@link Arez#areObserverErrorHandlersEnabled()} returns false.
2090   *
2091   * @param handler the error handler.
2092   */
2093  @OmitSymbol( unless = "arez.enable_observer_error_handlers" )
2094  public void addObserverErrorHandler( @Nonnull final ObserverErrorHandler handler )
2095  {
2096    if ( Arez.shouldCheckInvariants() )
2097    {
2098      invariant( Arez::areObserverErrorHandlersEnabled,
2099                 () -> "Arez-0182: ArezContext.addObserverErrorHandler() invoked when Arez.areObserverErrorHandlersEnabled() returns false." );
2100    }
2101    getObserverErrorHandlerSupport().addObserverErrorHandler( handler );
2102  }
2103
2104  /**
2105   * Remove error handler from list of existing error handlers.
2106   * The handler should already be in the list. This method should NOT be called if
2107   * {@link Arez#areObserverErrorHandlersEnabled()} returns false.
2108   *
2109   * @param handler the error handler.
2110   */
2111  @OmitSymbol( unless = "arez.enable_observer_error_handlers" )
2112  public void removeObserverErrorHandler( @Nonnull final ObserverErrorHandler handler )
2113  {
2114    if ( Arez.shouldCheckInvariants() )
2115    {
2116      invariant( Arez::areObserverErrorHandlersEnabled,
2117                 () -> "Arez-0181: ArezContext.removeObserverErrorHandler() invoked when Arez.areObserverErrorHandlersEnabled() returns false." );
2118    }
2119    getObserverErrorHandlerSupport().removeObserverErrorHandler( handler );
2120  }
2121
2122  /**
2123   * Report an error in observer.
2124   *
2125   * @param observer  the observer that generated error.
2126   * @param error     the type of the error.
2127   * @param throwable the exception that caused error if any.
2128   */
2129  void reportObserverError( @Nonnull final Observer observer,
2130                            @Nonnull final ObserverError error,
2131                            @Nullable final Throwable throwable )
2132  {
2133    if ( willPropagateSpyEvents() )
2134    {
2135      getSpy().reportSpyEvent( new ObserverErrorEvent( observer.asInfo(), error, throwable ) );
2136    }
2137    if ( Arez.areObserverErrorHandlersEnabled() )
2138    {
2139      getObserverErrorHandlerSupport().onObserverError( observer, error, throwable );
2140    }
2141  }
2142
2143  /**
2144   * Return true if spy events will be propagated.
2145   * This means spies are enabled and there is at least one spy event handler present.
2146   *
2147   * @return true if spy events will be propagated, false otherwise.
2148   */
2149  boolean willPropagateSpyEvents()
2150  {
2151    return Arez.areSpiesEnabled() && getSpy().willPropagateSpyEvents();
2152  }
2153
2154  /**
2155   * Return the spy associated with context.
2156   * This method should not be invoked unless {@link Arez#areSpiesEnabled()} returns true.
2157   *
2158   * @return the spy associated with context.
2159   */
2160  @Nonnull
2161  public Spy getSpy()
2162  {
2163    if ( Arez.shouldCheckApiInvariants() )
2164    {
2165      apiInvariant( Arez::areSpiesEnabled, () -> "Arez-0021: Attempting to get Spy but spies are not enabled." );
2166    }
2167    assert null != _spy;
2168    return _spy;
2169  }
2170
2171  /**
2172   * Return the task queue associated with the context.
2173   *
2174   * @return the task queue associated with the context.
2175   */
2176  @Nonnull
2177  TaskQueue getTaskQueue()
2178  {
2179    return _taskQueue;
2180  }
2181
2182  @OmitSymbol( unless = "arez.enable_registries" )
2183  void registerObservableValue( @Nonnull final ObservableValue<?> observableValue )
2184  {
2185    final String name = observableValue.getName();
2186    if ( Arez.shouldCheckInvariants() )
2187    {
2188      invariant( Arez::areRegistriesEnabled,
2189                 () -> "Arez-0022: ArezContext.registerObservableValue invoked when Arez.areRegistriesEnabled() returns false." );
2190      assert null != _observableValues;
2191      invariant( () -> !_observableValues.containsKey( name ),
2192                 () -> "Arez-0023: ArezContext.registerObservableValue invoked with observableValue named '" + name +
2193                       "' but an existing observableValue with that name is already registered." );
2194    }
2195    assert null != _observableValues;
2196    _observableValues.put( name, observableValue );
2197  }
2198
2199  @OmitSymbol( unless = "arez.enable_registries" )
2200  void deregisterObservableValue( @Nonnull final ObservableValue<?> observableValue )
2201  {
2202    final String name = observableValue.getName();
2203    if ( Arez.shouldCheckInvariants() )
2204    {
2205      invariant( Arez::areRegistriesEnabled,
2206                 () -> "Arez-0024: ArezContext.deregisterObservableValue invoked when Arez.areRegistriesEnabled() returns false." );
2207      assert null != _observableValues;
2208      invariant( () -> _observableValues.containsKey( name ),
2209                 () -> "Arez-0025: ArezContext.deregisterObservableValue invoked with observableValue named '" + name +
2210                       "' but no observableValue with that name is registered." );
2211    }
2212    assert null != _observableValues;
2213    _observableValues.remove( name );
2214  }
2215
2216  @OmitSymbol( unless = "arez.enable_registries" )
2217  @Nonnull
2218  Map<String, ObservableValue<?>> getTopLevelObservables()
2219  {
2220    if ( Arez.shouldCheckInvariants() )
2221    {
2222      invariant( Arez::areRegistriesEnabled,
2223                 () -> "Arez-0026: ArezContext.getTopLevelObservables() invoked when Arez.areRegistriesEnabled() returns false." );
2224    }
2225    assert null != _observableValues;
2226    return _observableValues;
2227  }
2228
2229  @OmitSymbol( unless = "arez.enable_registries" )
2230  void registerObserver( @Nonnull final Observer observer )
2231  {
2232    final String name = observer.getName();
2233    if ( Arez.shouldCheckInvariants() )
2234    {
2235      invariant( Arez::areRegistriesEnabled,
2236                 () -> "Arez-0027: ArezContext.registerObserver invoked when Arez.areRegistriesEnabled() returns false." );
2237      assert null != _observers;
2238      invariant( () -> !_observers.containsKey( name ),
2239                 () -> "Arez-0028: ArezContext.registerObserver invoked with observer named '" + name +
2240                       "' but an existing observer with that name is already registered." );
2241    }
2242    assert null != _observers;
2243    _observers.put( name, observer );
2244  }
2245
2246  @OmitSymbol( unless = "arez.enable_registries" )
2247  void deregisterObserver( @Nonnull final Observer observer )
2248  {
2249    final String name = observer.getName();
2250    if ( Arez.shouldCheckInvariants() )
2251    {
2252      invariant( Arez::areRegistriesEnabled,
2253                 () -> "Arez-0029: ArezContext.deregisterObserver invoked when Arez.areRegistriesEnabled() returns false." );
2254      assert null != _observers;
2255      invariant( () -> _observers.containsKey( name ),
2256                 () -> "Arez-0030: ArezContext.deregisterObserver invoked with observer named '" + name +
2257                       "' but no observer with that name is registered." );
2258    }
2259    assert null != _observers;
2260    _observers.remove( name );
2261  }
2262
2263  @OmitSymbol( unless = "arez.enable_registries" )
2264  @Nonnull
2265  Map<String, Observer> getTopLevelObservers()
2266  {
2267    if ( Arez.shouldCheckInvariants() )
2268    {
2269      invariant( Arez::areRegistriesEnabled,
2270                 () -> "Arez-0031: ArezContext.getTopLevelObservers() invoked when Arez.areRegistriesEnabled() returns false." );
2271    }
2272    assert null != _observers;
2273    return _observers;
2274  }
2275
2276  @OmitSymbol( unless = "arez.enable_registries" )
2277  void registerComputableValue( @Nonnull final ComputableValue<?> computableValue )
2278  {
2279    final String name = computableValue.getName();
2280    if ( Arez.shouldCheckInvariants() )
2281    {
2282      invariant( Arez::areRegistriesEnabled,
2283                 () -> "Arez-0032: ArezContext.registerComputableValue invoked when Arez.areRegistriesEnabled() returns false." );
2284      assert null != _computableValues;
2285      invariant( () -> !_computableValues.containsKey( name ),
2286                 () -> "Arez-0033: ArezContext.registerComputableValue invoked with ComputableValue named '" + name +
2287                       "' but an existing ComputableValue with that name is already registered." );
2288    }
2289    assert null != _computableValues;
2290    _computableValues.put( name, computableValue );
2291  }
2292
2293  @OmitSymbol( unless = "arez.enable_registries" )
2294  void deregisterComputableValue( @Nonnull final ComputableValue<?> computableValue )
2295  {
2296    final String name = computableValue.getName();
2297    if ( Arez.shouldCheckInvariants() )
2298    {
2299      invariant( Arez::areRegistriesEnabled,
2300                 () -> "Arez-0034: ArezContext.deregisterComputableValue invoked when Arez.areRegistriesEnabled() returns false." );
2301      assert null != _computableValues;
2302      invariant( () -> _computableValues.containsKey( name ),
2303                 () -> "Arez-0035: ArezContext.deregisterComputableValue invoked with ComputableValue named '" + name +
2304                       "' but no ComputableValue with that name is registered." );
2305    }
2306    assert null != _computableValues;
2307    _computableValues.remove( name );
2308  }
2309
2310  @OmitSymbol( unless = "arez.enable_registries" )
2311  @Nonnull
2312  Map<String, ComputableValue<?>> getTopLevelComputableValues()
2313  {
2314    if ( Arez.shouldCheckInvariants() )
2315    {
2316      invariant( Arez::areRegistriesEnabled,
2317                 () -> "Arez-0036: ArezContext.getTopLevelComputableValues() invoked when Arez.areRegistriesEnabled() returns false." );
2318    }
2319    assert null != _computableValues;
2320    return _computableValues;
2321  }
2322
2323  @OmitSymbol( unless = "arez.enable_registries" )
2324  void registerTask( @Nonnull final Task task )
2325  {
2326    final String name = task.getName();
2327    if ( Arez.shouldCheckInvariants() )
2328    {
2329      invariant( Arez::areRegistriesEnabled,
2330                 () -> "Arez-0214: ArezContext.registerTask invoked when Arez.areRegistriesEnabled() returns false." );
2331      assert null != _tasks;
2332      invariant( () -> !_tasks.containsKey( name ),
2333                 () -> "Arez-0225: ArezContext.registerTask invoked with Task named '" + name +
2334                       "' but an existing Task with that name is already registered." );
2335    }
2336    assert null != _tasks;
2337    _tasks.put( name, task );
2338  }
2339
2340  @OmitSymbol( unless = "arez.enable_registries" )
2341  void deregisterTask( @Nonnull final Task task )
2342  {
2343    final String name = task.getName();
2344    if ( Arez.shouldCheckInvariants() )
2345    {
2346      invariant( Arez::areRegistriesEnabled,
2347                 () -> "Arez-0226: ArezContext.deregisterTask invoked when Arez.areRegistriesEnabled() returns false." );
2348      assert null != _tasks;
2349      invariant( () -> _tasks.containsKey( name ),
2350                 () -> "Arez-0227: ArezContext.deregisterTask invoked with Task named '" + name +
2351                       "' but no Task with that name is registered." );
2352    }
2353    assert null != _tasks;
2354    _tasks.remove( name );
2355  }
2356
2357  @OmitSymbol( unless = "arez.enable_registries" )
2358  @Nonnull
2359  Map<String, Task> getTopLevelTasks()
2360  {
2361    if ( Arez.shouldCheckInvariants() )
2362    {
2363      invariant( Arez::areRegistriesEnabled,
2364                 () -> "Arez-0228: ArezContext.getTopLevelTasks() invoked when Arez.areRegistriesEnabled() returns false." );
2365    }
2366    assert null != _tasks;
2367    return _tasks;
2368  }
2369
2370  @Nonnull
2371  Zone getZone()
2372  {
2373    assert null != _zone;
2374    return _zone;
2375  }
2376
2377  @OmitSymbol( unless = "arez.enable_observer_error_handlers" )
2378  @Nonnull
2379  ObserverErrorHandlerSupport getObserverErrorHandlerSupport()
2380  {
2381    assert null != _observerErrorHandlerSupport;
2382    return _observerErrorHandlerSupport;
2383  }
2384
2385  @Nullable
2386  private String observerToName( @Nonnull final Observer observer )
2387  {
2388    return Arez.areNamesEnabled() ? observer.getName() : null;
2389  }
2390
2391  private int trackerObserveFlags( @Nonnull final Observer observer )
2392  {
2393    return Transaction.Flags.REQUIRE_NEW_TRANSACTION |
2394           ( Arez.shouldCheckInvariants() ?
2395             observer.areArezDependenciesRequired() ?
2396             Observer.Flags.AREZ_DEPENDENCIES :
2397             Observer.Flags.AREZ_OR_NO_DEPENDENCIES :
2398             0 ) |
2399           ( Arez.areSpiesEnabled() && observer.noReportResults() ? Observer.Flags.NO_REPORT_RESULT : 0 ) |
2400           ( Arez.shouldEnforceTransactionType() ?
2401             ( observer.isMutation() ? Observer.Flags.READ_WRITE : Observer.Flags.READ_ONLY ) :
2402             0 );
2403  }
2404
2405  private void reportActionStarted( @Nullable final String name,
2406                                    @Nullable final Object[] parameters,
2407                                    final boolean observed )
2408  {
2409    assert null != name;
2410    final Object[] params = null == parameters ? new Object[ 0 ] : parameters;
2411    getSpy().reportSpyEvent( new ActionStartEvent( name, observed, params ) );
2412  }
2413
2414  private void reportActionCompleted( @Nullable final String name,
2415                                      @Nullable final Object[] parameters,
2416                                      final boolean observed,
2417                                      final Throwable t,
2418                                      final long startedAt,
2419                                      final boolean returnsResult,
2420                                      final Object result )
2421  {
2422    final long duration = System.currentTimeMillis() - startedAt;
2423    assert null != name;
2424    final Object[] params = null == parameters ? new Object[ 0 ] : parameters;
2425    getSpy().reportSpyEvent( new ActionCompleteEvent( name,
2426                                                      observed,
2427                                                      params,
2428                                                      returnsResult,
2429                                                      result,
2430                                                      t,
2431                                                      (int) duration ) );
2432  }
2433
2434  @OmitSymbol
2435  int currentNextTransactionId()
2436  {
2437    return _nextTransactionId;
2438  }
2439
2440  @OmitSymbol
2441  void setNextNodeId( final int nextNodeId )
2442  {
2443    _nextNodeId = nextNodeId;
2444  }
2445
2446  @OmitSymbol
2447  int getNextNodeId()
2448  {
2449    return _nextNodeId;
2450  }
2451
2452  @OmitSymbol
2453  int getSchedulerLockCount()
2454  {
2455    return _schedulerLockCount;
2456  }
2457
2458  @SuppressWarnings( "SameParameterValue" )
2459  @OmitSymbol
2460  void setSchedulerLockCount( final int schedulerLockCount )
2461  {
2462    _schedulerLockCount = schedulerLockCount;
2463  }
2464
2465  @OmitSymbol
2466  void markSchedulerAsActive()
2467  {
2468    _schedulerActive = true;
2469  }
2470}