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