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