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