/* * Copyright 2013-2014 Spotify AB. All rights reserved. * * The contents of this file are 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 com.spotify.trickle; import com.google.common.base.Function; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.builder; import static com.google.common.util.concurrent.Futures.allAsList; import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; /** * A decorator class for Graph that holds bound values for input names thus making * graph runnable. * * This class is immutable and thread safe. Calls to any of the bind methods will * return a new instance containing that binding. * * @param <R> The return type of the graph */ final class PreparedGraph<R> extends Graph<R> { private final GraphBuilder<R> graph; private final ImmutableMap<Input<?>, Object> inputBindings; private final boolean debug; private PreparedGraph(GraphBuilder<R> graph, ImmutableMap<Input<?>, Object> inputBindings, boolean debug) { this.graph = checkNotNull(graph, "graph"); this.inputBindings = checkNotNull(inputBindings, "inputBindings"); this.debug = debug; } PreparedGraph(GraphBuilder<R> graph, boolean debug) { this(graph, ImmutableMap.<Input<?>, Object>of(), debug); } @Override public <P> Graph<R> bind(Input<P> input, P value) { return addToInputs(input, value); } @Override public <P> Graph<R> bind(Input<P> input, ListenableFuture<P> inputFuture) { return addToInputs(input, inputFuture); } @Override public Graph<R> debug(boolean debug) { return new PreparedGraph<R>(graph, inputBindings, debug); } @Override public ListenableFuture<R> run() { return run(sameThreadExecutor()); } @Override public ListenableFuture<R> run(Executor executor) { return run(TraverseState.empty(executor, debug)); } @Override ListenableFuture<R> run(TraverseState state) { state.addBindings(inputBindings); return future(state); } private ListenableFuture<R> future(final TraverseState state) { final ImmutableList.Builder<ListenableFuture<?>> futuresListBuilder = builder(); // get node and value dependencies for (Dep<?> input : graph.getInputs()) { final ListenableFuture<?> inputFuture = input.getFuture(state); futuresListBuilder.add(inputFuture); } final ImmutableList<ListenableFuture<?>> futures = futuresListBuilder.build(); final TraverseState.FutureCallInformation currentCall = state.record(this, futures); // future for signaling propagation - needs to include predecessors, too List<ListenableFuture<?>> mustHappenBefore = Lists.newArrayList(futures); for (Graph<?> predecessor : graph.getPredecessors()) { mustHappenBefore.add(state.futureForGraph(predecessor)); } final ListenableFuture<List<Object>> allFuture = allAsList(mustHappenBefore); checkArgument(graph.getInputs().size() == futures.size(), "sanity check result: insane"); return Futures.withFallback( nodeFuture(futures, allFuture, state.getExecutor()), new NodeExecutionFallback<R>(graph, currentCall, state)); } private ListenableFuture<R> nodeFuture(final ImmutableList<ListenableFuture<?>> values, final ListenableFuture<List<Object>> doneSignal, final Executor executor) { return Futures.transform( doneSignal, new AsyncFunction<List<Object>, R>() { @Override public ListenableFuture<R> apply(List<Object> input) { // the input future is not going to be null unless there's a Trickle bug, so we should // be fine with an NPE in that case //noinspection NullableProblems return graph.getNode().run( Lists.transform(values, new Function<ListenableFuture<?>, Object>() { @Override public Object apply(ListenableFuture<?> input) { return inputValueFromFuture(input); } })); } }, executor); } private Object inputValueFromFuture(ListenableFuture<?> input) { try { return Uninterruptibles.getUninterruptibly(input); } catch (ExecutionException e) { Throwables.propagateIfInstanceOf(e.getCause(), GraphExecutionException.class); throw Throwables.propagate(e); } } private PreparedGraph<R> addToInputs(Input<?> input, Object value) { checkState(!inputBindings.containsKey(input), "Duplicate binding for input: " + input); return new PreparedGraph<R>( graph, ImmutableMap.<Input<?>, Object>builder() .putAll(inputBindings) .put(input, value) .build(), debug); } @Override public String name() { return graph.name(); } @Override public List<? extends NodeInfo> arguments() { return graph.arguments(); } @Override public Iterable<? extends NodeInfo> predecessors() { return graph.predecessors(); } @Override public Type type() { return graph.type(); } @Override public String toString() { return graph.name(); } }