/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.plugin.pullrequest.client.workflow;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Objects.requireNonNull;
/**
* Defines a chain of steps.
*
* @author Yevhenii Voevodin
* @see ChainExecutor
* @see ContributionWorkflow
*/
public final class StepsChain {
/**
* Creates a new {@code StepsChain} with the initial step.
*
* @param firstStep
* the first step in the chain
* @return new chain instance
* @throws NullPointerException
* when {@code firstStep} is null
*/
public static StepsChain first(Step firstStep) {
return new StepsChain().then(firstStep);
}
private final List<Step> steps;
private StepsChain() {
steps = new ArrayList<>();
}
/**
* Returns the list of the steps currently added to this chain.
* The list is unmodifiable copy of added steps, so next chain modification
* won't affect returned list.
*/
public List<Step> getSteps() {
return ImmutableList.copyOf(steps);
}
/**
* Adds the step to the chain.
*
* @param step
* the next chain step
* @throws NullPointerException
* when {@code step} is null
*/
public StepsChain then(Step step) {
steps.add(step);
return this;
}
/**
* Adds {@code stepIfTrue} to the chain, and executes added step only
* if supplier supplies true.
*
* @param supplier
* supplier of a boolean value
* @param stepIfTrue
* step which should be added to the chain if expression is true
* @return this chain instance
* @throws NullPointerException
* when {@code stepIfTrue} is null
*/
public StepsChain thenIf(Supplier<Boolean> supplier, Step stepIfTrue) {
return then(new ExpressionStep(supplier, stepIfTrue));
}
/**
* Adds all the steps from the {@code chain} to this chain.
*
* @param chain
* chain which steps should be added to this chain
* @return this chain instance
* @throws NullPointerException
* when {@code chain} is null
*/
public StepsChain thenChain(StepsChain chain) {
steps.addAll(requireNonNull(chain, "Expected non-null chain").getSteps());
return this;
}
/**
* Adds all the steps from the {@code chainIfTrue} chain to
* this chain with a boolean supplier. Each added step will be executed only if
* supplier provides true value.
*
* @param supplier
* any boolean value or expression
* @param chainIfTrue
* chain which steps should be added to this chain if expression is true
* @return this chain instance
* @throws NullPointerException
* when {@code chainIfTrue} is null
*/
public StepsChain thenChainIf(Supplier<Boolean> supplier, StepsChain chainIfTrue) {
final CachingSupplier cachingSupplier = new CachingSupplier(supplier);
for (Step stepIfTrue : chainIfTrue.getSteps()) {
thenIf(cachingSupplier, stepIfTrue);
}
return this;
}
/**
* Adds all the steps from the {@code chainIfTrue} and {@code chainIfFalse} chains to this chain
* with a opposite suppliers. If supplier supplies true then all the steps from the {@code chainIfTrue} chain
* will be executed, otherwise all the steps from the chainIfFalse step will be executed.
*
* @param supplier
* any boolean value or expression
* @param chainIfTrue
* chain which steps should be added to this chain if expression is true
* @param chainIfFalse
* chain which steps should be added to this chain if expression is false
* @return this chain instance
* @throws NullPointerException
* when either {@code chainIfTrue} or {@code chainIfFalse} is null
*/
public StepsChain thenChainIf(Supplier<Boolean> supplier, StepsChain chainIfTrue, StepsChain chainIfFalse) {
thenChainIf(supplier, chainIfTrue);
thenChainIf(new NegateSupplier(supplier), chainIfFalse);
return this;
}
public static StepsChain firstIf(Supplier<Boolean> condition, Step stepIfTrue) {
return new StepsChain().thenIf(condition, stepIfTrue);
}
/**
* Executes given step only if {@code supplier} provides true value,
* otherwise continues workflow execution with {@link WorkflowExecutor#executeNextStep(Context)} method.
* This is helpful class for defining steps which execution is based on the future condition.
*/
private static class ExpressionStep implements SyntheticStep {
final Supplier<Boolean> supplier;
final Step stepIfTrue;
ExpressionStep(Supplier<Boolean> supplier, Step stepIfTrue) {
this.supplier = supplier;
this.stepIfTrue = stepIfTrue;
}
@Override
public void execute(WorkflowExecutor executor, Context context) {
if (supplier.get()) {
stepIfTrue.execute(executor, context);
} else {
executor.done(this, context);
}
}
}
/**
* Supplier which caches {@code delegate.get()} value and reuses it.
* It allows chains to depend on the other chain and future check.
*
* For example:
* <pre>{@code
* StepsChain.first(...)
* .thenChainIf(IS_AUTH_NEEDED, authChain);
* }</pre>
* If {@code authChain} contains several steps those steps execution should
* depend only on the {@code IS_AUTH_NEEDED} supplier result, and this result
* should be the same for all the next chain steps.
*/
private static class CachingSupplier implements Supplier<Boolean> {
final Supplier<Boolean> delegate;
Boolean cachedResult;
CachingSupplier(Supplier<Boolean> delegate) {
this.delegate = delegate;
}
@Override
public Boolean get() {
if (cachedResult == null) {
cachedResult = delegate.get();
}
return cachedResult;
}
}
/** Delegates the {@code delegate.get()} and negates its result. */
private static class NegateSupplier implements Supplier<Boolean> {
final Supplier<Boolean> delegate;
NegateSupplier(Supplier<Boolean> delegate) {
this.delegate = delegate;
}
@Override
public Boolean get() {
return !delegate.get();
}
}
}