1 /* 2 * Copyright (C) 2019 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.bindinggraphvalidation; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Preconditions.checkState; 22 import static dagger.internal.codegen.base.ElementFormatter.elementToString; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 24 import static dagger.internal.codegen.xprocessing.XElements.transitivelyEncloses; 25 26 import com.google.common.collect.ImmutableSet; 27 import dagger.assisted.Assisted; 28 import dagger.assisted.AssistedFactory; 29 import dagger.assisted.AssistedInject; 30 import dagger.internal.codegen.model.BindingGraph; 31 import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge; 32 import dagger.internal.codegen.model.BindingGraph.ComponentNode; 33 import dagger.internal.codegen.model.BindingGraph.DependencyEdge; 34 import dagger.internal.codegen.model.BindingGraph.MaybeBinding; 35 import dagger.internal.codegen.model.BindingGraphPlugin; 36 import dagger.internal.codegen.model.DaggerProcessingEnv; 37 import dagger.internal.codegen.model.DiagnosticReporter; 38 import dagger.internal.codegen.validation.DiagnosticMessageGenerator; 39 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin; 40 import java.util.HashMap; 41 import java.util.Map; 42 import java.util.Optional; 43 import java.util.Set; 44 import javax.tools.Diagnostic; 45 46 /** 47 * Combines many {@link BindingGraphPlugin} implementations. This helps reduce spam by combining all 48 * of the messages that are reported on the root component. 49 */ 50 final class CompositeBindingGraphPlugin extends ValidationBindingGraphPlugin { 51 @AssistedFactory 52 interface Factory { create(ImmutableSet<ValidationBindingGraphPlugin> plugins)53 CompositeBindingGraphPlugin create(ImmutableSet<ValidationBindingGraphPlugin> plugins); 54 } 55 56 private final ImmutableSet<ValidationBindingGraphPlugin> plugins; 57 private final DiagnosticMessageGenerator.Factory messageGeneratorFactory; 58 private final Map<ComponentNode, String> errorMessages = new HashMap<>(); 59 60 @AssistedInject CompositeBindingGraphPlugin( @ssisted ImmutableSet<ValidationBindingGraphPlugin> plugins, DiagnosticMessageGenerator.Factory messageGeneratorFactory)61 CompositeBindingGraphPlugin( 62 @Assisted ImmutableSet<ValidationBindingGraphPlugin> plugins, 63 DiagnosticMessageGenerator.Factory messageGeneratorFactory) { 64 this.plugins = plugins; 65 this.messageGeneratorFactory = messageGeneratorFactory; 66 } 67 68 @Override visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)69 public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) { 70 AggregatingDiagnosticReporter aggregatingDiagnosticReporter = new AggregatingDiagnosticReporter( 71 bindingGraph, diagnosticReporter, messageGeneratorFactory.create(bindingGraph)); 72 plugins.forEach( 73 plugin -> { 74 aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName()); 75 plugin.visitGraph(bindingGraph, aggregatingDiagnosticReporter); 76 if (plugin.visitFullGraphRequested(bindingGraph)) { 77 requestVisitFullGraph(bindingGraph); 78 } 79 }); 80 if (visitFullGraphRequested(bindingGraph)) { 81 errorMessages.put( 82 bindingGraph.rootComponentNode(), aggregatingDiagnosticReporter.getMessage()); 83 } else { 84 aggregatingDiagnosticReporter.report(); 85 } 86 } 87 88 @Override revisitFullGraph( BindingGraph prunedGraph, BindingGraph fullGraph, DiagnosticReporter diagnosticReporter)89 public void revisitFullGraph( 90 BindingGraph prunedGraph, BindingGraph fullGraph, DiagnosticReporter diagnosticReporter) { 91 AggregatingDiagnosticReporter aggregatingDiagnosticReporter = 92 new AggregatingDiagnosticReporter( 93 fullGraph, 94 diagnosticReporter, 95 errorMessages.get(prunedGraph.rootComponentNode()), 96 messageGeneratorFactory.create(fullGraph)); 97 plugins.stream() 98 .filter(plugin -> plugin.visitFullGraphRequested(prunedGraph)) 99 .forEach( 100 plugin -> { 101 aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName()); 102 plugin.revisitFullGraph(prunedGraph, fullGraph, aggregatingDiagnosticReporter); 103 }); 104 aggregatingDiagnosticReporter.report(); 105 } 106 107 @Override init(DaggerProcessingEnv processingEnv, Map<String, String> options)108 public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) { 109 plugins.forEach(plugin -> plugin.init(processingEnv, options)); 110 } 111 112 @Override onPluginEnd()113 public void onPluginEnd() { 114 plugins.forEach(BindingGraphPlugin::onPluginEnd); 115 } 116 117 @Override supportedOptions()118 public Set<String> supportedOptions() { 119 return plugins.stream() 120 .flatMap(plugin -> plugin.supportedOptions().stream()) 121 .collect(toImmutableSet()); 122 } 123 124 @Override pluginName()125 public String pluginName() { 126 return "Dagger/Validation"; 127 } 128 129 // TODO(erichang): This kind of breaks some of the encapsulation by relying on or repeating 130 // logic within DiagnosticReporterImpl. Hopefully if the experiment for aggregated messages 131 // goes well though this can be merged with that implementation. 132 private static final class AggregatingDiagnosticReporter extends DiagnosticReporter { 133 private final DiagnosticReporter delegate; 134 private final BindingGraph graph; 135 // Initialize with a new line so the first message appears below the reported component 136 private final StringBuilder messageBuilder = new StringBuilder("\n"); 137 private final DiagnosticMessageGenerator messageGenerator; 138 private Optional<Diagnostic.Kind> mergedDiagnosticKind = Optional.empty(); 139 private String currentPluginName = null; 140 AggregatingDiagnosticReporter( BindingGraph graph, DiagnosticReporter delegate, DiagnosticMessageGenerator messageGenerator)141 AggregatingDiagnosticReporter( 142 BindingGraph graph, 143 DiagnosticReporter delegate, 144 DiagnosticMessageGenerator messageGenerator) { 145 this.graph = graph; 146 this.delegate = delegate; 147 this.messageGenerator = messageGenerator; 148 } 149 AggregatingDiagnosticReporter( BindingGraph graph, DiagnosticReporter delegate, String baseMessage, DiagnosticMessageGenerator messageGenerator)150 AggregatingDiagnosticReporter( 151 BindingGraph graph, 152 DiagnosticReporter delegate, 153 String baseMessage, 154 DiagnosticMessageGenerator messageGenerator) { 155 this.graph = graph; 156 this.delegate = delegate; 157 this.messageGenerator = messageGenerator; 158 this.messageBuilder.append(baseMessage); 159 } 160 161 /** Sets the currently running aggregated plugin. Used to add a diagnostic prefix. */ setCurrentPlugin(String pluginName)162 void setCurrentPlugin(String pluginName) { 163 currentPluginName = pluginName; 164 } 165 166 /** Reports all of the stored diagnostics. */ report()167 void report() { 168 if (mergedDiagnosticKind.isPresent()) { 169 delegate.reportComponent( 170 mergedDiagnosticKind.get(), 171 graph.rootComponentNode(), 172 PackageNameCompressor.compressPackagesInMessage(messageBuilder.toString())); 173 } 174 } 175 getMessage()176 String getMessage() { 177 return messageBuilder.toString(); 178 } 179 180 @Override reportComponent( Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message)181 public void reportComponent( 182 Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) { 183 addMessage(diagnosticKind, message); 184 messageGenerator.appendComponentPathUnlessAtRoot(messageBuilder, componentNode); 185 } 186 187 @Override reportBinding( Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message)188 public void reportBinding( 189 Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) { 190 addMessage(diagnosticKind, 191 String.format("%s%s", message, messageGenerator.getMessage(binding))); 192 } 193 194 @Override reportDependency( Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message)195 public void reportDependency( 196 Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) { 197 addMessage(diagnosticKind, 198 String.format("%s%s", message, messageGenerator.getMessage(dependencyEdge))); 199 } 200 201 @Override reportSubcomponentFactoryMethod( Diagnostic.Kind diagnosticKind, ChildFactoryMethodEdge childFactoryMethodEdge, String message)202 public void reportSubcomponentFactoryMethod( 203 Diagnostic.Kind diagnosticKind, 204 ChildFactoryMethodEdge childFactoryMethodEdge, 205 String message) { 206 // TODO(erichang): This repeats some of the logic in DiagnosticReporterImpl. Remove when 207 // merged. 208 if (transitivelyEncloses( 209 graph.rootComponentNode().componentPath().currentComponent().xprocessing(), 210 childFactoryMethodEdge.factoryMethod().xprocessing())) { 211 // Let this pass through since it is not an error reported on the root component 212 delegate.reportSubcomponentFactoryMethod(diagnosticKind, childFactoryMethodEdge, message); 213 } else { 214 addMessage( 215 diagnosticKind, 216 String.format( 217 "[%s] %s", 218 elementToString(childFactoryMethodEdge.factoryMethod().xprocessing()), message)); 219 } 220 } 221 222 /** Adds a message to the stored aggregated message. */ addMessage(Diagnostic.Kind diagnosticKind, String message)223 private void addMessage(Diagnostic.Kind diagnosticKind, String message) { 224 checkNotNull(diagnosticKind); 225 checkNotNull(message); 226 checkState(currentPluginName != null); 227 228 // Add a separator if this isn't the first message 229 if (mergedDiagnosticKind.isPresent()) { 230 messageBuilder.append("\n\n"); 231 } 232 233 mergeDiagnosticKind(diagnosticKind); 234 // Adds brackets as well as special color strings to make the string red and bold. 235 messageBuilder.append(String.format("\033[1;31m[%s]\033[0m ", currentPluginName)); 236 messageBuilder.append(message); 237 } 238 mergeDiagnosticKind(Diagnostic.Kind diagnosticKind)239 private void mergeDiagnosticKind(Diagnostic.Kind diagnosticKind) { 240 checkArgument(diagnosticKind != Diagnostic.Kind.MANDATORY_WARNING, 241 "Dagger plugins should not be issuing mandatory warnings"); 242 if (!mergedDiagnosticKind.isPresent()) { 243 mergedDiagnosticKind = Optional.of(diagnosticKind); 244 return; 245 } 246 Diagnostic.Kind current = mergedDiagnosticKind.get(); 247 if (current == Diagnostic.Kind.ERROR || diagnosticKind == Diagnostic.Kind.ERROR) { 248 mergedDiagnosticKind = Optional.of(Diagnostic.Kind.ERROR); 249 } else if (current == Diagnostic.Kind.WARNING || diagnosticKind == Diagnostic.Kind.WARNING) { 250 mergedDiagnosticKind = Optional.of(Diagnostic.Kind.WARNING); 251 } else if (current == Diagnostic.Kind.NOTE || diagnosticKind == Diagnostic.Kind.NOTE) { 252 mergedDiagnosticKind = Optional.of(Diagnostic.Kind.NOTE); 253 } else { 254 mergedDiagnosticKind = Optional.of(Diagnostic.Kind.OTHER); 255 } 256 } 257 } 258 } 259