diff options
author | 2015-11-30 20:39:40 +0000 | |
---|---|---|
committer | 2015-12-01 18:21:02 +0000 | |
commit | 016b4b6d45ff81028b1650c00b87ee5bde9c7987 (patch) | |
tree | 3fc7b85e153bd27c2fa9b8476a7e08cc39459353 | |
parent | 7be81866611f850a690277e0bbd318c312bc3268 (diff) |
Rollback of commit 8e7da8704dba78dba47fbaa0eafce3a91c2deb13.
*** Reason for rollback ***
--
MOS_MIGRATED_REVID=109005097
14 files changed, 226 insertions, 460 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java b/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java index 6408276f99..c4a5dca0ef 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/commands/FetchCommand.java @@ -22,7 +22,6 @@ import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.pkgcache.PackageCacheOptions; import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment; -import com.google.devtools.build.lib.query2.engine.Callback; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting; import com.google.devtools.build.lib.query2.engine.QueryException; import com.google.devtools.build.lib.query2.engine.QueryExpression; @@ -122,13 +121,7 @@ public final class FetchCommand implements BlazeCommand { // 2. Evaluate expression: try { - queryEnv.evaluateQuery(expr, new Callback<Target>() { - @Override - public void process(Iterable<Target> partialResult) - throws QueryException, InterruptedException { - // Throw away the result. - } - }); + queryEnv.evaluateQuery(expr); } catch (QueryException | InterruptedException e) { // Keep consistent with reportBuildFileError() env.getReporter().handle(Event.error(e.getMessage())); diff --git a/src/main/java/com/google/devtools/build/lib/query2/AbstractBlazeQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/AbstractBlazeQueryEnvironment.java index b3e4341715..11eef89f1d 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/AbstractBlazeQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/AbstractBlazeQueryEnvironment.java @@ -17,7 +17,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.TargetParsingException; @@ -32,11 +31,11 @@ import com.google.devtools.build.lib.pkgcache.PathPackageLocator; import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator; import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader; import com.google.devtools.build.lib.profiler.AutoProfiler; -import com.google.devtools.build.lib.query2.engine.Callback; import com.google.devtools.build.lib.query2.engine.QueryEnvironment; import com.google.devtools.build.lib.query2.engine.QueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryException; import com.google.devtools.build.lib.query2.engine.QueryExpression; +import com.google.devtools.build.lib.query2.engine.QueryUtil; import com.google.devtools.build.lib.util.BinaryPredicate; import com.google.devtools.build.skyframe.WalkableGraph.WalkableGraphFactory; @@ -46,7 +45,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -138,11 +136,10 @@ public abstract class AbstractBlazeQueryEnvironment<T> implements QueryEnvironme * @throws QueryException if the evaluation failed and {@code --nokeep_going} was in * effect */ - public QueryEvalResult evaluateQuery(QueryExpression expr, final Callback<T> callback) + public QueryEvalResult<T> evaluateQuery(QueryExpression expr) throws QueryException, InterruptedException { - - final AtomicBoolean empty = new AtomicBoolean(true); - try (final AutoProfiler p = AutoProfiler.logged("evaluating query", LOG)) { + Set<T> resultNodes; + try (AutoProfiler p = AutoProfiler.logged("evaluating query", LOG)) { resolvedTargetPatterns.clear(); // In the --nokeep_going case, errors are reported in the order in which the patterns are @@ -155,15 +152,9 @@ public abstract class AbstractBlazeQueryEnvironment<T> implements QueryEnvironme // Unfortunately, by evaluating the patterns in parallel, we lose some location information. throw new QueryException(expr, e.getMessage()); } + try { - this.eval(expr, new Callback<T>() { - @Override - public void process(Iterable<T> partialResult) - throws QueryException, InterruptedException { - empty.compareAndSet(true, Iterables.isEmpty(partialResult)); - callback.process(partialResult); - } - }); + resultNodes = QueryUtil.evalAll(this, expr); } catch (QueryException e) { throw new QueryException(e, expr); } @@ -181,12 +172,12 @@ public abstract class AbstractBlazeQueryEnvironment<T> implements QueryEnvironme } } - return new QueryEvalResult(!eventHandler.hasErrors(), empty.get()); + return new QueryEvalResult<>(!eventHandler.hasErrors(), resultNodes); } - public QueryEvalResult evaluateQuery(String query, Callback<T> callback) + public QueryEvalResult<T> evaluateQuery(String query) throws QueryException, InterruptedException { - return evaluateQuery(QueryExpression.parse(query, this), callback); + return evaluateQuery(QueryExpression.parse(query, this)); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/query2/BUILD b/src/main/java/com/google/devtools/build/lib/query2/BUILD index 306ef7ad65..a5edb8e972 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/BUILD +++ b/src/main/java/com/google/devtools/build/lib/query2/BUILD @@ -44,7 +44,6 @@ java_library( srcs = glob(["engine/*.java"]), deps = [ "//src/main/java/com/google/devtools/build/lib:common", - "//src/main/java/com/google/devtools/build/lib:concurrent", "//src/main/java/com/google/devtools/build/lib:graph", "//src/main/java/com/google/devtools/build/lib:util", "//third_party:jsr305", diff --git a/src/main/java/com/google/devtools/build/lib/query2/BlazeQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/BlazeQueryEnvironment.java index db006b540c..55b1bc7be5 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/BlazeQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/BlazeQueryEnvironment.java @@ -37,8 +37,8 @@ import com.google.devtools.build.lib.pkgcache.TargetEdgeObserver; import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator; import com.google.devtools.build.lib.pkgcache.TargetProvider; import com.google.devtools.build.lib.pkgcache.TransitivePackageLoader; +import com.google.devtools.build.lib.query2.engine.BlazeQueryEvalResult; import com.google.devtools.build.lib.query2.engine.Callback; -import com.google.devtools.build.lib.query2.engine.DigraphQueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryException; import com.google.devtools.build.lib.query2.engine.QueryExpression; @@ -54,7 +54,6 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; /** * The environment of a Blaze query. Not thread-safe. @@ -103,22 +102,15 @@ public class BlazeQueryEnvironment extends AbstractBlazeQueryEnvironment<Target> } @Override - public DigraphQueryEvalResult<Target> evaluateQuery(QueryExpression expr, - final Callback<Target> callback) throws QueryException, InterruptedException { + public BlazeQueryEvalResult<Target> evaluateQuery(QueryExpression expr) + throws QueryException, InterruptedException { // Some errors are reported as QueryExceptions and others as ERROR events (if --keep_going). The // result is set to have an error iff there were errors emitted during the query, so we reset // errors here. eventHandler.resetErrors(); - final AtomicBoolean empty = new AtomicBoolean(true); - QueryEvalResult queryEvalResult = super.evaluateQuery(expr, new Callback<Target>() { - @Override - public void process(Iterable<Target> partialResult) - throws QueryException, InterruptedException { - empty.compareAndSet(true, Iterables.isEmpty(partialResult)); - callback.process(partialResult); - } - }); - return new DigraphQueryEvalResult<>(queryEvalResult.getSuccess(), empty.get(), graph); + QueryEvalResult<Target> queryEvalResult = super.evaluateQuery(expr); + return new BlazeQueryEvalResult<>(queryEvalResult.getSuccess(), queryEvalResult.getResultSet(), + graph); } @Override diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java index 5cb7a326b3..85d96ecf95 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java +++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java @@ -167,14 +167,14 @@ public class SkyQueryEnvironment extends AbstractBlazeQueryEnvironment<Target> { } @Override - public QueryEvalResult evaluateQuery(QueryExpression expr, Callback<Target> callback) + public QueryEvalResult<Target> evaluateQuery(QueryExpression expr) throws QueryException, InterruptedException { // Some errors are reported as QueryExceptions and others as ERROR events (if --keep_going). The // result is set to have an error iff there were errors emitted during the query, so we reset // errors here. eventHandler.resetErrors(); init(); - return super.evaluateQuery(expr, callback); + return super.evaluateQuery(expr); } private Map<Target, Collection<Target>> makeTargetsMap(Map<SkyKey, Iterable<SkyKey>> input) { @@ -376,7 +376,7 @@ public class SkyQueryEnvironment extends AbstractBlazeQueryEnvironment<Target> { } } - private static Target getSubincludeTarget(Label label, Package pkg) { + private static Target getSubincludeTarget(final Label label, Package pkg) { return new FakeSubincludeTarget(label, pkg); } diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/DigraphQueryEvalResult.java b/src/main/java/com/google/devtools/build/lib/query2/engine/BlazeQueryEvalResult.java index 24612b25e3..4ad13573f9 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/engine/DigraphQueryEvalResult.java +++ b/src/main/java/com/google/devtools/build/lib/query2/engine/BlazeQueryEvalResult.java @@ -17,18 +17,20 @@ package com.google.devtools.build.lib.query2.engine; import com.google.common.base.Preconditions; import com.google.devtools.build.lib.graph.Digraph; +import java.util.Set; + /** {@link QueryEvalResult} along with a digraph giving the structure of the results. */ -public class DigraphQueryEvalResult<T> extends QueryEvalResult { +public class BlazeQueryEvalResult<T> extends QueryEvalResult<T> { private final Digraph<T> graph; - public DigraphQueryEvalResult(boolean success, boolean isEmpty, Digraph<T> graph) { - super(success, isEmpty); + public BlazeQueryEvalResult(boolean success, Set<T> resultSet, Digraph<T> graph) { + super(success, resultSet); this.graph = Preconditions.checkNotNull(graph); } - /** Returns the recorded graph */ - public Digraph<T> getGraph() { - return graph; + /** Returns the result as a directed graph over elements. */ + public Digraph<T> getResultGraph() { + return graph.extractSubgraph(resultSet); } } diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java b/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java deleted file mode 100644 index 546b0d51bb..0000000000 --- a/src/main/java/com/google/devtools/build/lib/query2/engine/OutputFormatterCallback.java +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2015 The Bazel Authors. All rights reserved. -// -// 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.google.devtools.build.lib.query2.engine; - -import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; - -import java.io.Closeable; -import java.io.IOException; - -import javax.annotation.Nullable; - -/** A callback that can receive a finish event when there are no more partial results */ -@ThreadCompatible -public abstract class OutputFormatterCallback<T> implements Callback<T>, Closeable { - - private IOException ioException; - - /** - * This method will be called before any partial result are available. - * - * <p>It should be used for opening resources or sending a header to the output. - */ - public void start() throws IOException { } - - /** - * Same as start but for closing resources or writting a footer. - * - * <p>Will be called even in the case of an error. - */ - @Override - public void close() throws IOException{} - - /** - * Note that {@link Callback} interface does not throw IOExceptions. What this implementation does - * instead is throw {@code InterruptedException} and store the {@code IOException} in the {@code - * ioException} field. Users of this class should check on InterruptedException the field to - * disambiguate between real interruptions or IO Exceptions. - */ - @Override - public final void process(Iterable<T> partialResult) throws QueryException, InterruptedException { - try { - processOutput(partialResult); - } catch (IOException e) { - ioException = e; - throw new InterruptedException("Interrupting due to a IOException in the OutputFormatter"); - } - } - - protected abstract void processOutput(Iterable<T> partialResult) - throws IOException, InterruptedException; - - @Nullable - public IOException getIoException() { - return ioException; - } - - /** - * Use an {@code OutputFormatterCallback} with an already computed set of targets. Note that this - * does not work in stream mode, as the {@code targets} would already be computed. - * - * <p>The intended usage of this method is to use {@code StreamedFormatter} formaters in non - * streaming contexts. - */ - public static <T> void processAllTargets(OutputFormatterCallback<T> callback, - Iterable<T> targets) throws IOException, InterruptedException { - try { - callback.start(); - callback.process(targets); - } catch (InterruptedException e) { - IOException ioException = callback.getIoException(); - if (ioException != null) { - throw ioException; - } - throw e; - } catch (QueryException e) { - throw new IllegalStateException("This should not happen, as we are not running any query," - + " only printing the results:" + e.getMessage(), e); - } finally { - callback.close(); - } - } -} diff --git a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryEvalResult.java b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryEvalResult.java index 886cb439fe..23e77e6758 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/engine/QueryEvalResult.java +++ b/src/main/java/com/google/devtools/build/lib/query2/engine/QueryEvalResult.java @@ -14,18 +14,24 @@ package com.google.devtools.build.lib.query2.engine; +import com.google.common.base.Preconditions; + +import java.util.Set; + /** - * Information about the query evaluation, like if it was successful and number of elements - * returned. + * The result of a query evaluation, containing a set of elements. + * + * @param <T> the node type of the elements. */ -public class QueryEvalResult { +public class QueryEvalResult<T> { - private final boolean success; - private final boolean empty; + protected final boolean success; + protected final Set<T> resultSet; - public QueryEvalResult(boolean success, boolean empty) { + public QueryEvalResult( + boolean success, Set<T> resultSet) { this.success = success; - this.empty = empty; + this.resultSet = Preconditions.checkNotNull(resultSet); } /** @@ -36,13 +42,16 @@ public class QueryEvalResult { return success; } - /** True if the query did not return any result; */ - public boolean isEmpty() { - return empty; + /** + * Returns the result as a set of targets. + */ + public Set<T> getResultSet() { + return resultSet; } @Override public String toString() { - return (getSuccess() ? "Successful" : "Unsuccessful") + ", empty = " + empty; + return (getSuccess() ? "Successful" : "Unsuccessful") + ", result size = " + + getResultSet().size() + ", " + getResultSet(); } } diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java index a6351d4df9..93d28bbd50 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java @@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.devtools.build.lib.cmdline.Label; -import com.google.devtools.build.lib.collect.CompactHashSet; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.graph.Digraph; import com.google.devtools.build.lib.graph.Node; @@ -28,7 +27,6 @@ import com.google.devtools.build.lib.packages.Attribute; import com.google.devtools.build.lib.packages.AttributeSerializer; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; -import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Printer; @@ -155,20 +153,17 @@ public abstract class OutputFormatter implements Serializable { AspectResolver aspectProvider) throws IOException, InterruptedException; /** - * Unordered streamed output formatter (wrt. dependency ordering). + * Unordered output formatter (wrt. dependency ordering). * - * <p>Formatters that support streamed output may be used when only the set of query results is + * <p>Formatters that support unordered output may be used when only the set of query results is * requested but their ordering is irrelevant. * - * <p>The benefit of using a streamed formatter is that we can save the potentially expensive - * subgraph extraction step before presenting the query results and that depending on the query - * environment used, it can be more memory performant, as it does not aggregate all the data - * before writting in the output. + * <p>The benefit of using a unordered formatter is that we can save the potentially expensive + * subgraph extraction step before presenting the query results. */ - public interface StreamedFormatter { - - OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, PrintStream out, - AspectResolver aspectResolver); + public interface UnorderedFormatter { + void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, + AspectResolver aspectResolver) throws IOException, InterruptedException; } /** @@ -177,8 +172,8 @@ public abstract class OutputFormatter implements Serializable { public abstract String getName(); abstract static class AbstractUnorderedFormatter extends OutputFormatter - implements StreamedFormatter { - protected Iterable<Target> getOrderedTargets( + implements UnorderedFormatter { + private static Iterable<Target> getOrderedTargets( Digraph<Target> result, QueryOptions options) { Iterable<Node<Target>> orderedResult = options.orderOutput == OrderOutput.DEPS @@ -188,11 +183,13 @@ public abstract class OutputFormatter implements Serializable { } @Override - public void output(QueryOptions options, Digraph<Target> result, PrintStream out, - AspectResolver aspectResolver) throws IOException, InterruptedException { - OutputFormatterCallback.processAllTargets( - createStreamCallback(options, out, aspectResolver), - getOrderedTargets(result, options)); + public void output( + QueryOptions options, + Digraph<Target> result, + PrintStream out, + AspectResolver aspectResolver) + throws IOException, InterruptedException { + outputUnordered(options, getOrderedTargets(result, options), out, aspectResolver); } } @@ -204,7 +201,7 @@ public abstract class OutputFormatter implements Serializable { private final boolean showKind; - private LabelOutputFormatter(boolean showKind) { + public LabelOutputFormatter(boolean showKind) { this.showKind = showKind; } @@ -214,22 +211,15 @@ public abstract class OutputFormatter implements Serializable { } @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, AspectResolver aspectResolver) { - return new OutputFormatterCallback<Target>() { - - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { - for (Target target : partialResult) { - if (showKind) { - out.print(target.getTargetKind()); - out.print(' '); - } - out.println(target.getLabel()); - } + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, + AspectResolver aspectResolver) { + for (Target target : result) { + if (showKind) { + out.print(target.getTargetKind()); + out.print(' '); } - }; + out.println(target.getLabel()); + } } } @@ -255,28 +245,15 @@ public abstract class OutputFormatter implements Serializable { } @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, AspectResolver aspectResolver) { - return new OutputFormatterCallback<Target>() { - private final Set<String> packageNames = Sets.newTreeSet(); - - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { - - for (Target target : partialResult) { - packageNames.add(target.getLabel().getPackageName()); - } - } - - @Override - public void close() throws IOException { - for (String packageName : packageNames) { - out.println(packageName); - } - } - }; + Set<String> packageNames = Sets.newTreeSet(); + for (Target target : result) { + packageNames.add(target.getLabel().getPackageName()); + } + for (String packageName : packageNames) { + out.println(packageName); + } } } @@ -293,20 +270,12 @@ public abstract class OutputFormatter implements Serializable { } @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, AspectResolver aspectResolver) { - return new OutputFormatterCallback<Target>() { - - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { - for (Target target : partialResult) { - Location location = target.getLocation(); - out.println(location.print() + ": " + target.getTargetKind() + " " + target.getLabel()); - } - } - }; + for (Target target : result) { + Location location = target.getLocation(); + out.println(location.print() + ": " + target.getTargetKind() + " " + target.getLabel()); + } } } @@ -321,57 +290,48 @@ public abstract class OutputFormatter implements Serializable { return "build"; } - @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, - AspectResolver aspectResolver) { - return new OutputFormatterCallback<Target>() { - private final Set<Label> printed = CompactHashSet.create(); - - private void outputRule(Rule rule, PrintStream out) { - out.printf("# %s%n", rule.getLocation()); - out.printf("%s(%n", rule.getRuleClass()); - out.printf(" name = \"%s\",%n", rule.getName()); - - for (Attribute attr : rule.getAttributes()) { - Pair<Iterable<Object>, AttributeValueSource> values = getAttributeValues(rule, attr); - if (Iterables.size(values.first) != 1) { - continue; // TODO(bazel-team): handle configurable attributes. - } - if (values.second != AttributeValueSource.RULE) { - continue; // Don't print default values. - } - Object value = Iterables.getOnlyElement(values.first); - out.printf(" %s = ", attr.getPublicName()); - if (value instanceof Label) { - value = value.toString(); - } else if (value instanceof List<?> && EvalUtils.isImmutable(value)) { - // Display it as a list (and not as a tuple). Attributes can never be tuples. - value = new ArrayList<>((List<?>) value); - } - // It is *much* faster to write to a StringBuilder compared to the PrintStream object. - StringBuilder builder = new StringBuilder(); - Printer.write(builder, value); - out.print(builder); - out.println(","); - } - out.printf(")\n%n"); - } + private void outputRule(Rule rule, PrintStream out) { + out.printf("# %s%n", rule.getLocation()); + out.printf("%s(%n", rule.getRuleClass()); + out.printf(" name = \"%s\",%n", rule.getName()); - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { + for (Attribute attr : rule.getAttributes()) { + Pair<Iterable<Object>, AttributeValueSource> values = getAttributeValues(rule, attr); + if (Iterables.size(values.first) != 1) { + continue; // TODO(bazel-team): handle configurable attributes. + } + if (values.second != AttributeValueSource.RULE) { + continue; // Don't print default values. + } + Object value = Iterables.getOnlyElement(values.first); + out.printf(" %s = ", attr.getPublicName()); + if (value instanceof Label) { + value = value.toString(); + } else if (value instanceof List<?> && EvalUtils.isImmutable(value)) { + // Display it as a list (and not as a tuple). Attributes can never be tuples. + value = new ArrayList<>((List<?>) value); + } + // It is *much* faster to write to a StringBuilder compared to the PrintStream object. + StringBuilder builder = new StringBuilder(); + Printer.write(builder, value); + out.print(builder); + out.println(","); + } + out.printf(")\n%n"); + } - for (Target target : partialResult) { - Rule rule = target.getAssociatedRule(); - if (rule == null || printed.contains(rule.getLabel())) { - continue; - } - outputRule(rule, out); - printed.add(rule.getLabel()); - } + @Override + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, + AspectResolver aspectResolver) { + Set<Label> printed = new HashSet<>(); + for (Target target : result) { + Rule rule = target.getAssociatedRule(); + if (rule == null || printed.contains(rule.getLabel())) { + continue; } - }; + outputRule(rule, out); + printed.add(rule.getLabel()); + } } } diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java index 8fc130177e..815f9152bf 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/ProtoOutputFormatter.java @@ -34,12 +34,10 @@ import com.google.devtools.build.lib.packages.ProtoUtils; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.query2.FakeSubincludeTarget; -import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; import com.google.devtools.build.lib.query2.output.AspectResolver.BuildFileDependencyMode; -import com.google.devtools.build.lib.query2.output.OutputFormatter.AbstractUnorderedFormatter; +import com.google.devtools.build.lib.query2.output.OutputFormatter.UnorderedFormatter; import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput; import com.google.devtools.build.lib.query2.proto.proto2api.Build; -import com.google.devtools.build.lib.query2.proto.proto2api.Build.QueryResult.Builder; import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.util.BinaryPredicate; @@ -56,7 +54,7 @@ import java.util.Set; * By taking the bytes and calling {@code mergeFrom()} on a * {@code Build.QueryResult} object the full result can be reconstructed. */ -public class ProtoOutputFormatter extends AbstractUnorderedFormatter { +public class ProtoOutputFormatter extends OutputFormatter implements UnorderedFormatter { /** * A special attribute name for the rule implementation hash code. @@ -79,36 +77,19 @@ public class ProtoOutputFormatter extends AbstractUnorderedFormatter { } @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, AspectResolver aspectResolver) { + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, + AspectResolver aspectResolver) throws IOException, InterruptedException { relativeLocations = options.relativeLocations; this.aspectResolver = aspectResolver; this.includeDefaultValues = options.protoIncludeDefaultValues; setDependencyFilter(options); - return new OutputFormatterCallback<Target>() { - - private Builder queryResult; - - @Override - public void start() { - queryResult = Build.QueryResult.newBuilder(); - } - - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { - - for (Target target : partialResult) { - queryResult.addTarget(toTargetProtoBuffer(target)); - } - } + Build.QueryResult.Builder queryResult = Build.QueryResult.newBuilder(); + for (Target target : result) { + addTarget(queryResult, target); + } - @Override - public void close() throws IOException { - queryResult.build().writeTo(out); - } - }; + queryResult.build().writeTo(out); } private static Iterable<Target> getSortedLabels(Digraph<Target> result) { @@ -117,8 +98,24 @@ public class ProtoOutputFormatter extends AbstractUnorderedFormatter { } @Override - protected Iterable<Target> getOrderedTargets(Digraph<Target> result, QueryOptions options) { - return options.orderOutput == OrderOutput.FULL ? getSortedLabels(result) : result.getLabels(); + public void output(QueryOptions options, Digraph<Target> result, PrintStream out, + AspectResolver aspectResolver) throws IOException, InterruptedException { + outputUnordered( + options, + options.orderOutput == OrderOutput.FULL ? getSortedLabels(result) : result.getLabels(), + out, + aspectResolver); + } + + /** + * Add the target to the query result. + * @param queryResult The query result that contains all rule, input and + * output targets. + * @param target The query target being converted to a protocol buffer. + */ + private void addTarget(Build.QueryResult.Builder queryResult, Target target) + throws InterruptedException { + queryResult.addTarget(toTargetProtoBuffer(target)); } /** diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java index 1533b7bf43..36f5464461 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/QueryOutputUtils.java @@ -14,43 +14,32 @@ package com.google.devtools.build.lib.query2.output; import com.google.devtools.build.lib.packages.Target; -import com.google.devtools.build.lib.query2.engine.DigraphQueryEvalResult; -import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; +import com.google.devtools.build.lib.query2.engine.BlazeQueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryEvalResult; -import com.google.devtools.build.lib.query2.output.OutputFormatter.StreamedFormatter; +import com.google.devtools.build.lib.query2.output.OutputFormatter.UnorderedFormatter; import com.google.devtools.build.lib.query2.output.QueryOptions.OrderOutput; import java.io.IOException; import java.io.PrintStream; -import java.util.Set; /** Static utility methods for outputting a query. */ public class QueryOutputUtils { // Utility class cannot be instantiated. private QueryOutputUtils() {} - public static boolean shouldStreamResults(QueryOptions queryOptions, OutputFormatter formatter) { - return queryOptions.orderOutput == OrderOutput.NO - && formatter instanceof StreamedFormatter; + public static boolean orderResults(QueryOptions queryOptions, OutputFormatter formatter) { + return queryOptions.orderOutput != OrderOutput.NO || !(formatter instanceof UnorderedFormatter); } - public static void output(QueryOptions queryOptions, QueryEvalResult result, - Set<Target> targetsResult, OutputFormatter formatter, PrintStream outputStream, - AspectResolver aspectResolver) + public static void output(QueryOptions queryOptions, QueryEvalResult<Target> result, + OutputFormatter formatter, PrintStream outputStream, AspectResolver aspectResolver) throws IOException, InterruptedException { - /* - * This is not really streaming, but we are using the streaming interface for writing into the - * output everything in one batch. This happens when the QueryEnvironment does not - * support streaming but we don't care about ordered results. - */ - boolean orderedResults = !shouldStreamResults(queryOptions, formatter); - if (orderedResults) { - formatter.output(queryOptions, - ((DigraphQueryEvalResult<Target>) result).getGraph().extractSubgraph(targetsResult), + if (orderResults(queryOptions, formatter)) { + formatter.output(queryOptions, ((BlazeQueryEvalResult<Target>) result).getResultGraph(), outputStream, aspectResolver); } else { - OutputFormatterCallback.processAllTargets(((StreamedFormatter) formatter) - .createStreamCallback(queryOptions, outputStream, aspectResolver), targetsResult); + ((UnorderedFormatter) formatter).outputUnordered(queryOptions, result.getResultSet(), + outputStream, aspectResolver); } } } diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/XmlOutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/XmlOutputFormatter.java index 2af71b7414..b8b4466bad 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/XmlOutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/XmlOutputFormatter.java @@ -27,7 +27,6 @@ import com.google.devtools.build.lib.packages.PackageGroup; import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.query2.FakeSubincludeTarget; -import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; import com.google.devtools.build.lib.query2.output.AspectResolver.BuildFileDependencyMode; import com.google.devtools.build.lib.query2.output.OutputFormatter.AbstractUnorderedFormatter; import com.google.devtools.build.lib.util.BinaryPredicate; @@ -37,7 +36,6 @@ import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; -import java.io.IOException; import java.io.PrintStream; import java.util.Collection; import java.util.HashSet; @@ -59,9 +57,11 @@ import javax.xml.transform.stream.StreamResult; */ class XmlOutputFormatter extends AbstractUnorderedFormatter { - private QueryOptions options; - private AspectResolver aspectResolver; - private BinaryPredicate<Rule, Attribute> dependencyFilter; + private boolean xmlLineNumbers; + private boolean showDefaultValues; + private boolean relativeLocations; + private transient AspectResolver aspectResolver; + private transient BinaryPredicate<Rule, Attribute> dependencyFilter; @Override public String getName() { @@ -69,52 +69,36 @@ class XmlOutputFormatter extends AbstractUnorderedFormatter { } @Override - public OutputFormatterCallback<Target> createStreamCallback(QueryOptions options, - final PrintStream out, AspectResolver aspectResolver) { - this.options = options; - this.aspectResolver = aspectResolver; + public void outputUnordered(QueryOptions options, Iterable<Target> result, PrintStream out, + AspectResolver aspectResolver) throws InterruptedException { + this.xmlLineNumbers = options.xmlLineNumbers; + this.showDefaultValues = options.xmlShowDefaultValues; + this.relativeLocations = options.relativeLocations; this.dependencyFilter = OutputFormatter.getDependencyFilter(options); - return new OutputFormatterCallback<Target>() { - - private Document doc; - private Element queryElem; - - - @Override - public void start() { - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - doc = factory.newDocumentBuilder().newDocument(); - } catch (ParserConfigurationException e) { - // This shouldn't be possible: all the configuration is hard-coded. - throw new IllegalStateException("XML output failed", e); - } - doc.setXmlVersion("1.1"); - queryElem = doc.createElement("query"); - queryElem.setAttribute("version", "2"); - doc.appendChild(queryElem); - } - - @Override - protected void processOutput(Iterable<Target> partialResult) - throws IOException, InterruptedException { - for (Target target : partialResult) { - queryElem.appendChild(createTargetElement(doc, target)); - } - } - - @Override - public void close() throws IOException { - try { - Transformer transformer = TransformerFactory.newInstance().newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.transform(new DOMSource(doc), new StreamResult(out)); - } catch (TransformerFactoryConfigurationError | TransformerException e) { - // This shouldn't be possible: all the configuration is hard-coded. - throw new IllegalStateException("XML output failed", e); - } - } - }; + this.aspectResolver = aspectResolver; + Document doc; + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + doc = factory.newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + // This shouldn't be possible: all the configuration is hard-coded. + throw new IllegalStateException("XML output failed", e); + } + doc.setXmlVersion("1.1"); + Element queryElem = doc.createElement("query"); + queryElem.setAttribute("version", "2"); + doc.appendChild(queryElem); + for (Target target : result) { + queryElem.appendChild(createTargetElement(doc, target)); + } + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(new DOMSource(doc), new StreamResult(out)); + } catch (TransformerFactoryConfigurationError | TransformerException e) { + // This shouldn't be possible: all the configuration is hard-coded. + throw new IllegalStateException("XML output failed", e); + } } /** @@ -136,9 +120,9 @@ class XmlOutputFormatter extends AbstractUnorderedFormatter { Rule rule = (Rule) target; elem = doc.createElement("rule"); elem.setAttribute("class", rule.getRuleClass()); - for (Attribute attr : rule.getAttributes()) { + for (Attribute attr: rule.getAttributes()) { Pair<Iterable<Object>, AttributeValueSource> values = getAttributeValues(rule, attr); - if (values.second == AttributeValueSource.RULE || options.xmlShowDefaultValues) { + if (values.second == AttributeValueSource.RULE || showDefaultValues) { Element attrElem = createValueElement(doc, attr.getType(), values.first); attrElem.setAttribute("name", attr.getName()); elem.appendChild(attrElem); @@ -221,8 +205,8 @@ class XmlOutputFormatter extends AbstractUnorderedFormatter { } elem.setAttribute("name", target.getLabel().toString()); - String location = getLocation(target, options.relativeLocations); - if (!options.xmlLineNumbers) { + String location = getLocation(target, relativeLocations); + if (!xmlLineNumbers) { int firstColon = location.indexOf(':'); if (firstColon != -1) { location = location.substring(0, firstColon); diff --git a/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java b/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java index 76eac71bcf..13d5be4dd8 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java +++ b/src/main/java/com/google/devtools/build/lib/rules/genquery/GenQuery.java @@ -49,11 +49,10 @@ import com.google.devtools.build.lib.pkgcache.FilteringPolicy; import com.google.devtools.build.lib.pkgcache.PackageProvider; import com.google.devtools.build.lib.pkgcache.TargetPatternEvaluator; import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment; -import com.google.devtools.build.lib.query2.engine.DigraphQueryEvalResult; +import com.google.devtools.build.lib.query2.engine.BlazeQueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting; import com.google.devtools.build.lib.query2.engine.QueryException; -import com.google.devtools.build.lib.query2.engine.QueryUtil.AggregateAllCallback; import com.google.devtools.build.lib.query2.engine.SkyframeRestartQueryException; import com.google.devtools.build.lib.query2.output.OutputFormatter; import com.google.devtools.build.lib.query2.output.QueryOptions; @@ -261,9 +260,8 @@ public class GenQuery implements RuleConfiguredTargetFactory { String query, RuleContext ruleContext) throws InterruptedException { - DigraphQueryEvalResult<Target> queryResult; + BlazeQueryEvalResult<Target> queryResult; OutputFormatter formatter; - AggregateAllCallback<Target> targets = new AggregateAllCallback<>(); try { Set<Setting> settings = queryOptions.toSettings(); @@ -285,20 +283,20 @@ public class GenQuery implements RuleConfiguredTargetFactory { // All the packages are already loaded at this point, so there is no need // to start up many threads. 4 are started up to make good use of multiple // cores. - queryResult = (DigraphQueryEvalResult<Target>) AbstractBlazeQueryEnvironment + queryResult = (BlazeQueryEvalResult<Target>) AbstractBlazeQueryEnvironment .newQueryEnvironment( /*transitivePackageLoader=*/null, /*graph=*/null, packageProvider, evaluator, /*keepGoing=*/false, ruleContext.attributes().get("strict", Type.BOOLEAN), - /*orderedResults=*/!QueryOutputUtils.shouldStreamResults(queryOptions, formatter), + /*orderedResults=*/QueryOutputUtils.orderResults(queryOptions, formatter), /*universeScope=*/ImmutableList.<String>of(), /*loadingPhaseThreads=*/4, labelFilter, getEventHandler(ruleContext), settings, ImmutableList.<QueryFunction>of(), - /*packagePath=*/null).evaluateQuery(query, targets); + /*packagePath=*/null).evaluateQuery(query); } catch (SkyframeRestartQueryException e) { // Do not emit errors for skyframe restarts. They make output of the ConfiguredTargetFunction // inconsistent from run to run, and make detecting legitimate errors more difficult. @@ -312,8 +310,7 @@ public class GenQuery implements RuleConfiguredTargetFactory { PrintStream printStream = new PrintStream(outputStream); try { - QueryOutputUtils - .output(queryOptions, queryResult, targets.getResult(), formatter, printStream, + QueryOutputUtils.output(queryOptions, queryResult, formatter, printStream, queryOptions.aspectDeps.createResolver(packageProvider, getEventHandler(ruleContext))); } catch (ClosedByInterruptException e) { throw new InterruptedException(e.getMessage()); diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java index b80be41938..2f371c9dd1 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/QueryCommand.java @@ -16,21 +16,17 @@ package com.google.devtools.build.lib.runtime.commands; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import com.google.devtools.build.lib.Constants; -import com.google.devtools.build.lib.collect.CompactHashSet; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.pkgcache.PackageCacheOptions; import com.google.devtools.build.lib.query2.AbstractBlazeQueryEnvironment; -import com.google.devtools.build.lib.query2.engine.OutputFormatterCallback; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.QueryFunction; import com.google.devtools.build.lib.query2.engine.QueryEnvironment.Setting; import com.google.devtools.build.lib.query2.engine.QueryEvalResult; import com.google.devtools.build.lib.query2.engine.QueryException; import com.google.devtools.build.lib.query2.engine.QueryExpression; import com.google.devtools.build.lib.query2.output.OutputFormatter; -import com.google.devtools.build.lib.query2.output.OutputFormatter.StreamedFormatter; import com.google.devtools.build.lib.query2.output.QueryOptions; import com.google.devtools.build.lib.query2.output.QueryOutputUtils; import com.google.devtools.build.lib.runtime.BlazeCommand; @@ -111,11 +107,10 @@ public final class QueryCommand implements BlazeCommand { String query = Joiner.on(' ').join(options.getResidue()); Set<Setting> settings = queryOptions.toSettings(); - boolean streamResults = QueryOutputUtils.shouldStreamResults(queryOptions, formatter); AbstractBlazeQueryEnvironment<Target> queryEnv = newQueryEnvironment( env, queryOptions.keepGoing, - !streamResults, + QueryOutputUtils.orderResults(queryOptions, formatter), queryOptions.universeScope, queryOptions.loadingPhaseThreads, settings); @@ -129,70 +124,36 @@ public final class QueryCommand implements BlazeCommand { return ExitCode.COMMAND_LINE_ERROR; } - QueryEvalResult result; - PrintStream output = new PrintStream(env.getReporter().getOutErr().getOutputStream()); - OutputFormatterCallback<Target> callback; - if (streamResults) { - // 2. Evaluate expression: - callback = ((StreamedFormatter) formatter) - .createStreamCallback(queryOptions, output, queryOptions.aspectDeps.createResolver( - env.getPackageManager(), env.getReporter())); - } else { - callback = new AggregateAllOutputFormatterCallback<>(); - } + // 2. Evaluate expression: + QueryEvalResult<Target> result; try { - callback.start(); - result = queryEnv.evaluateQuery(expr, callback); - } catch (QueryException e) { + result = queryEnv.evaluateQuery(expr); + } catch (QueryException | InterruptedException e) { // Keep consistent with reportBuildFileError() env.getReporter() - // TODO(bazel-team): this is a kludge to fix a bug observed in the wild. We should make - // sure no null error messages ever get in. - .handle(Event.error(e.getMessage() == null ? e.toString() : e.getMessage())); + // TODO(bazel-team): this is a kludge to fix a bug observed in the wild. We should make + // sure no null error messages ever get in. + .handle(Event.error(e.getMessage() == null ? e.toString() : e.getMessage())); return ExitCode.ANALYSIS_FAILURE; - } catch (InterruptedException e) { - IOException ioException = callback.getIoException(); - if (ioException == null || ioException instanceof ClosedByInterruptException) { - env.getReporter().handle(Event.error("query interrupted")); - return ExitCode.INTERRUPTED; - } else { - env.getReporter().handle(Event.error("I/O error: " + e.getMessage())); - return ExitCode.LOCAL_ENVIRONMENTAL_ERROR; - } + } + + env.getReporter().switchToAnsiAllowingHandler(); + // 3. Output results: + PrintStream output = new PrintStream(env.getReporter().getOutErr().getOutputStream()); + try { + QueryOutputUtils.output(queryOptions, result, formatter, output, + queryOptions.aspectDeps.createResolver( + env.getPackageManager(), env.getReporter())); + } catch (ClosedByInterruptException | InterruptedException e) { + env.getReporter().handle(Event.error("query interrupted")); + return ExitCode.INTERRUPTED; } catch (IOException e) { env.getReporter().handle(Event.error("I/O error: " + e.getMessage())); return ExitCode.LOCAL_ENVIRONMENTAL_ERROR; } finally { output.flush(); - try { - callback.close(); - } catch (IOException e) { - env.getReporter().handle(Event.error("I/O error: " + e.getMessage())); - return ExitCode.LOCAL_ENVIRONMENTAL_ERROR; - } - } - if (!streamResults) { - // 3. Output results: - try { - Set<Target> targets = ((AggregateAllOutputFormatterCallback<Target>) callback).getOutput(); - QueryOutputUtils.output(queryOptions, result, - targets, formatter, output, - queryOptions.aspectDeps.createResolver( - env.getPackageManager(), env.getReporter())); - } catch (ClosedByInterruptException | InterruptedException e) { - env.getReporter().handle(Event.error("query interrupted")); - return ExitCode.INTERRUPTED; - } catch (IOException e) { - env.getReporter().handle(Event.error("I/O error: " + e.getMessage())); - return ExitCode.LOCAL_ENVIRONMENTAL_ERROR; - } finally { - output.flush(); - } - } - env.getReporter().switchToAnsiAllowingHandler(); - - if (result.isEmpty()) { + if (result.getResultSet().isEmpty()) { env.getReporter().handle(Event.info("Empty results")); } @@ -224,19 +185,4 @@ public final class QueryCommand implements BlazeCommand { functions.build(), env.getPackageManager().getPackagePath()); } - - private static class AggregateAllOutputFormatterCallback<T> extends OutputFormatterCallback<T> { - - private Set<T> output = CompactHashSet.create(); - - @Override - protected final void processOutput(Iterable<T> partialResult) - throws IOException, InterruptedException { - Iterables.addAll(output, partialResult); - } - - public Set<T> getOutput() { - return output; - } - } } |