001package arez;
002
003import arez.spy.ComputeCompleteEvent;
004import arez.spy.ComputeStartEvent;
005import arez.spy.ObserveCompleteEvent;
006import arez.spy.ObserveStartEvent;
007import arez.spy.ObserverCreateEvent;
008import arez.spy.ObserverDisposeEvent;
009import arez.spy.ObserverInfo;
010import grim.annotations.OmitSymbol;
011import java.util.ArrayList;
012import java.util.HashSet;
013import java.util.LinkedHashMap;
014import java.util.List;
015import java.util.Map;
016import java.util.Objects;
017import java.util.stream.Collectors;
018import javax.annotation.Nonnull;
019import javax.annotation.Nullable;
020import static org.realityforge.braincheck.Guards.*;
021
022/**
023 * A node within Arez that is notified of changes in 0 or more Observables.
024 */
025public final class Observer
026  extends Node
027{
028  /**
029   * The component that this observer is contained within.
030   * This should only be set if {@link Arez#areNativeComponentsEnabled()} is true but may also be null if
031   * the observer is a "top-level" observer.
032   */
033  @OmitSymbol( unless = "arez.enable_native_components" )
034  @Nullable
035  private final Component _component;
036  /**
037   * The reference to the ComputableValue if this observer is a derivation.
038   */
039  @Nullable
040  private final ComputableValue<?> _computableValue;
041  /**
042   * The observables that this observer receives notifications from.
043   * These are the dependencies within the dependency graph and will
044   * typically correspond to the observables that were accessed in the
045   * last transaction that this observer was tracking.
046   *
047   * <p>This list should contain no duplicates.</p>
048   */
049  @Nonnull
050  private List<ObservableValue<?>> _dependencies = new ArrayList<>();
051  /**
052   * The list of hooks that this observer that will be invoked when it deactivates or
053   * has already been invoked as part of the register/activate process.
054   * These correspond to the hooks that were registered in the last
055   * transaction that this observer was tracking.
056   */
057  @Nonnull
058  private Map<String, Hook> _hooks = new LinkedHashMap<>();
059  /**
060   * Observe function to invoke if any.
061   * This may be null if external executor is responsible for executing the observe function via
062   * methods such as {@link ArezContext#observe(Observer, Function, Object...)}. If this is null then
063   * {@link #_onDepsChange} must not be null.
064   */
065  @Nullable
066  private final Procedure _observe;
067  /**
068   * Callback invoked when dependencies are updated.
069   * This may be null when the observer re-executes the observe function when dependencies change
070   * but in that case {@link #_observe} must not be null.
071   */
072  @Nullable
073  private final Procedure _onDepsChange;
074  /**
075   * Cached info object associated with element.
076   * This should be null if {@link Arez#areSpiesEnabled()} is false.
077   */
078  @OmitSymbol( unless = "arez.enable_spies" )
079  @Nullable
080  private ObserverInfo _info;
081  /**
082   * A bitfield that contains config time and runtime flags/state.
083   * See the values in {@link Flags} that are covered by the masks
084   * {@link Flags#RUNTIME_FLAGS_MASK} and {@link Flags#CONFIG_FLAGS_MASK}
085   * for acceptable values.
086   */
087  private int _flags;
088  @Nonnull
089  private final Task _task;
090
091  Observer( @Nonnull final ComputableValue<?> computableValue, final int flags )
092  {
093    this( Arez.areZonesEnabled() ? computableValue.getContext() : null,
094          null,
095          Arez.areNamesEnabled() ? computableValue.getName() : null,
096          computableValue,
097          computableValue::compute,
098          null,
099          flags |
100          ( Flags.KEEPALIVE == Flags.getScheduleType( flags ) ? 0 : Flags.DEACTIVATE_ON_UNOBSERVE ) |
101          Task.Flags.runType( flags, Flags.KEEPALIVE == Flags.getScheduleType( flags ) ?
102                                     Task.Flags.RUN_NOW :
103                                     Task.Flags.RUN_LATER ) |
104          ( Arez.shouldEnforceTransactionType() ? Flags.READ_ONLY : 0 ) |
105          Flags.NESTED_ACTIONS_DISALLOWED |
106          Flags.dependencyType( flags ) );
107  }
108
109  Observer( @Nullable final ArezContext context,
110            @Nullable final Component component,
111            @Nullable final String name,
112            @Nullable final Procedure observe,
113            @Nullable final Procedure onDepsChange,
114            final int flags )
115  {
116    this( context,
117          component,
118          name,
119          null,
120          observe,
121          onDepsChange,
122          flags |
123          ( null == observe ? Flags.APPLICATION_EXECUTOR : Flags.KEEPALIVE ) |
124          Task.Flags.runType( flags, null == observe ? Task.Flags.RUN_LATER : Task.Flags.RUN_NOW ) |
125          Flags.nestedActionRule( flags ) |
126          Flags.dependencyType( flags ) |
127          Transaction.Flags.transactionMode( flags ) );
128  }
129
130  private Observer( @Nullable final ArezContext context,
131                    @Nullable final Component component,
132                    @Nullable final String name,
133                    @Nullable final ComputableValue<?> computableValue,
134                    @Nullable final Procedure observe,
135                    @Nullable final Procedure onDepsChange,
136                    final int flags )
137  {
138    super( context, name );
139    _task = new Task( context,
140                      name,
141                      this::invokeReaction,
142                      ( flags & Task.Flags.OBSERVER_TASK_FLAGS_MASK ) |
143                      Task.Flags.NO_REGISTER_TASK |
144                      Task.Flags.NO_WRAP_TASK );
145    _flags = ( flags & ~Task.Flags.OBSERVER_TASK_FLAGS_MASK ) | Flags.STATE_INACTIVE;
146    if ( Arez.shouldCheckInvariants() )
147    {
148      if ( Arez.shouldEnforceTransactionType() )
149      {
150        invariant( () -> Transaction.Flags.isTransactionModeValid( flags ),
151                   () -> "Arez-0079: Observer named '" + getName() + "' incorrectly specified both READ_ONLY " +
152                         "and READ_WRITE transaction mode flags." );
153      }
154      else
155      {
156        invariant( () -> !Transaction.Flags.isTransactionModeSpecified( flags ),
157                   () -> "Arez-0082: Observer named '" + getName() + "' specified transaction mode '" +
158                         Transaction.Flags.getTransactionModeName( flags ) + "' when " +
159                         "Arez.enforceTransactionType() is false." );
160      }
161      invariant( () -> Task.Flags.isPriorityValid( _task.getFlags() ),
162                 () -> "Arez-0080: Observer named '" + getName() + "' has invalid priority " +
163                       Task.Flags.getPriorityIndex( _task.getFlags() ) + "." );
164      invariant( () -> Task.Flags.isRunTypeValid( _task.getFlags() ),
165                 () -> "Arez-0081: Observer named '" + getName() + "' incorrectly specified both " +
166                       "RUN_NOW and RUN_LATER flags." );
167      invariant( () -> 0 == ( flags & Observer.Flags.RUN_NOW ) || null != observe,
168                 () -> "Arez-0206: Observer named '" + getName() + "' incorrectly specified " +
169                       "RUN_NOW flag but the observe function is null." );
170      invariant( () -> Arez.areNativeComponentsEnabled() || null == component,
171                 () -> "Arez-0083: Observer named '" + getName() + "' has component specified but " +
172                       "Arez.areNativeComponentsEnabled() is false." );
173      invariant( () -> Task.Flags.getPriority( flags ) != Task.Flags.PRIORITY_LOWEST ||
174                       0 == ( flags & Flags.OBSERVE_LOWER_PRIORITY_DEPENDENCIES ),
175                 () -> "Arez-0184: Observer named '" + getName() + "' has LOWEST priority but has passed " +
176                       "OBSERVE_LOWER_PRIORITY_DEPENDENCIES option which should not be present as the observer " +
177                       "has no lower priority." );
178      invariant( () -> null != observe || null != onDepsChange,
179                 () -> "Arez-0204: Observer named '" + getName() + "' has not supplied a value for either the " +
180                       "observe parameter or the onDepsChange parameter." );
181      // Next lines are impossible situations to create from tests. Add asserts to verify this.
182      assert Flags.KEEPALIVE != Flags.getScheduleType( flags ) || null != observe;
183      assert Flags.APPLICATION_EXECUTOR != Flags.getScheduleType( flags ) || null == observe;
184      assert !( Observer.Flags.RUN_NOW == ( flags & Observer.Flags.RUN_NOW ) &&
185                Flags.KEEPALIVE != Flags.getScheduleType( flags ) &&
186                null != computableValue );
187      invariant( () -> Flags.isNestedActionsModeValid( flags ),
188                 () -> "Arez-0209: Observer named '" + getName() + "' incorrectly specified both the " +
189                       "NESTED_ACTIONS_ALLOWED flag and the NESTED_ACTIONS_DISALLOWED flag." );
190      invariant( () -> Flags.isScheduleTypeValid( flags ),
191                 () -> "Arez-0210: Observer named '" + getName() + "' incorrectly specified multiple " +
192                       "schedule type flags (KEEPALIVE, DEACTIVATE_ON_UNOBSERVE, APPLICATION_EXECUTOR)." );
193      invariant( () -> ( ~( Flags.RUNTIME_FLAGS_MASK | Flags.CONFIG_FLAGS_MASK ) & flags ) == 0,
194                 () -> "Arez-0207: Observer named '" + getName() + "' specified illegal flags: " +
195                       ( ~( Flags.RUNTIME_FLAGS_MASK | Flags.CONFIG_FLAGS_MASK ) & flags ) );
196    }
197    assert null == computableValue || !Arez.areNamesEnabled() || computableValue.getName().equals( name );
198    _component = Arez.areNativeComponentsEnabled() ? component : null;
199    _computableValue = computableValue;
200    _observe = observe;
201    _onDepsChange = onDepsChange;
202
203    executeObserveNextIfPresent();
204
205    if ( null == _computableValue )
206    {
207      if ( null != _component )
208      {
209        _component.addObserver( this );
210      }
211      else if ( Arez.areRegistriesEnabled() )
212      {
213        getContext().registerObserver( this );
214      }
215    }
216    if ( null == _computableValue )
217    {
218      if ( willPropagateSpyEvents() )
219      {
220        getSpy().reportSpyEvent( new ObserverCreateEvent( asInfo() ) );
221      }
222      if ( null != _observe )
223      {
224        initialSchedule();
225      }
226    }
227  }
228
229  void initialSchedule()
230  {
231    getContext().scheduleReaction( this );
232    _task.triggerSchedulerInitiallyUnlessRunLater();
233  }
234
235  boolean areArezDependenciesRequired()
236  {
237    assert Arez.shouldCheckApiInvariants();
238    return Flags.AREZ_DEPENDENCIES == ( _flags & Flags.AREZ_DEPENDENCIES );
239  }
240
241  boolean areExternalDependenciesAllowed()
242  {
243    assert Arez.shouldCheckApiInvariants();
244    return Flags.AREZ_OR_EXTERNAL_DEPENDENCIES == ( _flags & Flags.AREZ_OR_EXTERNAL_DEPENDENCIES );
245  }
246
247  /**
248   * Return true if the Observer supports invocations of {@link #schedule()} from non-arez code.
249   * This is true if both a {@link #_observe} and {@link #_onDepsChange} parameters
250   * are provided at construction.
251   */
252  boolean supportsManualSchedule()
253  {
254    assert Arez.shouldCheckApiInvariants();
255    return null != _observe && null != _onDepsChange;
256  }
257
258  boolean isApplicationExecutor()
259  {
260    assert Arez.shouldCheckApiInvariants();
261    return null == _observe;
262  }
263
264  boolean nestedActionsAllowed()
265  {
266    assert Arez.shouldCheckApiInvariants();
267    return 0 != ( _flags & Flags.NESTED_ACTIONS_ALLOWED );
268  }
269
270  boolean canObserveLowerPriorityDependencies()
271  {
272    assert Arez.shouldCheckApiInvariants();
273    return 0 != ( _flags & Flags.OBSERVE_LOWER_PRIORITY_DEPENDENCIES );
274  }
275
276  boolean noReportResults()
277  {
278    assert Arez.areSpiesEnabled();
279    return 0 != ( _flags & Observer.Flags.NO_REPORT_RESULT );
280  }
281
282  boolean isComputableValue()
283  {
284    return null != _computableValue;
285  }
286
287  /**
288   * Make the Observer INACTIVE and release any resources associated with observer.
289   * The applications should NOT interact with the Observer after it has been disposed.
290   */
291  @Override
292  public void dispose()
293  {
294    if ( isNotDisposedOrDisposing() )
295    {
296      getContext().safeAction( Arez.areNamesEnabled() ? getName() + ".dispose" : null,
297                               this::performDispose,
298                               ActionFlags.NO_VERIFY_ACTION_REQUIRED );
299      if ( !isComputableValue() )
300      {
301        if ( willPropagateSpyEvents() )
302        {
303          reportSpyEvent( new ObserverDisposeEvent( asInfo() ) );
304        }
305        if ( null != _component )
306        {
307          _component.removeObserver( this );
308        }
309        else if ( Arez.areRegistriesEnabled() )
310        {
311          getContext().deregisterObserver( this );
312        }
313      }
314      if ( null != _computableValue )
315      {
316        _computableValue.dispose();
317      }
318      _task.dispose();
319      markAsDisposed();
320    }
321  }
322
323  private void performDispose()
324  {
325    getContext().getTransaction().reportDispose( this );
326    markDependenciesLeastStaleObserverAsUpToDate();
327    setState( Flags.STATE_DISPOSING );
328  }
329
330  void markAsDisposed()
331  {
332    _flags = Flags.setState( _flags, Flags.STATE_DISPOSED );
333  }
334
335  @Override
336  public boolean isDisposed()
337  {
338    return Flags.STATE_DISPOSED == getState();
339  }
340
341  boolean isNotDisposedOrDisposing()
342  {
343    return Flags.STATE_DISPOSING < getState();
344  }
345
346  /**
347   * Return true during invocation of dispose, false otherwise.
348   *
349   * @return true during invocation of dispose, false otherwise.
350   */
351  boolean isDisposing()
352  {
353    return Flags.STATE_DISPOSING == getState();
354  }
355
356  /**
357   * Return the state of the observer relative to the observers dependencies.
358   *
359   * @return the state of the observer relative to the observers dependencies.
360   */
361  int getState()
362  {
363    return Flags.getState( _flags );
364  }
365
366  int getLeastStaleObserverState()
367  {
368    return Flags.getLeastStaleObserverState( _flags );
369  }
370
371  /**
372   * Return true if observer creates a READ_WRITE transaction.
373   *
374   * @return true if observer creates a READ_WRITE transaction.
375   */
376  boolean isMutation()
377  {
378    assert Arez.shouldEnforceTransactionType();
379    return 0 != ( _flags & Flags.READ_WRITE );
380  }
381
382  /**
383   * Return true if the observer is active.
384   * Being "active" means that the state of the observer is not {@link Flags#STATE_INACTIVE},
385   * {@link Flags#STATE_DISPOSING} or {@link Flags#STATE_DISPOSED}.
386   *
387   * <p>An inactive observer has no dependencies and depending on the type of observer may
388   * have other consequences. i.e. An inactive observer will never be scheduled even if it has a
389   * reaction.</p>
390   *
391   * @return true if the Observer is active.
392   */
393  boolean isActive()
394  {
395    return Flags.isActive( _flags );
396  }
397
398  /**
399   * Return true if the observer is not active.
400   * The inverse of {@link #isActive()}
401   *
402   * @return true if the Observer is inactive.
403   */
404  boolean isInactive()
405  {
406    return !isActive();
407  }
408
409  /**
410   * This method should be invoked if the observer has non-arez dependencies and one of
411   * these dependencies has been updated. This will mark the observer as stale and reschedule
412   * the reaction if necessary. The method must be invoked from within a read-write transaction.
413   * the reaction if necessary. The method must be invoked from within a read-write transaction.
414   */
415  public void reportStale()
416  {
417    if ( Arez.shouldCheckApiInvariants() )
418    {
419      apiInvariant( this::areExternalDependenciesAllowed,
420                    () -> "Arez-0199: Observer.reportStale() invoked on observer named '" + getName() +
421                          "' but the observer has not specified AREZ_OR_EXTERNAL_DEPENDENCIES flag." );
422      apiInvariant( () -> getContext().isTransactionActive(),
423                    () -> "Arez-0200: Observer.reportStale() invoked on observer named '" + getName() +
424                          "' when there is no active transaction." );
425      apiInvariant( () -> getContext().getTransaction().isMutation(),
426                    () -> "Arez-0201: Observer.reportStale() invoked on observer named '" + getName() +
427                          "' when the active transaction '" + getContext().getTransaction().getName() +
428                          "' is READ_ONLY rather than READ_WRITE." );
429    }
430    if ( Arez.shouldEnforceTransactionType() && Arez.shouldCheckInvariants() )
431    {
432      getContext().getTransaction().markTransactionAsUsed();
433    }
434    setState( Flags.STATE_STALE );
435  }
436
437  /**
438   * Set the state of the observer.
439   * Call the hook actions for relevant state change.
440   * This is equivalent to passing true in <code>schedule</code> parameter to {@link #setState(int, boolean)}
441   *
442   * @param state the new state of the observer.
443   */
444  void setState( final int state )
445  {
446    setState( state, true );
447  }
448
449  /**
450   * Set the state of the observer.
451   * Call the hook actions for relevant state change.
452   *
453   * @param state    the new state of the observer.
454   * @param schedule true if a state transition can cause observer to reschedule, false otherwise.
455   */
456  void setState( final int state, final boolean schedule )
457  {
458    if ( Arez.shouldCheckInvariants() )
459    {
460      invariant( () -> getContext().isTransactionActive(),
461                 () -> "Arez-0086: Attempt to invoke setState on observer named '" + getName() + "' when there is " +
462                       "no active transaction." );
463      invariantState();
464    }
465    final int originalState = getState();
466    if ( state != originalState )
467    {
468      _flags = Flags.setState( _flags, state );
469      if ( Arez.shouldCheckInvariants() && Flags.STATE_DISPOSED == originalState )
470      {
471        fail( () -> "Arez-0087: Attempted to activate disposed observer named '" + getName() + "'." );
472      }
473      else if ( null == _computableValue && Flags.STATE_STALE == state )
474      {
475        if ( schedule )
476        {
477          scheduleReaction();
478        }
479      }
480      else if ( null != _computableValue &&
481                Flags.STATE_UP_TO_DATE == originalState &&
482                ( Flags.STATE_STALE == state || Flags.STATE_POSSIBLY_STALE == state ) )
483      {
484        _computableValue.getObservableValue().reportPossiblyChanged();
485        if ( schedule )
486        {
487          scheduleReaction();
488        }
489      }
490      else if ( Flags.STATE_INACTIVE == state ||
491                ( Flags.STATE_INACTIVE != originalState && Flags.STATE_DISPOSING == state ) )
492      {
493        if ( isComputableValue() )
494        {
495          getComputableValue().completeDeactivate();
496        }
497        final Map<String, Hook> hooks = getHooks();
498        hooks
499          .values()
500          .stream()
501          .map( Hook::getOnDeactivate )
502          .filter( Objects::nonNull )
503          .forEach( hook -> runHook( hook, ObserverError.ON_DEACTIVATE_ERROR ) );
504        hooks.clear();
505        clearDependencies();
506      }
507      if ( Arez.shouldCheckInvariants() )
508      {
509        invariantState();
510      }
511    }
512  }
513
514  /**
515   * Run the supplied hook if non null.
516   *
517   * @param hook the hook to run.
518   */
519  void runHook( @Nullable final Procedure hook, @Nonnull final ObserverError error )
520  {
521    if ( null != hook )
522    {
523      try
524      {
525        hook.call();
526      }
527      catch ( final Throwable t )
528      {
529        getContext().reportObserverError( this, error, t );
530      }
531    }
532  }
533
534  /**
535   * Remove all dependencies, removing this observer from all dependencies in the process.
536   */
537  void clearDependencies()
538  {
539    getDependencies().forEach( dependency -> {
540      dependency.removeObserver( this );
541      if ( !dependency.hasObservers() )
542      {
543        dependency.setLeastStaleObserverState( Flags.STATE_UP_TO_DATE );
544      }
545    } );
546    getDependencies().clear();
547  }
548
549  /**
550   * Return the task associated with the observer.
551   * The task is used during scheduling.
552   *
553   * @return the task associated with the observer.
554   */
555  @Nonnull
556  Task getTask()
557  {
558    return _task;
559  }
560
561  /**
562   * Schedule this observer if it does not already have a reaction pending.
563   * The observer will not actually react if it is not already marked as STALE.
564   */
565  public void schedule()
566  {
567    if ( Arez.shouldCheckApiInvariants() )
568    {
569      apiInvariant( this::supportsManualSchedule,
570                    () -> "Arez-0202: Observer.schedule() invoked on observer named '" + getName() +
571                          "' but supportsManualSchedule() returns false." );
572    }
573    if ( Arez.shouldEnforceTransactionType() && getContext().isTransactionActive() && Arez.shouldCheckInvariants() )
574    {
575      getContext().getTransaction().markTransactionAsUsed();
576    }
577    executeObserveNextIfPresent();
578    scheduleReaction();
579    getContext().triggerScheduler();
580  }
581
582  /**
583   * Schedule this observer if it does not already have a reaction pending.
584   */
585  void scheduleReaction()
586  {
587    if ( isNotDisposed() )
588    {
589      if ( Arez.shouldCheckInvariants() )
590      {
591        invariant( this::isActive,
592                   () -> "Arez-0088: Observer named '" + getName() + "' is not active but an attempt has been made " +
593                         "to schedule observer." );
594      }
595      if ( !getTask().isQueued() )
596      {
597        getContext().scheduleReaction( this );
598      }
599    }
600  }
601
602  /**
603   * Run the reaction in a transaction with the name and mode defined
604   * by the observer. If the reaction throws an exception, the exception is reported
605   * to the context global ObserverErrorHandlers
606   */
607  void invokeReaction()
608  {
609    if ( isNotDisposed() )
610    {
611      final long start;
612      if ( willPropagateSpyEvents() )
613      {
614        start = System.currentTimeMillis();
615        if ( isComputableValue() )
616        {
617          reportSpyEvent( new ComputeStartEvent( getComputableValue().asInfo() ) );
618        }
619        else
620        {
621          reportSpyEvent( new ObserveStartEvent( asInfo() ) );
622        }
623      }
624      else
625      {
626        start = 0;
627      }
628      Throwable error = null;
629      try
630      {
631        // ComputableValues may have calculated their values and thus be up to date so no need to recalculate.
632        if ( Flags.STATE_UP_TO_DATE != getState() )
633        {
634          if ( shouldExecuteObserveNext() )
635          {
636            executeOnDepsChangeNextIfPresent();
637            runObserveFunction();
638          }
639          else
640          {
641            assert null != _onDepsChange;
642            _onDepsChange.call();
643          }
644        }
645        else if ( shouldExecuteObserveNext() )
646        {
647          /*
648           * The observer should invoke onDepsChange next if the following conditions hold.
649           *  - a manual schedule() invocation
650           *  - the observer is not stale, and
651           *  - there is an onDepsChange hook present
652           *
653           *  This block ensures this is the case.
654           */
655          executeOnDepsChangeNextIfPresent();
656        }
657      }
658      catch ( final Throwable t )
659      {
660        error = t;
661        getContext().reportObserverError( this, ObserverError.REACTION_ERROR, t );
662      }
663      // start == 0 implies that spy events were enabled as part of observer, and thus we can skip this
664      // chain of events
665      if ( willPropagateSpyEvents() && 0 != start )
666      {
667        final long duration = System.currentTimeMillis() - start;
668        if ( isComputableValue() )
669        {
670          final ComputableValue<?> computableValue = getComputableValue();
671          reportSpyEvent( new ComputeCompleteEvent( computableValue.asInfo(),
672                                                    noReportResults() ? null : computableValue.getValue(),
673                                                    computableValue.getError(),
674                                                    (int) duration ) );
675        }
676        else
677        {
678          reportSpyEvent( new ObserveCompleteEvent( asInfo(), error, (int) duration ) );
679        }
680      }
681    }
682  }
683
684  private void runObserveFunction()
685    throws Throwable
686  {
687    assert null != _observe;
688    final Procedure action;
689    if ( Arez.shouldCheckInvariants() && areArezDependenciesRequired() )
690    {
691      action = () -> {
692        _observe.call();
693        final Transaction current = Transaction.current();
694
695        final List<ObservableValue<?>> observableValues = current.getObservableValues();
696        invariant( () -> Objects.requireNonNull( current.getTracker() ).isDisposing() ||
697                         ( null != observableValues && !observableValues.isEmpty() ),
698                   () -> "Arez-0172: Observer named '" + getName() + "' that does not use an external executor " +
699                         "completed observe function but is not observing any properties. As a result the observer " +
700                         "will never be rescheduled." );
701      };
702    }
703    else
704    {
705      action = _observe;
706    }
707    getContext().rawObserve( this, action, null );
708  }
709
710  /**
711   * Utility to mark all dependencies least stale observer as up to date.
712   * Used when the Observer is determined to be up todate.
713   */
714  void markDependenciesLeastStaleObserverAsUpToDate()
715  {
716    for ( final ObservableValue<?> dependency : getDependencies() )
717    {
718      dependency.setLeastStaleObserverState( Flags.STATE_UP_TO_DATE );
719    }
720  }
721
722  /**
723   * Determine if any dependency of the Observer has actually changed.
724   * If the state is POSSIBLY_STALE then recalculate any ComputableValue dependencies.
725   * If any ComputableValue dependencies actually changed then the STALE state will
726   * be propagated.
727   *
728   * <p>By iterating over the dependencies in the same order that they were reported and
729   * stopping on the first change, all the recalculations are only called for ComputableValues
730   * that will be tracked by derivation. That is because we assume that if the first N
731   * dependencies of the derivation doesn't change then the derivation should run the same way
732   * up until accessing N-th dependency.</p>
733   *
734   * @return true if the Observer should be recomputed.
735   */
736  boolean shouldCompute()
737  {
738    final int state = getState();
739    switch ( state )
740    {
741      case Flags.STATE_UP_TO_DATE:
742        return false;
743      case Flags.STATE_INACTIVE:
744      case Flags.STATE_STALE:
745        return true;
746      case Flags.STATE_POSSIBLY_STALE:
747      {
748        for ( final ObservableValue<?> observableValue : getDependencies() )
749        {
750          if ( observableValue.isComputableValue() )
751          {
752            final Observer owner = observableValue.getObserver();
753            final ComputableValue<?> computableValue = owner.getComputableValue();
754            try
755            {
756              computableValue.get();
757            }
758            catch ( final Throwable ignored )
759            {
760            }
761            // Call to get() will update this state if ComputableValue changed
762            if ( Flags.STATE_STALE == getState() )
763            {
764              return true;
765            }
766          }
767        }
768      }
769      break;
770      default:
771        if ( Arez.shouldCheckInvariants() )
772        {
773          fail( () -> "Arez-0205: Observer.shouldCompute() invoked on observer named '" + getName() +
774                      "' but observer is in state " + Flags.getStateName( getState() ) );
775        }
776    }
777    /*
778     * This results in POSSIBLY_STALE returning to UP_TO_DATE
779     */
780    markDependenciesLeastStaleObserverAsUpToDate();
781    return false;
782  }
783
784  /**
785   * Return the hooks.
786   *
787   * @return the hooks.
788   */
789  @Nonnull
790  Map<String, Hook> getHooks()
791  {
792    return _hooks;
793  }
794
795  /**
796   * Replace the current set of hooks with the supplied hooks.
797   *
798   * @param hooks the new set of hooks.
799   */
800  void replaceHooks( @Nonnull final Map<String, Hook> hooks )
801  {
802    _hooks = Objects.requireNonNull( hooks );
803  }
804
805  /**
806   * Return the dependencies.
807   *
808   * @return the dependencies.
809   */
810  @Nonnull
811  List<ObservableValue<?>> getDependencies()
812  {
813    return _dependencies;
814  }
815
816  /**
817   * Replace the current set of dependencies with supplied dependencies.
818   * This should be the only mechanism via which the dependencies are updated.
819   *
820   * @param dependencies the new set of dependencies.
821   */
822  void replaceDependencies( @Nonnull final List<ObservableValue<?>> dependencies )
823  {
824    if ( Arez.shouldCheckInvariants() )
825    {
826      invariantDependenciesUnique( "Pre replaceDependencies" );
827    }
828    _dependencies = Objects.requireNonNull( dependencies );
829    if ( Arez.shouldCheckInvariants() )
830    {
831      invariantDependenciesUnique( "Post replaceDependencies" );
832      invariantDependenciesBackLink( "Post replaceDependencies" );
833      invariantDependenciesNotDisposed();
834    }
835  }
836
837  /**
838   * Ensure the dependencies list contain no duplicates.
839   * Should be optimized away if invariant checking is disabled.
840   *
841   * @param context some useful debugging context used in invariant checks.
842   */
843  void invariantDependenciesUnique( @Nonnull final String context )
844  {
845    if ( Arez.shouldCheckInvariants() )
846    {
847      invariant( () -> getDependencies().size() == new HashSet<>( getDependencies() ).size(),
848                 () -> "Arez-0089: " + context + ": The set of dependencies in observer named '" +
849                       getName() + "' is not unique. Current list: '" +
850                       getDependencies().stream().map( Node::getName ).collect( Collectors.toList() ) + "'." );
851    }
852  }
853
854  /**
855   * Ensure all dependencies contain this observer in the list of observers.
856   * Should be optimized away if invariant checking is disabled.
857   *
858   * @param context some useful debugging context used in invariant checks.
859   */
860  void invariantDependenciesBackLink( @Nonnull final String context )
861  {
862    if ( Arez.shouldCheckInvariants() )
863    {
864      getDependencies().forEach( observable ->
865                                   invariant( () -> observable.getObservers().contains( this ),
866                                              () -> "Arez-0090: " + context + ": Observer named '" + getName() +
867                                                    "' has ObservableValue dependency named '" + observable.getName() +
868                                                    "' which does not contain the observer in the list of " +
869                                                    "observers." ) );
870      invariantComputableValueObserverState();
871    }
872  }
873
874  /**
875   * Ensure all dependencies are not disposed.
876   */
877  void invariantDependenciesNotDisposed()
878  {
879    if ( Arez.shouldCheckInvariants() )
880    {
881      getDependencies().forEach( observable ->
882                                   invariant( observable::isNotDisposed,
883                                              () -> "Arez-0091: Observer named '" + getName() + "' has " +
884                                                    "ObservableValue dependency named '" + observable.getName() +
885                                                    "' which is disposed." ) );
886      invariantComputableValueObserverState();
887    }
888  }
889
890  /**
891   * Ensure that state field and other fields of the Observer are consistent.
892   */
893  void invariantState()
894  {
895    if ( Arez.shouldCheckInvariants() )
896    {
897      if ( isInactive() && !isDisposing() )
898      {
899        invariant( () -> getDependencies().isEmpty(),
900                   () -> "Arez-0092: Observer named '" + getName() + "' is inactive but still has dependencies: " +
901                         getDependencies().stream().map( Node::getName ).collect( Collectors.toList() ) + "." );
902      }
903      if ( null != _computableValue && _computableValue.isNotDisposed() )
904      {
905        final ObservableValue<?> observable = _computableValue.getObservableValue();
906        invariant( () -> Objects.equals( observable.isComputableValue() ? observable.getObserver() : null, this ),
907                   () -> "Arez-0093: Observer named '" + getName() + "' is associated with an ObservableValue that " +
908                         "does not link back to observer." );
909      }
910    }
911  }
912
913  void invariantComputableValueObserverState()
914  {
915    if ( Arez.shouldCheckInvariants() )
916    {
917      if ( isComputableValue() && isActive() && isNotDisposed() )
918      {
919        invariant( () -> !getComputableValue().getObservableValue().getObservers().isEmpty() ||
920                         Objects.equals( getContext().getTransaction().getTracker(), this ),
921                   () -> "Arez-0094: Observer named '" + getName() + "' is a ComputableValue and active but the " +
922                         "associated ObservableValue has no observers." );
923      }
924    }
925  }
926
927  /**
928   * Return the ComputableValue for Observer.
929   * This should not be called if observer is not part of a ComputableValue and will generate an invariant failure
930   * if invariants are enabled.
931   *
932   * @return the associated ComputableValue.
933   */
934  @Nonnull
935  ComputableValue<?> getComputableValue()
936  {
937    if ( Arez.shouldCheckInvariants() )
938    {
939      invariant( this::isComputableValue,
940                 () -> "Arez-0095: Attempted to invoke getComputableValue on observer named '" + getName() + "' when " +
941                       "is not a computable observer." );
942    }
943    assert null != _computableValue;
944    return _computableValue;
945  }
946
947  @Nullable
948  Component getComponent()
949  {
950    return _component;
951  }
952
953  /**
954   * Return the info associated with this class.
955   *
956   * @return the info associated with this class.
957   */
958  @SuppressWarnings( "ConstantConditions" )
959  @OmitSymbol( unless = "arez.enable_spies" )
960  @Nonnull
961  ObserverInfo asInfo()
962  {
963    if ( Arez.shouldCheckInvariants() )
964    {
965      invariant( Arez::areSpiesEnabled,
966                 () -> "Arez-0197: Observer.asInfo() invoked but Arez.areSpiesEnabled() returned false." );
967    }
968    if ( Arez.areSpiesEnabled() && null == _info )
969    {
970      _info = new ObserverInfoImpl( getContext().getSpy(), this );
971    }
972    return Arez.areSpiesEnabled() ? _info : null;
973  }
974
975  @Nullable
976  Procedure getObserve()
977  {
978    return _observe;
979  }
980
981  @Nullable
982  Procedure getOnDepsChange()
983  {
984    return _onDepsChange;
985  }
986
987  boolean isKeepAlive()
988  {
989    return Flags.KEEPALIVE == Flags.getScheduleType( _flags );
990  }
991
992  boolean shouldExecuteObserveNext()
993  {
994    return 0 != ( _flags & Flags.EXECUTE_OBSERVE_NEXT );
995  }
996
997  void executeObserveNextIfPresent()
998  {
999    if ( null != _observe )
1000    {
1001      _flags |= Flags.EXECUTE_OBSERVE_NEXT;
1002    }
1003  }
1004
1005  private void executeOnDepsChangeNextIfPresent()
1006  {
1007    if ( null != _onDepsChange )
1008    {
1009      _flags &= ~Flags.EXECUTE_OBSERVE_NEXT;
1010    }
1011  }
1012
1013  public static final class Flags
1014  {
1015    /**
1016     * The flag can be passed to actions or observers to force the action to not report result to spy infrastructure.
1017     */
1018    public static final int NO_REPORT_RESULT = 1 << 12;
1019    /**
1020     * Highest priority.
1021     * This priority should be used when the observer will dispose or release other reactive elements
1022     * (and thus remove elements from being scheduled).
1023     * <p>Only one of the PRIORITY_* flags should be applied to observer.</p>
1024     *
1025     * @see arez.annotations.Priority#HIGHEST
1026     * @see arez.spy.Priority#HIGHEST
1027     * @see Task.Flags#PRIORITY_HIGHEST
1028     */
1029    public static final int PRIORITY_HIGHEST = 0b001 << 15;
1030    /**
1031     * High priority.
1032     * To reduce the chance that downstream elements will react multiple times within a single
1033     * reaction round, this priority should be used when the observer may trigger many downstream
1034     * reactions.
1035     * <p>Only one of the PRIORITY_* flags should be applied to observer.</p>
1036     *
1037     * @see arez.annotations.Priority#HIGH
1038     * @see arez.spy.Priority#HIGH
1039     * @see Task.Flags#PRIORITY_HIGH
1040     */
1041    public static final int PRIORITY_HIGH = 0b010 << 15;
1042    /**
1043     * Normal priority if no other priority otherwise specified.
1044     * <p>Only one of the PRIORITY_* flags should be applied to observer.</p>
1045     *
1046     * @see arez.annotations.Priority#NORMAL
1047     * @see arez.spy.Priority#NORMAL
1048     * @see Task.Flags#PRIORITY_NORMAL
1049     */
1050    public static final int PRIORITY_NORMAL = 0b011 << 15;
1051    /**
1052     * Low priority.
1053     * Usually used to schedule observers that reflect state onto non-reactive
1054     * application components. i.e. Observers that are used to build html views,
1055     * perform network operations etc. These reactions are often at low priority
1056     * to avoid recalculation of dependencies (i.e. {@link ComputableValue}s) triggering
1057     * this reaction multiple times within a single reaction round.
1058     * <p>Only one of the PRIORITY_* flags should be applied to observer.</p>
1059     *
1060     * @see arez.annotations.Priority#LOW
1061     * @see arez.spy.Priority#LOW
1062     * @see Task.Flags#PRIORITY_LOW
1063     */
1064    public static final int PRIORITY_LOW = 0b100 << 15;
1065    /**
1066     * Lowest priority. Use this priority if the observer is a {@link ComputableValue} that
1067     * may be unobserved when a {@link #PRIORITY_LOW} observer reacts. This is used to avoid
1068     * recomputing state that is likely to either be unobserved or recomputed as part of
1069     * another observers reaction.
1070     * <p>Only one of the PRIORITY_* flags should be applied to observer.</p>
1071     *
1072     * @see arez.annotations.Priority#LOWEST
1073     * @see arez.spy.Priority#LOWEST
1074     * @see Task.Flags#PRIORITY_LOWEST
1075     */
1076    public static final int PRIORITY_LOWEST = 0b101 << 15;
1077    /**
1078     * Mask used to extract priority bits.
1079     */
1080    public static final int PRIORITY_MASK = 0b111 << 15;
1081    /**
1082     * The observer can only read arez state.
1083     */
1084    public static final int READ_ONLY = 1 << 24;
1085    /**
1086     * The observer can read or write arez state.
1087     */
1088    public static final int READ_WRITE = 1 << 23;
1089    /**
1090     * The scheduler will be triggered when the observer is created to immediately invoke the
1091     * {@link Observer#_observe} function. This configuration should not be specified if there
1092     * is no {@link Observer#_observe} function supplied. This should not be
1093     * specified if {@link #RUN_LATER} is specified.
1094     */
1095    @SuppressWarnings( "WeakerAccess" )
1096    public static final int RUN_NOW = 1 << 22;
1097    /**
1098     * The scheduler will not be triggered when the observer is created. The observer either
1099     * has no {@link Observer#_observe} function or is responsible for ensuring that
1100     * {@link ArezContext#triggerScheduler()} is invoked at a later time. This should not be
1101     * specified if {@link #RUN_NOW} is specified.
1102     */
1103    public static final int RUN_LATER = 1 << 21;
1104    /**
1105     * Flag indicating that the Observer is allowed to observe {@link ComputableValue} instances with a lower priority.
1106     */
1107    public static final int OBSERVE_LOWER_PRIORITY_DEPENDENCIES = 1 << 30;
1108    /**
1109     * Indicates that the an action can be created from within the Observers observed function.
1110     */
1111    public static final int NESTED_ACTIONS_ALLOWED = 1 << 29;
1112    /**
1113     * Indicates that the an action must not be created from within the Observers observed function.
1114     */
1115    public static final int NESTED_ACTIONS_DISALLOWED = 1 << 28;
1116    /**
1117     * Flag set set if the application code can not invoke the {@link Observer#reportStale()} method.
1118     *
1119     * @see arez.annotations.DepType#AREZ
1120     */
1121    public static final int AREZ_DEPENDENCIES = 1 << 27;
1122    /**
1123     * Flag set set if the application code can not invokethe  {@link Observer#reportStale()} method to indicate
1124     * that a dependency has changed. In this scenario it is not an error if the observer does not invoke the
1125     * {@link ObservableValue#reportObserved()} on a dependency during it's reaction.
1126     *
1127     * @see arez.annotations.DepType#AREZ_OR_NONE
1128     */
1129    public static final int AREZ_OR_NO_DEPENDENCIES = 1 << 26;
1130    /**
1131     * Flag set if the application code can invoke the {@link Observer#reportStale()} method to indicate that a non-arez dependency has changed.
1132     *
1133     * @see arez.annotations.DepType#AREZ_OR_EXTERNAL
1134     */
1135    public static final int AREZ_OR_EXTERNAL_DEPENDENCIES = 1 << 25;
1136    /**
1137     * The runtime will keep the observer reacting to dependencies until disposed. This is the default value for
1138     * observers that supply a observed function.
1139     */
1140    public static final int KEEPALIVE = 1 << 20;
1141    /**
1142     * Mask used to extract dependency type bits.
1143     */
1144    private static final int DEPENDENCIES_TYPE_MASK =
1145      AREZ_DEPENDENCIES | AREZ_OR_NO_DEPENDENCIES | AREZ_OR_EXTERNAL_DEPENDENCIES;
1146    /**
1147     * Mask to extract "NESTED_ACTIONS" option so can derive default value if required.
1148     */
1149    private static final int NESTED_ACTIONS_MASK = NESTED_ACTIONS_ALLOWED | NESTED_ACTIONS_DISALLOWED;
1150    /**
1151     * Flag indicating whether next scheduled invocation of {@link Observer} should invoke {@link Observer#_observe} or {@link Observer#_onDepsChange}.
1152     */
1153    static final int EXECUTE_OBSERVE_NEXT = 1 << 9;
1154    /**
1155     * Mask used to extract state bits.
1156     * State is the lowest bits as it is the most frequently accessed numeric fields
1157     * and placing values at lower part of integer avoids a shift.
1158     */
1159    static final int STATE_MASK = 0b111;
1160    /**
1161     * Mask that identifies the bits associated with runtime configuration.
1162     */
1163    static final int RUNTIME_FLAGS_MASK = EXECUTE_OBSERVE_NEXT | STATE_MASK;
1164    /**
1165     * The observer has been disposed.
1166     */
1167    static final int STATE_DISPOSED = 0b001;
1168    /**
1169     * The observer is in the process of being disposed.
1170     */
1171    static final int STATE_DISPOSING = 0b010;
1172    /**
1173     * The observer is not active and is not holding any data about it's dependencies.
1174     * Typically mean this tracker observer has not been run or if it is a ComputableValue that
1175     * there is no observer observing the associated ObservableValue.
1176     */
1177    static final int STATE_INACTIVE = 0b011;
1178    /**
1179     * No change since last time observer was notified.
1180     */
1181    static final int STATE_UP_TO_DATE = 0b100;
1182    /**
1183     * A transitive dependency has changed but it has not been determined if a shallow
1184     * dependency has changed. The observer will need to check if shallow dependencies
1185     * have changed. Only Derived observables will propagate POSSIBLY_STALE state.
1186     */
1187    static final int STATE_POSSIBLY_STALE = 0b101;
1188    /**
1189     * A dependency has changed so the observer will need to recompute.
1190     */
1191    static final int STATE_STALE = 0b110;
1192    /**
1193     * The flag is valid on observers associated with computable values and will deactivate the observer if the
1194     * computable value has no observers.
1195     */
1196    static final int DEACTIVATE_ON_UNOBSERVE = 1 << 19;
1197    /**
1198     * The flag is valid on observers where the observed function is invoked by the application.
1199     */
1200    static final int APPLICATION_EXECUTOR = 1 << 18;
1201    /**
1202     * Mask used to extract react type bits.
1203     */
1204    static final int SCHEDULE_TYPE_MASK = KEEPALIVE | DEACTIVATE_ON_UNOBSERVE | APPLICATION_EXECUTOR;
1205    /**
1206     * Mask that identifies the bits associated with static configuration.
1207     */
1208    static final int CONFIG_FLAGS_MASK =
1209      PRIORITY_MASK |
1210      RUN_NOW | RUN_LATER |
1211      READ_ONLY | READ_WRITE |
1212      OBSERVE_LOWER_PRIORITY_DEPENDENCIES |
1213      NESTED_ACTIONS_MASK |
1214      DEPENDENCIES_TYPE_MASK |
1215      SCHEDULE_TYPE_MASK |
1216      NO_REPORT_RESULT;
1217
1218    /**
1219     * Extract and return the observer's state.
1220     *
1221     * @param flags the flags.
1222     * @return the state.
1223     */
1224    static int getState( final int flags )
1225    {
1226      return flags & STATE_MASK;
1227    }
1228
1229    /**
1230     * Return the new value of flags when supplied with specified state.
1231     *
1232     * @param flags the flags.
1233     * @param state the new state.
1234     * @return the new flags.
1235     */
1236    static int setState( final int flags, final int state )
1237    {
1238      return ( flags & ~STATE_MASK ) | state;
1239    }
1240
1241    /**
1242     * Return true if the state is UP_TO_DATE, POSSIBLY_STALE or STALE.
1243     * The inverse of {@link #isNotActive(int)}
1244     *
1245     * @param flags the flags to check.
1246     * @return true if the state is UP_TO_DATE, POSSIBLY_STALE or STALE.
1247     */
1248    static boolean isActive( final int flags )
1249    {
1250      return getState( flags ) > STATE_INACTIVE;
1251    }
1252
1253    /**
1254     * Return true if the state is INACTIVE, DISPOSING or DISPOSED.
1255     * The inverse of {@link #isActive(int)}
1256     *
1257     * @param flags the flags to check.
1258     * @return true if the state is INACTIVE, DISPOSING or DISPOSED.
1259     */
1260    static boolean isNotActive( final int flags )
1261    {
1262      return !isActive( flags );
1263    }
1264
1265    /**
1266     * Return the least stale observer state. if the state is not active
1267     * then the {@link #STATE_UP_TO_DATE} will be returned.
1268     *
1269     * @param flags the flags to check.
1270     * @return the least stale observer state.
1271     */
1272    static int getLeastStaleObserverState( final int flags )
1273    {
1274      final int state = getState( flags );
1275      return state > STATE_INACTIVE ? state : STATE_UP_TO_DATE;
1276    }
1277
1278    /**
1279     * Return the state as a string.
1280     *
1281     * @param state the state value. One of the STATE_* constants
1282     * @return the string describing state.
1283     */
1284    @Nonnull
1285    static String getStateName( final int state )
1286    {
1287      assert Arez.shouldCheckInvariants() || Arez.shouldCheckApiInvariants();
1288      switch ( state )
1289      {
1290        case STATE_DISPOSED:
1291          return "DISPOSED";
1292        case STATE_DISPOSING:
1293          return "DISPOSING";
1294        case STATE_INACTIVE:
1295          return "INACTIVE";
1296        case STATE_POSSIBLY_STALE:
1297          return "POSSIBLY_STALE";
1298        case STATE_STALE:
1299          return "STALE";
1300        case STATE_UP_TO_DATE:
1301          return "UP_TO_DATE";
1302        default:
1303          return "UNKNOWN(" + state + ")";
1304      }
1305    }
1306
1307    static int nestedActionRule( final int flags )
1308    {
1309      return Arez.shouldCheckInvariants() ?
1310             0 != ( flags & NESTED_ACTIONS_MASK ) ? 0 : NESTED_ACTIONS_DISALLOWED :
1311             0;
1312    }
1313
1314    /**
1315     * Return true if flags contains a valid nested action mode.
1316     *
1317     * @param flags the flags.
1318     * @return true if flags contains valid nested action mode.
1319     */
1320    static boolean isNestedActionsModeValid( final int flags )
1321    {
1322      return NESTED_ACTIONS_ALLOWED == ( flags & NESTED_ACTIONS_ALLOWED ) ^
1323             NESTED_ACTIONS_DISALLOWED == ( flags & NESTED_ACTIONS_DISALLOWED );
1324    }
1325
1326    /**
1327     * Return the default dependency type flag if dependency type not specified.
1328     *
1329     * @param flags the flags.
1330     * @return the default dependency type if dependency type unspecified else 0.
1331     */
1332    static int dependencyType( final int flags )
1333    {
1334      return Arez.shouldCheckInvariants() ? 0 != ( flags & DEPENDENCIES_TYPE_MASK ) ? 0 : AREZ_DEPENDENCIES : 0;
1335    }
1336
1337    /**
1338     * Extract and return the schedule type.
1339     *
1340     * @param flags the flags.
1341     * @return the schedule type.
1342     */
1343    static int getScheduleType( final int flags )
1344    {
1345      return flags & SCHEDULE_TYPE_MASK;
1346    }
1347
1348    /**
1349     * Return true if flags contains a valid ScheduleType.
1350     *
1351     * @param flags the flags.
1352     * @return true if flags contains a valid ScheduleType.
1353     */
1354    static boolean isScheduleTypeValid( final int flags )
1355    {
1356      return KEEPALIVE == ( flags & KEEPALIVE ) ^
1357             DEACTIVATE_ON_UNOBSERVE == ( flags & DEACTIVATE_ON_UNOBSERVE ) ^
1358             APPLICATION_EXECUTOR == ( flags & APPLICATION_EXECUTOR );
1359    }
1360  }
1361}