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