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