/* * The MIT License * * Copyright 2015 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.acteurbase; import com.google.inject.ProvisionException; import com.mastfrog.acteurbase.ActeurState; import com.mastfrog.acteurbase.Deferral.Resumer; import com.mastfrog.guicy.scope.ReentrantScope; import com.mastfrog.util.Checks; import com.mastfrog.util.thread.QuietAutoCloseable; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; /** * Runs a chain of AbstractActeurs, invoking the callback when the chain has * been exhausted. * <p> * This class involves a sadly complex generic signature, which is necessary in * order to preserve the types in question. If you are creating an acteur-based * framework, it is best to write specific, parameterized subclasses of this, * AbstractActeur, State and StateCallback, so that it is clear what someone is * supposed to pass. * * @author Tim Boudreau */ public final class ChainRunner { private final ExecutorService svc; private final ReentrantScope scope; @Inject public ChainRunner(ExecutorService svc, ReentrantScope scope) { Checks.notNull("svc", svc); Checks.notNull("scope", scope); this.svc = svc; this.scope = scope; } /** * Run one {@link Chain} of {@link AbstractActeur}s, constructing each and * retrieving its state, and calling the passed callback with the results. * * @param <A> The AbstractActeur subtype * @param <S> The State subtype * @param <P> The Chain subtype * @param <T> The public type the AbstractActeur subtype is parameterized on * @param <R> The implementation type the AbstractActeur subtype is * parameterized on * @param chain The chain * @param onDone The callback * @param cancelled Set this to true if execution should be silently * cancelled */ public <A extends AbstractActeur<T, R, S>, S extends ActeurState<T, R>, P extends Chain<? extends A>, T, R extends T> void submit(P chain, ChainCallback<A, S, P, T, R> onDone, AtomicBoolean cancelled) { ActeurInvoker<A, S, P, T, R> cc = new ActeurInvoker<>(svc, scope, chain, onDone, cancelled); // Enter the scope, with the Chain (so it can be dynamically added to) // and the deferral, which can be used to pause the chain try (QuietAutoCloseable ac = scope.enter(chain, cc.deferral)) { // Wrap the callable so whenn it is invoked, we will be in the // scope with the same contents as before svc.submit(scope.wrap(cc)); } } private static class ActeurInvoker<A extends AbstractActeur<T, R, S>, S extends ActeurState<T, R>, P extends Chain<? extends A>, T, R extends T> implements Callable<Void>, Resumer { private final ExecutorService svc; private final ReentrantScope scope; private final Iterator<? extends A> iter; private Object[] state = new Object[0]; private final List<R> responses = new LinkedList<>(); private final ChainCallback<A, S, P, T, R> onDone; private final AtomicBoolean deferred = new AtomicBoolean(); private Callable<?> next; final Deferral deferral = new DeferralImpl(); private final P chain; private final AtomicBoolean cancelled; public ActeurInvoker(ExecutorService svc, ReentrantScope scope, P chain, ChainCallback<A, S, P, T, R> onDone, AtomicBoolean cancelled) { this.svc = svc; this.scope = scope; this.iter = chain.iterator(); this.chain = chain; this.onDone = onDone; this.cancelled = cancelled; } class DeferralImpl implements Deferral { @Override public Resumer defer() { if (deferred.compareAndSet(false, true)) { return ActeurInvoker.this; } else { throw new IllegalStateException("Already deferred"); } } } private void addToContext(ActeurState state) { synchronized (this) { addToContext(state.context()); } } private synchronized void addToContext(Object[] ctx) { if (ctx != null && ctx.length > 0) { Object[] nue = new Object[this.state.length + ctx.length]; System.arraycopy(this.state, 0, nue, 0, this.state.length); System.arraycopy(ctx, 0, nue, this.state.length, ctx.length); this.state = nue; } } @Override public Void call() throws Exception { if (cancelled.get()) { return null; } try (AutoCloseable ctx = scope.enter(chain.getContextContribution())) { AutoCloseable ac = null; // Optimization - only reenter the scope if we have some state // from previous acteurs to incorporate into it synchronized (this) { if (this.state.length > 0) { ac = scope.enter(this.state); } } S newState; try { A a2 = null; try { onDone.onBeforeRunOne(chain); // Instantiate the next acteur, most likely causing its // constructor to set its state a2 = iter.next(); // Get the state, which may compute the state if it is lazy newState = a2.getState(); } finally { onDone.onAfterRunOne(chain, a2); } if (newState.isRejected()) { onDone.onRejected(newState); return null; } // Add any objects it provided into the scope for the next // invocation addToContext(newState); } catch (Exception | Error e) { Throwable t = e; if (e instanceof ProvisionException && e.getCause() != null) { t = e.getCause(); } onDone.onFailure(t); return null; } finally { if (ac != null) { ac.close(); } } if (cancelled.get()) { return null; } // Get the response, which may be null if it was untouched by the // acteurs execution R resp = newState.response(); if (resp != null) { // Add it into the set of response objects the OnDone will // coalesce synchronized (this) { responses.add(resp); } } // See if we're done if (!newState.isFinished()) { // If no more Acteurs, tell the callback we give up if (!iter.hasNext()) { onDone.onNoResponse(); } else if (deferred.get()) { // Store the next iteration with the current scope // contents, so that when resumer.resume() is called // we can go back to work // try (QuietAutoCloseable qac = scope.enter(state)) { next = scope.wrap(this); // } } else // Re-wrap "this" in the current scope and tee it up // to be run if (!cancelled.get()) { svc.submit(scope.wrap(this)); } } else { onDone.onDone(newState, responses); } return null; } catch (Exception | Error e) { e.printStackTrace(); onDone.onFailure(e); return null; } } @Override public void resume(Object... addToContext) { if (cancelled.get()) { return; } if (deferred.compareAndSet(true, false)) { addToContext(addToContext); Callable<?> next = this.next; svc.submit(next); } else { throw new IllegalStateException("Not deferred"); } } } }