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