package jalse.actions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import jalse.actions.MultiAction.ActionOperation;
/**
* An multi-{@link Action} for creating Actions that chain, schedule or await other actions. This is
* useful for when some operations cannot be done out of sequence but the {@link ActionEngine} is a
* concurrent one.<br>
* <br>
* Actions can be easily chained with {@link #buildChain(Action...)}.
*
* @author Elliot Ford
*
* @param <T>
* Type of actor to be supplied (can be {@code ?} for no actor).
*
* @see ForkJoinActionEngine
* @see ThreadPoolActionEngine
* @see ManualActionEngine
*/
public final class MultiAction<T> extends CopyOnWriteArrayList<ActionOperation<T>>implements Action<T> {
/**
* An {@link Action} operation to be executed by a {@link MultiAction}.
*
* @author Elliot Ford
*
* @param <T>
* Actor type.
*/
public static final class ActionOperation<T> {
private final Collection<? extends Action<T>> actions;
private final OperationType type;
/**
* Creates a new operation.
*
* @param type
* Operation type.
* @param action
* Action to execute.
*/
public ActionOperation(final OperationType type, final Action<T> action) {
this(type, Collections.singleton(Objects.requireNonNull(action)));
}
/**
* Creates a new operation.
*
* @param type
* Operation type.
* @param actions
* Actions to execute.
*/
public ActionOperation(final OperationType type, final Collection<? extends Action<T>> actions) {
this.type = Objects.requireNonNull(type);
if (actions.isEmpty()) {
throw new IllegalArgumentException();
}
this.actions = actions;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ActionOperation<?>)) {
return false;
}
final ActionOperation<?> other = (ActionOperation<?>) obj;
return type == other.type && actions.equals(other.actions);
}
/**
* Gets the actions to execute.
*
* @return Actions to execute.
*/
public Collection<? extends Action<T>> getActions() {
return Collections.unmodifiableCollection(actions);
}
/**
* Gets the operation type.
*
* @return Operation type.
*/
public OperationType getType() {
return type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + actions.hashCode();
result = prime * result + type.hashCode();
return result;
}
}
/**
* A {@link MultiAction} instance builder.
*
* @author Elliot Ford
*
* @param <T>
* Actor type.
*/
public static final class Builder<T> {
private final List<ActionOperation<T>> builderOperations;
/**
* Creates a new builder instance.
*/
public Builder() {
builderOperations = new ArrayList<>();
}
/**
* Adds an action to be performed next.
*
* @param action
* Action to perform next.
* @return This builder.
*/
public Builder<T> addPerform(final Action<T> action) {
builderOperations.add(new ActionOperation<>(OperationType.PERFORM, action));
return this;
}
/**
* Adds a chain of actions to be performed in sequence.
*
* @param actions
* Actions to perform.
* @return This builder.
*/
public Builder<T> addPerform(final List<? extends Action<T>> actions) {
builderOperations.add(new ActionOperation<>(OperationType.PERFORM, actions));
return this;
}
/**
* Adds an action to be scheduled.
*
* @param action
* Action to schedule.
* @return This builder.
*/
public Builder<T> addSchedule(final Action<T> action) {
builderOperations.add(new ActionOperation<>(OperationType.SCHEDULE, action));
return this;
}
/**
* Adds a number of actions to be scheduled.
*
* @param actions
* Actions to schedule.
* @return This builder.
*/
public Builder<T> addSchedule(final Collection<? extends Action<T>> actions) {
builderOperations.add(new ActionOperation<>(OperationType.SCHEDULE, actions));
return this;
}
/**
* Adds an action to be scheduled then awaited.
*
* @param action
* Action to schedule and await.
* @return This builder.
*/
public Builder<T> addScheduleAndAwait(final Action<T> action) {
builderOperations.add(new ActionOperation<>(OperationType.SCHEDULE_AWAIT, action));
return this;
}
/**
* Adds a number of actions to be scheduled then awaited.
*
* @param actions
* Actions to schedule and await.
* @return This builder.
*/
public Builder<T> addScheduleAndAwait(final Collection<? extends Action<T>> actions) {
builderOperations.add(new ActionOperation<>(OperationType.SCHEDULE_AWAIT, actions));
return this;
}
/**
* Builds the multi-action.
*
* @return The multi-action.
*/
public MultiAction<T> build() {
final MultiAction<T> ma = new MultiAction<>();
ma.addAll(builderOperations);
return ma;
}
}
/**
* The {@link Action} operations this multi-action supports.
*
* @author Elliot Ford
*
*/
public enum OperationType {
/**
* The {@link Action}(s) will be performed.
*/
PERFORM,
/**
* The {@link Action}(s) will be scheduled.
*/
SCHEDULE,
/**
* The {@link Action}(s) will be scheduled and then awaited.
*/
SCHEDULE_AWAIT
}
private static final long serialVersionUID = 4768157900074722640L;
/**
* Builds an action that processes a chain of actions.
*
* @param actions
* Actions to perform.in sequence.
* @return Chain action.
*/
@SafeVarargs
public static <S> MultiAction<S> buildChain(final Action<S>... actions) {
return buildChain(Arrays.asList(actions));
}
/**
* Builds an action that processes a chain of actions.
*
* @param actions
* Actions to perform.in sequence.
* @return Chain action.
*/
public static <S> MultiAction<S> buildChain(final List<? extends Action<S>> actions) {
return new Builder<S>().addPerform(actions).build();
}
@Override
public void perform(final ActionContext<T> context) throws InterruptedException {
for (final ActionOperation<T> aao : this) {
switch (aao.getType()) {
case PERFORM:
performAll(context, aao.getActions());
break;
case SCHEDULE:
scheduleAll(context, aao.getActions());
break;
case SCHEDULE_AWAIT:
scheduleAwaitAll(context, aao.getActions());
break;
}
}
}
private void performAll(final ActionContext<T> context, final Collection<? extends Action<T>> actions)
throws InterruptedException {
for (final Action<T> action : actions) {
action.perform(context); // Execute action
}
}
private Collection<SchedulableActionContext<T>> scheduleAll(final ActionContext<T> context,
final Collection<? extends Action<T>> actions) {
final Collection<SchedulableActionContext<T>> newContexts = new ArrayList<>();
final ActionEngine engine = context.getEngine();
final T actor = context.getActor();
for (final Action<T> action : actions) {
final SchedulableActionContext<T> newContext = engine.newContext(action);
newContext.setActor(actor); // Same actor
newContext.putAll(context.toMap()); // Copy bindings (current)
newContext.schedule();
newContexts.add(newContext);
}
return newContexts;
}
private void scheduleAwaitAll(final ActionContext<T> context, final Collection<? extends Action<T>> actions)
throws InterruptedException {
for (final SchedulableActionContext<T> newContext : scheduleAll(context, actions)) {
if (!newContext.isDone()) { // Stops us for waiting forever
newContext.await();
}
}
}
}