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