/* * Copyright (C) 2019 The Dagger Authors. * * 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 dagger.internal.codegen.validation; 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.Lists.asList; import static dagger.internal.codegen.base.ElementFormatter.elementToString; import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; import static dagger.internal.codegen.langmodel.DaggerElements.elementEncloses; import com.google.common.collect.ImmutableSet; import com.google.errorprone.annotations.FormatMethod; import dagger.model.BindingGraph; import dagger.model.BindingGraph.ChildFactoryMethodEdge; import dagger.model.BindingGraph.ComponentNode; import dagger.model.BindingGraph.DependencyEdge; import dagger.model.BindingGraph.MaybeBinding; import dagger.spi.BindingGraphPlugin; import dagger.spi.DiagnosticReporter; import java.util.Map; import java.util.Optional; import java.util.Set; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.util.Elements; // ALLOW_TYPES_ELEMENTS because of interface dependencies import javax.lang.model.util.Types; // ALLOW_TYPES_ELEMENTS because of interface dependencies import javax.tools.Diagnostic; /** * Combines many {@link BindingGraphPlugin} implementations. This helps reduce spam by combining * all of the messages that are reported on the root component. */ public final class CompositeBindingGraphPlugin implements BindingGraphPlugin { private final ImmutableSet plugins; private final String pluginName; private final DiagnosticMessageGenerator.Factory messageGeneratorFactory; /** Factory class for {@link CompositeBindingGraphPlugin}. */ public static final class Factory { private final DiagnosticMessageGenerator.Factory messageGeneratorFactory; @Inject Factory(DiagnosticMessageGenerator.Factory messageGeneratorFactory) { this.messageGeneratorFactory = messageGeneratorFactory; } public CompositeBindingGraphPlugin create( ImmutableSet plugins, String pluginName) { return new CompositeBindingGraphPlugin(plugins, pluginName, messageGeneratorFactory); } } private CompositeBindingGraphPlugin( ImmutableSet plugins, String pluginName, DiagnosticMessageGenerator.Factory messageGeneratorFactory) { this.plugins = plugins; this.pluginName = pluginName; this.messageGeneratorFactory = messageGeneratorFactory; } @Override public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { AggregatingDiagnosticReporter aggregatingDiagnosticReporter = new AggregatingDiagnosticReporter( bindingGraph, diagnosticReporter, messageGeneratorFactory.create(bindingGraph)); plugins.forEach(plugin -> { aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName()); plugin.visitGraph(bindingGraph, aggregatingDiagnosticReporter); }); aggregatingDiagnosticReporter.report(); } @Override public void initFiler(Filer filer) { plugins.forEach(plugin -> plugin.initFiler(filer)); } @Override public void initTypes(Types types) { plugins.forEach(plugin -> plugin.initTypes(types)); } @Override public void initElements(Elements elements) { plugins.forEach(plugin -> plugin.initElements(elements)); } @Override public void initOptions(Map options) { plugins.forEach(plugin -> plugin.initOptions(options)); } @Override public Set supportedOptions() { return plugins.stream().flatMap( plugin -> plugin.supportedOptions().stream()).collect(toImmutableSet()); } @Override public String pluginName() { return pluginName; } // TODO(erichang): This kind of breaks some of the encapsulation by relying on or repeating // logic within DiagnosticReporterImpl. Hopefully if the experiment for aggregated messages // goes well though this can be merged with that implementation. private static final class AggregatingDiagnosticReporter implements DiagnosticReporter { private final DiagnosticReporter delegate; private final BindingGraph graph; // Initialize with a new line so the first message appears below the reported component private final StringBuilder messageBuilder = new StringBuilder("\n"); private final DiagnosticMessageGenerator messageGenerator; private Optional mergedDiagnosticKind = Optional.empty(); private String currentPluginName = null; AggregatingDiagnosticReporter( BindingGraph graph, DiagnosticReporter delegate, DiagnosticMessageGenerator messageGenerator) { this.graph = graph; this.delegate = delegate; this.messageGenerator = messageGenerator; } /** Sets the currently running aggregated plugin. Used to add a diagnostic prefix. */ void setCurrentPlugin(String pluginName) { currentPluginName = pluginName; } /** Reports all of the stored diagnostics. */ void report() { if (mergedDiagnosticKind.isPresent()) { delegate.reportComponent( mergedDiagnosticKind.get(), graph.rootComponentNode(), PackageNameCompressor.compressPackagesInMessage(messageBuilder.toString())); } } @Override public void reportComponent(Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) { addMessage(diagnosticKind, message); messageGenerator.appendComponentPathUnlessAtRoot(messageBuilder, componentNode); } @Override @FormatMethod public void reportComponent( Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String messageFormat, Object firstArg, Object... moreArgs) { reportComponent( diagnosticKind, componentNode, formatMessage(messageFormat, firstArg, moreArgs)); } @Override public void reportBinding(Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) { addMessage(diagnosticKind, String.format("%s%s", message, messageGenerator.getMessage(binding))); } @Override @FormatMethod public void reportBinding( Diagnostic.Kind diagnosticKind, MaybeBinding binding, String messageFormat, Object firstArg, Object... moreArgs) { reportBinding(diagnosticKind, binding, formatMessage(messageFormat, firstArg, moreArgs)); } @Override public void reportDependency( Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) { addMessage(diagnosticKind, String.format("%s%s", message, messageGenerator.getMessage(dependencyEdge))); } @Override @FormatMethod public void reportDependency( Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String messageFormat, Object firstArg, Object... moreArgs) { reportDependency( diagnosticKind, dependencyEdge, formatMessage(messageFormat, firstArg, moreArgs)); } @Override public void reportSubcomponentFactoryMethod( Diagnostic.Kind diagnosticKind, ChildFactoryMethodEdge childFactoryMethodEdge, String message) { // TODO(erichang): This repeats some of the logic in DiagnosticReporterImpl. Remove when // merged. if (elementEncloses( graph.rootComponentNode().componentPath().currentComponent(), childFactoryMethodEdge.factoryMethod())) { // Let this pass through since it is not an error reported on the root component delegate.reportSubcomponentFactoryMethod(diagnosticKind, childFactoryMethodEdge, message); } else { addMessage( diagnosticKind, String.format( "[%s] %s", elementToString(childFactoryMethodEdge.factoryMethod()), message)); } } @Override @FormatMethod public void reportSubcomponentFactoryMethod( Diagnostic.Kind diagnosticKind, ChildFactoryMethodEdge childFactoryMethodEdge, String messageFormat, Object firstArg, Object... moreArgs) { reportSubcomponentFactoryMethod( diagnosticKind, childFactoryMethodEdge, formatMessage(messageFormat, firstArg, moreArgs)); } /** Adds a message to the stored aggregated message. */ private void addMessage(Diagnostic.Kind diagnosticKind, String message) { checkNotNull(diagnosticKind); checkNotNull(message); checkState(currentPluginName != null); // Add a separator if this isn't the first message if (mergedDiagnosticKind.isPresent()) { messageBuilder.append("\n\n"); } mergeDiagnosticKind(diagnosticKind); // Adds brackets as well as special color strings to make the string red and bold. messageBuilder.append(String.format("\033[1;31m[%s]\033[0m ", currentPluginName)); messageBuilder.append(message); } private static String formatMessage(String messageFormat, Object firstArg, Object[] moreArgs) { return String.format(messageFormat, asList(firstArg, moreArgs).toArray()); } private void mergeDiagnosticKind(Diagnostic.Kind diagnosticKind) { checkArgument(diagnosticKind != Diagnostic.Kind.MANDATORY_WARNING, "Dagger plugins should not be issuing mandatory warnings"); if (!mergedDiagnosticKind.isPresent()) { mergedDiagnosticKind = Optional.of(diagnosticKind); return; } Diagnostic.Kind current = mergedDiagnosticKind.get(); if (current == Diagnostic.Kind.ERROR || diagnosticKind == Diagnostic.Kind.ERROR) { mergedDiagnosticKind = Optional.of(Diagnostic.Kind.ERROR); } else if (current == Diagnostic.Kind.WARNING || diagnosticKind == Diagnostic.Kind.WARNING) { mergedDiagnosticKind = Optional.of(Diagnostic.Kind.WARNING); } else if (current == Diagnostic.Kind.NOTE || diagnosticKind == Diagnostic.Kind.NOTE) { mergedDiagnosticKind = Optional.of(Diagnostic.Kind.NOTE); } else { mergedDiagnosticKind = Optional.of(Diagnostic.Kind.OTHER); } } } }