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