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