/** * Gets a future that completes when the state is no longer {@code .equals()} to {@code currentState)}. */ public ListenableFuture<T> getStateChange(T currentState) { checkState(!Thread.holdsLock(lock), "Can not wait for state change while holding the lock"); requireNonNull(currentState, "currentState is null"); synchronized (lock) { // return a completed future if the state has already changed, or we are in a terminal state if (!state.equals(currentState) || isTerminalState(state)) { return immediateFuture(state); } return futureStateChange.get().createNewListener(); } }
checkState(!isTerminalState(state), "%s can not transition from %s to %s", name, state, newState); if (isTerminalState(state)) { this.stateChangeListeners.clear();
/** * Sets the state. * If the new state does not {@code .equals()} the current state, listeners and waiters will be notified. * * @return the old state */ public T set(T newState) { checkState(!Thread.holdsLock(lock), "Can not set state while holding the lock"); requireNonNull(newState, "newState is null"); T oldState; FutureStateChange<T> futureStateChange; ImmutableList<StateChangeListener<T>> stateChangeListeners; synchronized (lock) { if (state.equals(newState)) { return state; } checkState(!isTerminalState(state), "%s can not transition from %s to %s", name, state, newState); oldState = state; state = newState; futureStateChange = this.futureStateChange.getAndSet(new FutureStateChange<>()); stateChangeListeners = ImmutableList.copyOf(this.stateChangeListeners); // if we are now in a terminal state, free the listeners since this will be the last notification if (isTerminalState(state)) { this.stateChangeListeners.clear(); } } fireStateChanged(newState, futureStateChange, stateChangeListeners); return oldState; }
/** * Adds a listener to be notified when the state instance changes according to {@code .equals()}. * Listener is always notified asynchronously using a dedicated notification thread pool so, care should * be taken to avoid leaking {@code this} when adding a listener in a constructor. Additionally, it is * possible notifications are observed out of order due to the asynchronous execution. The listener is * immediately notified immediately of the current state. */ public void addStateChangeListener(StateChangeListener<T> stateChangeListener) { requireNonNull(stateChangeListener, "stateChangeListener is null"); boolean inTerminalState; T currentState; synchronized (lock) { currentState = state; inTerminalState = isTerminalState(currentState); if (!inTerminalState) { stateChangeListeners.add(stateChangeListener); } } // fire state change listener with the current state // always fire listener callbacks from a different thread safeExecute(() -> stateChangeListener.stateChanged(currentState)); }
private static void assertStateChange(StateMachine<State> stateMachine, StateChanger stateChange, State expectedState) throws Exception { State initialState = stateMachine.get(); ListenableFuture<State> futureChange = stateMachine.getStateChange(initialState); SettableFuture<State> listenerChange = addTestListener(stateMachine); stateChange.run(); assertEquals(stateMachine.get(), expectedState); assertEquals(futureChange.get(10, SECONDS), expectedState); assertEquals(listenerChange.get(10, SECONDS), expectedState); // listeners should not be retained if we are in a terminal state boolean isTerminalState = stateMachine.isTerminalState(expectedState); if (isTerminalState) { assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of()); } }
private static void assertNoStateChange(StateMachine<State> stateMachine, StateChanger stateChange) { State initialState = stateMachine.get(); ListenableFuture<State> futureChange = stateMachine.getStateChange(initialState); SettableFuture<State> listenerChange = addTestListener(stateMachine); // listeners should not be added if we are in a terminal state, but listener should fire boolean isTerminalState = stateMachine.isTerminalState(initialState); if (isTerminalState) { assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of()); } stateChange.run(); assertEquals(stateMachine.get(), initialState); // the future change will trigger if the state machine is in a terminal state // this is to prevent waiting for state changes that will never occur assertEquals(futureChange.isDone(), isTerminalState); futureChange.cancel(true); // test listener future only completes if the state actually changed assertFalse(listenerChange.isDone()); listenerChange.cancel(true); }
private boolean isPossibleStateChange(T currentState) { return !state.equals(currentState) || isTerminalState(state); }
/** * Adds a listener to be notified when the state instance changes according to {@code .equals()}. */ public void addStateChangeListener(StateChangeListener<T> stateChangeListener) { requireNonNull(stateChangeListener, "stateChangeListener is null"); boolean inTerminalState; synchronized (lock) { inTerminalState = isTerminalState(state); if (!inTerminalState) { stateChangeListeners.add(stateChangeListener); } } // state machine will never transition from a terminal state, so fire state change immediately if (inTerminalState) { stateChangeListener.stateChanged(state); } }
checkState(!isTerminalState(state), "%s can not transition from %s to %s", name, state, newState); if (isTerminalState(state)) { this.stateChangeListeners.clear();
checkState(!isTerminalState(state), "%s can not transition from %s to %s", name, state, newState); if (isTerminalState(state)) { this.stateChangeListeners.clear();
boolean isTerminalState = stateMachine.isTerminalState(expectedState); if (isTerminalState) { assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of());
boolean isTerminalState = stateMachine.isTerminalState(initialState); if (isTerminalState) { assertEquals(stateMachine.getStateChangeListeners(), ImmutableSet.of());