Package utilities.util

# Class SuppressableCallback<T>

java.lang.Object
utilities.util.SuppressableCallback<T>

Type Parameters:
  `T` - the type of return value, often `Void`

public class SuppressableCallback<T> extends Object
A mechanism for preventing callback loops, stack overflows, event storms, etc.

This thing really is a terrible hack, but it becomes necessary when the cause of an event cannot be reliably determined. In cases where the callback (lacking a cause parameter) is expected to be invoked on the same stack as the method calling, this mechanism can optionally suppress that callback.

Suppression is implemented on a per-thread basis, so that suppression requests are only effective when the callback is in fact on the same stack as the request.

A common use case is for a method to suppress any recursive calls, i.e., it is self suppressing, e.g.:

     private final SuppressableCallback<Void> cbDoSomething = new SuppressableCallback<>();

     public boolean doSomething() {
     	cbDoSomething.invoke(__ -> {
     		try (Suppression supp = cbDoSomething.supppress(null)) {
     			// do the thing
     			return doAnotherThing();
     		}
     	}, false);
     }

     public boolean doAnotherThing() {
     	// do the other thing
     	return doSomething();
     }

This example is very trivial, but this sort of thing can easily happen in event driven programming. Consider the case where a state change causes an update to a table selection, which fires the selection changed event, in turn requesting the same state change. Checking for equality of the requested state with the current state can resolve some cases, but such checks are thwarted when any two events requesting unequal state get queued on an event thread, resulting in unending oscillation between the requested states. A more robust solution demands we know the cause(s) of events. For now, the solution is to suppress event firing whenever a state change is due to receiving an event.

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Class
  Description
  `static class`
  `SuppressableCallback.Suppression`
  A suppression handle on the callback, for a specific thread

- ## Constructor Summary

  Constructors
  Constructor
  Description
  `SuppressableCallback()`

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `invoke(Runnable callback)`
  Run the given callback unless it has been suppressed
  `<R> R`
  `invoke(Supplier<R> callback, R fallback)`
  Run the given callback, returning its value, unless it has been suppressed
  `void`
  `invokeWithStack(Consumer<List<T>> callback)`
  Run the given callback with the values from the stack of suppression requests
  `<R> R`
  `invokeWithStack(Function<List<T>,R> callback)`
  Run the given callback with the stack, returning its value
  `void`
  `invokeWithTop(Consumer<T> callback)`
  Run the given callback with the topmost suppression value or `null` if not suppressed
  `<R> R`
  `invokeWithTop(Function<T,R> callback)`
  Run the given callback with the topmost value, returning its value
  `SuppressableCallback.Suppression`
  `suppress(T value)`
  Suppress this callback, providing the given value as information

  ### Methods inherited from class java.lang.Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- ## Constructor Details

  - ### SuppressableCallback

    public SuppressableCallback()

- ## Method Details

  - ### suppress

    public SuppressableCallback.Suppression suppress(T value)
    Suppress this callback, providing the given value as information

    This should almost always be used in a try-with-resources block. The only exception is perhaps to wrap this in another `AutoCloseable`. The information is usually one of the parameters, or perhaps a n-tuple of all parameters. For many use cases, `null` is all that is needed. The information is needed if the callback would like to make the suppression decision based on that information, e.g., is this callback telling me to "go to" a place I'm already "going to"?

    Parameters:
      `value` - the value
    Returns:
      a handle to the request

  - ### invoke

    public void invoke(Runnable callback)
    Run the given callback unless it has been suppressed

    The values on the stack of suppression requests do not matter. If there's a request, the callback is suppressed.

    Parameters:
      `callback` - the callback

  - ### invoke

    public <R> R invoke(Supplier<R> callback, R fallback)
    Run the given callback, returning its value, unless it has been suppressed

    Works like `invoke(Runnable)`, except that the callback can return a value. If the callback is suppressed, a fallback value is returned instead.

    Type Parameters:
      `R` - the return type
    Parameters:
      `callback` - the callback
      `fallback` - a fallback value in case of suppression
    Returns:
      the value from the callback, or the fallback value

  - ### invokeWithTop

    public void invokeWithTop(Consumer<T> callback)
    Run the given callback with the topmost suppression value or `null` if not suppressed

    The callback is always invoked, allow it to decide what actions to take (or not take) based on the given value. Not for a `SuppressableCallback<T>`, this method is useless, as the provided value will always be `null`, whether or not suppressed.

    Parameters:
      `callback` - the callback

  - ### invokeWithTop

    public <R> R invokeWithTop(Function<T,R> callback)
    Run the given callback with the topmost value, returning its value

    This works like `invokeWithTop(Consumer)`, except that the callback can return a value. Note that a fallback parameter is not taken, since the callback is always invoked. The callback should implement its own fallback logic.

    Type Parameters:
      `R` - the return type
    Parameters:
      `callback` - the callback
    Returns:
      the value from the callback

  - ### invokeWithStack

    public void invokeWithStack(Consumer<List<T>> callback)
    Run the given callback with the values from the stack of suppression requests

    The callback is always invoked, allowing it to decide what actions to take (or not take) based on the values present in the stack.

    Parameters:
      `callback` - a method to receive the stack

  - ### invokeWithStack

    public <R> R invokeWithStack(Function<List<T>,R> callback)
    Run the given callback with the stack, returning its value

    This works like `invokeWithStack(Consumer)`, except that the callback can return a value. Note that a fallback parameter is not taken, since the callback is always invoked. The callback should implement its own fallback logic.

    Type Parameters:
      `R` - the return type
    Parameters:
      `callback` - the callback
    Returns:
      the value from the callback