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