org.gradle.internal.model.StateTransitionController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-test-kit Show documentation
Show all versions of gradle-test-kit Show documentation
Gradle 6.2.1 API redistribution.
/*
* Copyright 2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.internal.model;
import org.gradle.internal.DisplayName;
import org.gradle.internal.UncheckedException;
import org.gradle.internal.build.ExecutionResult;
import org.gradle.internal.work.Synchronizer;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Manages the transition between states of some object with mutable state.
*
* Adds validation to ensure that the object is in an expected state and applies thread safety.
*/
@ThreadSafe
public class StateTransitionController {
private final DisplayName displayName;
private final Synchronizer synchronizer;
// This structure is immutable, and this field is mutated only by the thread that owns the lock
private volatile CurrentState state;
public StateTransitionController(DisplayName displayName, T initialState, Synchronizer synchronizer) {
this.displayName = displayName;
this.synchronizer = synchronizer;
this.state = new InState<>(displayName, initialState, null);
}
/**
* Verifies that the current state is not the given state. Ignores any transition in progress and failures of previous operations.
*
* You should try to not use this method, as it does not provide any thread safety for the code that follows the call.
*/
public void assertNotInState(T forbidden) {
if (state.state == forbidden) {
throw new IllegalStateException(displayName.getCapitalizedDisplayName() + " should not be in state " + forbidden + ".");
}
}
/**
* Verifies that the current state is the given state or some later state. Ignores any transition in progress and failures of previous operations.
*
* You should try to not use this method, as it does not provide any thread safety for the code that follows the call.
*/
public void assertInStateOrLater(T expected) {
CurrentState current = state;
if (!current.hasSeenStateIgnoringTransitions(expected)) {
throw new IllegalStateException(displayName.getCapitalizedDisplayName() + " should be in state " + expected + " or later.");
}
}
/**
* Calculates a value when the current state is not the given state. Allows concurrent access to the state and does not block other threads from transitioning the state.
* Fails if the current state is the given state or if a transition to the given state is happening or a previous transition has failed.
*
* You should try to not use this method, as it does not provide full thread safety.
*/
public S notInStateIgnoreOtherThreads(T forbidden, Supplier supplier) {
CurrentState current = state;
current.asResult().rethrow();
current.assertNotInState(forbidden);
try {
return supplier.get();
} catch (Throwable t) {
// TODO - remove the need for locking here
synchronizer.withLock(() -> {
state = state.failed(ExecutionResult.failed(t));
});
throw UncheckedException.throwAsUncheckedException(t);
}
}
/**
* Runs the given action, verifying the current state is the expected state.
* Fails if the current state is not the given state or a previous operation has failed.
* Blocks until other operations are complete.
*/
public void inState(T expected, Runnable action) {
inState(expected, () -> {
action.run();
return null;
});
}
/**
* Runs the given action, verifying the current state is the expected state.
* Fails if the current state is not the given state, the current thread is transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public S inState(T expected, Supplier action) {
return synchronizer.withLock(() -> {
CurrentState current = state;
current.assertInState(expected);
try {
return action.get();
} catch (Throwable t) {
state = current.failed(ExecutionResult.failed(t));
throw state.rethrow();
}
});
}
/**
* Runs the given action, verifying the current state is not the forbidden state.
* Fails if the current state is the given state, the current thread is transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public S notInState(T forbidden, Supplier action) {
return synchronizer.withLock(() -> {
CurrentState current = state;
current.assertNotInState(forbidden);
try {
return action.get();
} catch (Throwable t) {
state = current.failed(ExecutionResult.failed(t));
throw state.rethrow();
}
});
}
/**
* Resets the state to the given state.
* Fails if the current state is not the given state, ignores failures from previous operations.
* Blocks until other operations are complete.
*/
public void restart(T fromState, T toState, Runnable action) {
synchronizer.withLock(() -> {
CurrentState current = state;
current.assertCanTransition(fromState, toState, true);
action.run();
state = new InState<>(displayName, toState, null);
});
}
/**
* Transitions to the given "to" state.
* Fails if the current state is not the given "from" state, the current thread is transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public void transition(T fromState, T toState, Runnable action) {
synchronizer.withLock(() -> doTransition(fromState, toState, action));
}
/**
* Transitions to the given "to" state.
* Fails if the current state is not the given "from" state, the current thread is transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public S transition(T fromState, T toState, Supplier extends S> action) {
return synchronizer.withLock(() -> doTransition(fromState, toState, () -> ExecutionResult.succeeded(action.get())).getValueOrRethrow());
}
/**
* Transitions to the given "to" state, returning any failure in the result object.
* Fails if the current state is not the given "from" state, the current thread is transitioning the state, or a previous operation has failed.
*/
public ExecutionResult tryTransition(T fromState, T toState, Supplier> action) {
return synchronizer.withLock(() -> doTransition(fromState, toState, action));
}
/**
* Transitions to the given "to" state. Does nothing if the current state is the "to" state.
* Fails if the current state is not either of the given "to" or "from" state, the current thread is currently transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public void maybeTransition(T fromState, T toState, Runnable action) {
synchronizer.withLock(() -> {
if (state.inStateAndNotTransitioning(toState)) {
return;
}
doTransition(fromState, toState, action);
});
}
public void maybeTransitionIfNotCurrentlyTransitioning(T fromState, T toState, Runnable action) {
synchronizer.withLock(() -> {
if (state.inStateOrTransitioningTo(toState)) {
return;
}
doTransition(fromState, toState, action);
});
}
/**
* Transitions to the given "to" state. Does nothing if the "to" state has already been transitioned to at some point in the past (but is not necessarily the current state).
* Fails if the current state is not the given "from" state, the current thread is currently transitioning the state, or a previous operation has failed.
* Blocks until other operations are complete.
*/
public void transitionIfNotPreviously(T fromState, T toState, Runnable action) {
synchronizer.withLock(() -> {
if (state.hasSeenStateAndNotTransitioning(toState)) {
return;
}
doTransition(fromState, toState, action);
});
}
/**
* Transitions to a final state, taking any failures from previous transitions and transforming them.
*/
public ExecutionResult transition(T fromState, T toState, Function, ExecutionResult> action) {
return synchronizer.withLock(() -> {
CurrentState current = state;
current.assertCanTransition(fromState, toState, true);
return doTransitionWithFailures(toState, action, current);
});
}
public ExecutionResult transition(List fromStates, T toState, Function, ExecutionResult> action) {
return synchronizer.withLock(() -> {
CurrentState current = state;
current.assertCanTransition(fromStates, toState, true);
return doTransitionWithFailures(toState, action, current);
});
}
private ExecutionResult doTransitionWithFailures(T toState, Function, ExecutionResult> action, CurrentState current) {
ExecutionResult currentResult = current.asResult();
state = current.transitioningTo(toState);
ExecutionResult result;
try {
result = action.apply(currentResult);
} catch (Throwable t) {
result = ExecutionResult.failed(t);
}
if (!result.getFailures().isEmpty()) {
state = state.failed(result);
} else {
state = state.nextState(toState);
}
return result;
}
private void doTransition(T fromState, T toState, Runnable action) {
doTransition(fromState, toState, () -> {
action.run();
return ExecutionResult.succeeded();
}).getValueOrRethrow();
}
private ExecutionResult doTransition(T fromState, T toState, Supplier> action) {
CurrentState current = state;
current.assertCanTransition(fromState, toState);
state = current.transitioningTo(toState);
ExecutionResult result;
try {
result = action.get();
} catch (Throwable t) {
result = ExecutionResult.failed(t);
}
if (!result.getFailures().isEmpty()) {
state = state.failed(result);
} else {
state = state.nextState(toState);
}
return result;
}
private static abstract class CurrentState {
final DisplayName displayName;
final T state;
public CurrentState(DisplayName displayName, T state) {
this.displayName = displayName;
this.state = state;
}
public abstract void assertInState(T expected);
public abstract void assertNotInState(T forbidden);
public void assertCanTransition(T fromState, T toState) {
assertCanTransition(fromState, toState, false);
}
public abstract void assertCanTransition(T fromState, T toState, boolean ignoreFailures);
public abstract void assertCanTransition(List fromStates, T toState, boolean ignoreFailures);
public abstract boolean inStateAndNotTransitioning(T toState);
public abstract boolean inStateOrTransitioningTo(T toState);
public abstract boolean hasSeenStateAndNotTransitioning(T toState);
public abstract boolean hasSeenStateIgnoringTransitions(T toState);
public CurrentState failed(ExecutionResult> failure) {
return new Failed<>(displayName, state, failure);
}
public RuntimeException rethrow() {
throw new IllegalStateException();
}
public ExecutionResult asResult() {
return ExecutionResult.succeeded();
}
public CurrentState transitioningTo(T toState) {
return new TransitioningToNewState(toState, this);
}
public abstract CurrentState nextState(T toState);
}
/**
* Currently in the given state.
*/
private static class InState extends CurrentState {
private final DisplayName displayName;
@Nullable
private final InState previous;
public InState(DisplayName displayName, T state, @Nullable InState previous) {
super(displayName, state);
this.displayName = displayName;
this.previous = previous;
}
@Override
public void assertInState(T expected) {
if (state != expected) {
throw new IllegalStateException("Expected " + displayName.getDisplayName() + " to be in state " + expected + " but is in state " + state + ".");
}
}
@Override
public void assertNotInState(T forbidden) {
if (state == forbidden) {
throw new IllegalStateException(displayName.getCapitalizedDisplayName() + " should not be in state " + forbidden + ".");
}
}
@Override
public void assertCanTransition(T fromState, T toState, boolean ignoreFailures) {
if (state != fromState) {
throw new IllegalStateException("Can only transition " + displayName.getCapitalizedDisplayName() + " to state " + toState + " from state " + fromState + " however it is currently in state " + state + ".");
}
}
@Override
public void assertCanTransition(List fromStates, T toState, boolean ignoreFailures) {
if (!fromStates.contains(state)) {
throw new IllegalStateException("Can only transition " + displayName.getCapitalizedDisplayName() + " to state " + toState + " from states " + fromStates + " however it is currently in state " + state + ".");
}
}
@Override
public boolean inStateAndNotTransitioning(T toState) {
return state == toState;
}
@Override
public boolean inStateOrTransitioningTo(T toState) {
return inStateAndNotTransitioning(toState);
}
@Override
public boolean hasSeenStateAndNotTransitioning(T toState) {
if (state == toState) {
return true;
}
if (previous != null) {
return previous.hasSeenStateAndNotTransitioning(toState);
}
return false;
}
@Override
public boolean hasSeenStateIgnoringTransitions(T toState) {
return hasSeenStateAndNotTransitioning(toState);
}
@Override
public CurrentState nextState(T toState) {
return new InState<>(displayName, toState, this);
}
}
/**
* Currently transitioning to a new state.
*/
private static class TransitioningToNewState extends CurrentState {
final T targetState;
final CurrentState fromState;
public TransitioningToNewState(T targetState, CurrentState fromState) {
super(fromState.displayName, fromState.state);
this.targetState = targetState;
this.fromState = fromState;
}
@Override
public boolean inStateAndNotTransitioning(T toState) {
throw new IllegalStateException("Expected " + displayName.getDisplayName() + " to be in state " + toState + " but is in state " + state + " and transitioning to " + targetState + ".");
}
@Override
public boolean inStateOrTransitioningTo(T toState) {
if (targetState == toState) {
return true;
}
throw new IllegalStateException("Expected " + displayName.getDisplayName() + " to be in state " + toState + " but is in state " + state + " and transitioning to " + targetState + ".");
}
@Override
public void assertInState(T expected) {
throw new IllegalStateException("Expected " + displayName.getDisplayName() + " to be in state " + expected + " but is in state " + state + " and transitioning to " + targetState + ".");
}
@Override
public void assertNotInState(T forbidden) {
throw new IllegalStateException(displayName.getCapitalizedDisplayName() + " should not be in state " + forbidden + " but is in state " + state + " and transitioning to " + targetState + ".");
}
@Override
public void assertCanTransition(T fromState, T toState, boolean ignoreFailures) {
failDueToTransition(toState);
}
@Override
public void assertCanTransition(List fromStates, T toState, boolean ignoreFailures) {
failDueToTransition(toState);
}
private void failDueToTransition(T toState) {
if (targetState == toState) {
throw new IllegalStateException("Cannot transition " + displayName.getDisplayName() + " to state " + toState + " as already transitioning to this state.");
} else {
throw new IllegalStateException("Cannot transition " + displayName.getDisplayName() + " to state " + toState + " as already transitioning to state " + targetState + ".");
}
}
@Override
public boolean hasSeenStateAndNotTransitioning(T toState) {
throw new IllegalStateException("Expected " + displayName.getDisplayName() + " to be in state " + toState + " or later but is in state " + state + " and transitioning to " + targetState + ".");
}
@Override
public boolean hasSeenStateIgnoringTransitions(T toState) {
return fromState.hasSeenStateIgnoringTransitions(toState);
}
@Override
public CurrentState nextState(T toState) {
return fromState.nextState(toState);
}
}
/**
* A previous operation has failed.
*/
private static class Failed extends CurrentState {
final ExecutionResult> failure;
public Failed(DisplayName displayName, T state, ExecutionResult> failure) {
super(displayName, state);
this.failure = failure;
}
public void throwFailure() {
failure.rethrow();
}
@Override
public void assertInState(T expected) {
throwFailure();
}
@Override
public void assertNotInState(T forbidden) {
throwFailure();
}
@Override
public void assertCanTransition(List fromStates, T toState, boolean ignoreFailures) {
if (!ignoreFailures) {
throwFailure();
}
}
@Override
public void assertCanTransition(T fromState, T toState, boolean ignoreFailures) {
if (!ignoreFailures) {
throwFailure();
}
}
@Override
public boolean hasSeenStateAndNotTransitioning(T toState) {
throwFailure();
return false;
}
@Override
public ExecutionResult asResult() {
return failure.asFailure();
}
@Override
public boolean inStateAndNotTransitioning(T toState) {
throwFailure();
return false;
}
@Override
public boolean inStateOrTransitioningTo(T toState) {
throwFailure();
return false;
}
@Override
public boolean hasSeenStateIgnoringTransitions(T toState) {
throwFailure();
return false;
}
@Override
public RuntimeException rethrow() {
failure.rethrow();
throw new IllegalStateException();
}
@Override
public CurrentState failed(ExecutionResult> failure) {
return new Failed<>(displayName, state, this.failure.withFailures(failure.asFailure()));
}
@Override
public CurrentState nextState(T toState) {
return new Failed<>(displayName, toState, failure);
}
}
public interface State {
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy