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