1 /* 2 * Copyright (C) 2015 Google, Inc. 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 package dagger.internal.codegen; 17 18 import com.google.auto.common.MoreElements; 19 import com.google.auto.common.MoreTypes; 20 import com.google.auto.value.AutoValue; 21 import com.google.common.base.Equivalence; 22 import com.google.common.base.Function; 23 import com.google.common.base.Joiner; 24 import com.google.common.base.Optional; 25 import com.google.common.base.Predicate; 26 import com.google.common.base.Predicates; 27 import com.google.common.collect.FluentIterable; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableListMultimap; 30 import com.google.common.collect.ImmutableMap; 31 import com.google.common.collect.ImmutableSet; 32 import com.google.common.collect.ImmutableSetMultimap; 33 import com.google.common.collect.Iterables; 34 import com.google.common.collect.Maps; 35 import com.google.common.collect.Multimap; 36 import com.google.common.collect.Ordering; 37 import com.google.common.collect.Sets; 38 import dagger.Component; 39 import dagger.Lazy; 40 import dagger.MapKey; 41 import dagger.internal.codegen.ComponentDescriptor.BuilderSpec; 42 import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor; 43 import dagger.internal.codegen.ContributionBinding.ContributionType; 44 import dagger.internal.codegen.writer.TypeNames; 45 import java.util.ArrayDeque; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.Deque; 49 import java.util.Formatter; 50 import java.util.HashSet; 51 import java.util.Iterator; 52 import java.util.LinkedHashSet; 53 import java.util.Map; 54 import java.util.Set; 55 import javax.inject.Provider; 56 import javax.lang.model.element.AnnotationMirror; 57 import javax.lang.model.element.Element; 58 import javax.lang.model.element.ExecutableElement; 59 import javax.lang.model.element.TypeElement; 60 import javax.lang.model.type.ArrayType; 61 import javax.lang.model.type.DeclaredType; 62 import javax.lang.model.type.ExecutableType; 63 import javax.lang.model.type.PrimitiveType; 64 import javax.lang.model.type.TypeMirror; 65 import javax.lang.model.util.SimpleTypeVisitor6; 66 import javax.lang.model.util.Types; 67 import javax.tools.Diagnostic; 68 69 import static com.google.auto.common.MoreElements.getAnnotationMirror; 70 import static com.google.auto.common.MoreTypes.asDeclared; 71 import static com.google.auto.common.MoreTypes.asExecutable; 72 import static com.google.auto.common.MoreTypes.asTypeElements; 73 import static com.google.common.base.Predicates.equalTo; 74 import static com.google.common.base.Predicates.in; 75 import static com.google.common.base.Predicates.not; 76 import static com.google.common.base.Verify.verify; 77 import static com.google.common.collect.Iterables.all; 78 import static com.google.common.collect.Iterables.any; 79 import static com.google.common.collect.Iterables.getOnlyElement; 80 import static com.google.common.collect.Iterables.indexOf; 81 import static com.google.common.collect.Iterables.skip; 82 import static com.google.common.collect.Maps.filterKeys; 83 import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind; 84 import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT; 85 import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies; 86 import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByAnnotationType; 87 import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByMapKey; 88 import static dagger.internal.codegen.ErrorMessages.DUPLICATE_SIZE_LIMIT; 89 import static dagger.internal.codegen.ErrorMessages.INDENT; 90 import static dagger.internal.codegen.ErrorMessages.MEMBERS_INJECTION_WITH_UNBOUNDED_TYPE; 91 import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT; 92 import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT; 93 import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_FORMAT; 94 import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_OR_PRODUCER_FORMAT; 95 import static dagger.internal.codegen.ErrorMessages.duplicateMapKeysError; 96 import static dagger.internal.codegen.ErrorMessages.inconsistentMapKeyAnnotationsError; 97 import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable; 98 import static dagger.internal.codegen.ErrorMessages.stripCommonTypePrefixes; 99 import static dagger.internal.codegen.Util.componentCanMakeNewInstances; 100 import static dagger.internal.codegen.Util.getKeyTypeOfMap; 101 import static dagger.internal.codegen.Util.getProvidedValueTypeOfMap; 102 import static dagger.internal.codegen.Util.getValueTypeOfMap; 103 import static dagger.internal.codegen.Util.isMapWithNonProvidedValues; 104 import static dagger.internal.codegen.Util.isMapWithProvidedValues; 105 import static javax.tools.Diagnostic.Kind.ERROR; 106 import static javax.tools.Diagnostic.Kind.WARNING; 107 108 public class BindingGraphValidator { 109 110 private final Types types; 111 private final InjectBindingRegistry injectBindingRegistry; 112 private final ValidationType scopeCycleValidationType; 113 private final Diagnostic.Kind nullableValidationType; 114 private final ContributionBindingFormatter contributionBindingFormatter; 115 private final MethodSignatureFormatter methodSignatureFormatter; 116 private final DependencyRequestFormatter dependencyRequestFormatter; 117 private final KeyFormatter keyFormatter; 118 BindingGraphValidator( Types types, InjectBindingRegistry injectBindingRegistry, ValidationType scopeCycleValidationType, Diagnostic.Kind nullableValidationType, ContributionBindingFormatter contributionBindingFormatter, MethodSignatureFormatter methodSignatureFormatter, DependencyRequestFormatter dependencyRequestFormatter, KeyFormatter keyFormatter)119 BindingGraphValidator( 120 Types types, 121 InjectBindingRegistry injectBindingRegistry, 122 ValidationType scopeCycleValidationType, 123 Diagnostic.Kind nullableValidationType, 124 ContributionBindingFormatter contributionBindingFormatter, 125 MethodSignatureFormatter methodSignatureFormatter, 126 DependencyRequestFormatter dependencyRequestFormatter, 127 KeyFormatter keyFormatter) { 128 this.types = types; 129 this.injectBindingRegistry = injectBindingRegistry; 130 this.scopeCycleValidationType = scopeCycleValidationType; 131 this.nullableValidationType = nullableValidationType; 132 this.contributionBindingFormatter = contributionBindingFormatter; 133 this.methodSignatureFormatter = methodSignatureFormatter; 134 this.dependencyRequestFormatter = dependencyRequestFormatter; 135 this.keyFormatter = keyFormatter; 136 } 137 138 private class Validation { 139 final BindingGraph topLevelGraph; 140 final BindingGraph subject; 141 final ValidationReport.Builder<TypeElement> reportBuilder; 142 Validation(BindingGraph topLevelGraph, BindingGraph subject)143 Validation(BindingGraph topLevelGraph, BindingGraph subject) { 144 this.topLevelGraph = topLevelGraph; 145 this.subject = subject; 146 this.reportBuilder = 147 ValidationReport.about(subject.componentDescriptor().componentDefinitionType()); 148 } 149 Validation(BindingGraph topLevelGraph)150 Validation(BindingGraph topLevelGraph) { 151 this(topLevelGraph, topLevelGraph); 152 } 153 buildReport()154 ValidationReport<TypeElement> buildReport() { 155 return reportBuilder.build(); 156 } 157 validateSubgraph()158 void validateSubgraph() { 159 validateComponentScope(); 160 validateDependencyScopes(); 161 validateComponentHierarchy(); 162 validateBuilders(); 163 164 for (ComponentMethodDescriptor componentMethod : 165 subject.componentDescriptor().componentMethods()) { 166 Optional<DependencyRequest> entryPoint = componentMethod.dependencyRequest(); 167 if (entryPoint.isPresent()) { 168 traverseRequest( 169 entryPoint.get(), 170 new ArrayDeque<ResolvedRequest>(), 171 new LinkedHashSet<BindingKey>(), 172 subject, 173 new HashSet<DependencyRequest>()); 174 } 175 } 176 177 for (Map.Entry<ComponentMethodDescriptor, ComponentDescriptor> entry : 178 filterKeys(subject.componentDescriptor().subcomponents(), isOfKind(SUBCOMPONENT)) 179 .entrySet()) { 180 validateSubcomponentFactoryMethod( 181 entry.getKey().methodElement(), entry.getValue().componentDefinitionType()); 182 } 183 184 for (BindingGraph subgraph : subject.subgraphs().values()) { 185 Validation subgraphValidation = 186 new Validation(topLevelGraph, subgraph); 187 subgraphValidation.validateSubgraph(); 188 reportBuilder.addSubreport(subgraphValidation.buildReport()); 189 } 190 } 191 validateSubcomponentFactoryMethod( ExecutableElement factoryMethod, TypeElement subcomponentType)192 private void validateSubcomponentFactoryMethod( 193 ExecutableElement factoryMethod, TypeElement subcomponentType) { 194 BindingGraph subgraph = subject.subgraphs().get(factoryMethod); 195 FluentIterable<TypeElement> missingModules = 196 FluentIterable.from(subgraph.componentRequirements()) 197 .filter(not(in(subgraphFactoryMethodParameters(factoryMethod)))) 198 .filter( 199 new Predicate<TypeElement>() { 200 @Override 201 public boolean apply(TypeElement moduleType) { 202 return !componentCanMakeNewInstances(moduleType); 203 } 204 }); 205 if (!missingModules.isEmpty()) { 206 reportBuilder.addError( 207 String.format( 208 "%s requires modules which have no visible default constructors. " 209 + "Add the following modules as parameters to this method: %s", 210 subcomponentType.getQualifiedName(), 211 Joiner.on(", ").join(missingModules.toSet())), 212 factoryMethod); 213 } 214 } 215 subgraphFactoryMethodParameters( ExecutableElement factoryMethod)216 private ImmutableSet<TypeElement> subgraphFactoryMethodParameters( 217 ExecutableElement factoryMethod) { 218 DeclaredType componentType = 219 asDeclared(subject.componentDescriptor().componentDefinitionType().asType()); 220 ExecutableType factoryMethodType = 221 asExecutable(types.asMemberOf(componentType, factoryMethod)); 222 return asTypeElements(factoryMethodType.getParameterTypes()); 223 } 224 225 /** 226 * Traverse the resolved dependency requests, validating resolved bindings, and reporting any 227 * cycles found. 228 * 229 * @param request the current dependency request 230 * @param bindingPath the dependency request path from the parent of {@code request} at the head 231 * up to the root dependency request from the component method at the tail 232 * @param keysInPath the binding keys corresponding to the dependency requests in 233 * {@code bindingPath}, but in reverse order: the first element is the binding key from the 234 * component method 235 * @param resolvedRequests the requests that have already been resolved, so we can avoid 236 * traversing that part of the graph again 237 */ 238 // TODO(dpb): It might be simpler to invert bindingPath's order. traverseRequest( DependencyRequest request, Deque<ResolvedRequest> bindingPath, LinkedHashSet<BindingKey> keysInPath, BindingGraph graph, Set<DependencyRequest> resolvedRequests)239 private void traverseRequest( 240 DependencyRequest request, 241 Deque<ResolvedRequest> bindingPath, 242 LinkedHashSet<BindingKey> keysInPath, 243 BindingGraph graph, 244 Set<DependencyRequest> resolvedRequests) { 245 verify(bindingPath.size() == keysInPath.size(), 246 "mismatched path vs keys -- (%s vs %s)", bindingPath, keysInPath); 247 BindingKey requestKey = request.bindingKey(); 248 if (keysInPath.contains(requestKey)) { 249 reportCycle( 250 // Invert bindingPath to match keysInPath's order 251 ImmutableList.copyOf(bindingPath).reverse(), 252 request, 253 indexOf(keysInPath, equalTo(requestKey))); 254 return; 255 } 256 257 // If request has already been resolved, avoid re-traversing the binding path. 258 if (resolvedRequests.add(request)) { 259 ResolvedRequest resolvedRequest = ResolvedRequest.create(request, graph); 260 bindingPath.push(resolvedRequest); 261 keysInPath.add(requestKey); 262 validateResolvedBinding(bindingPath, resolvedRequest.binding()); 263 264 for (Binding binding : resolvedRequest.binding().bindings()) { 265 for (DependencyRequest nextRequest : binding.implicitDependencies()) { 266 traverseRequest(nextRequest, bindingPath, keysInPath, graph, resolvedRequests); 267 } 268 } 269 bindingPath.poll(); 270 keysInPath.remove(requestKey); 271 } 272 } 273 274 /** 275 * Validates that the set of bindings resolved is consistent with the type of the binding, and 276 * returns true if the bindings are valid. 277 */ validateResolvedBinding( Deque<ResolvedRequest> path, ResolvedBindings resolvedBinding)278 private boolean validateResolvedBinding( 279 Deque<ResolvedRequest> path, ResolvedBindings resolvedBinding) { 280 if (resolvedBinding.bindings().isEmpty()) { 281 reportMissingBinding(path); 282 return false; 283 } 284 285 switch (resolvedBinding.bindingKey().kind()) { 286 case CONTRIBUTION: 287 ImmutableSet<ContributionBinding> contributionBindings = 288 resolvedBinding.contributionBindings(); 289 if (any(contributionBindings, Binding.Type.MEMBERS_INJECTION)) { 290 throw new IllegalArgumentException( 291 "contribution binding keys should never have members injection bindings"); 292 } 293 if (!validateNullability(path.peek().request(), contributionBindings)) { 294 return false; 295 } 296 if (any(contributionBindings, Binding.Type.PRODUCTION) 297 && doesPathRequireProvisionOnly(path)) { 298 reportProviderMayNotDependOnProducer(path); 299 return false; 300 } 301 if (contributionBindings.size() <= 1) { 302 return true; 303 } 304 ImmutableListMultimap<ContributionType, ContributionBinding> contributionsByType = 305 ContributionBinding.contributionTypesFor(contributionBindings); 306 if (contributionsByType.keySet().size() > 1) { 307 reportMultipleBindingTypes(path); 308 return false; 309 } 310 switch (getOnlyElement(contributionsByType.keySet())) { 311 case UNIQUE: 312 reportDuplicateBindings(path); 313 return false; 314 case MAP: 315 boolean duplicateMapKeys = hasDuplicateMapKeys(path, contributionBindings); 316 boolean inconsistentMapKeyAnnotationTypes = 317 hasInconsistentMapKeyAnnotationTypes(path, contributionBindings); 318 return !duplicateMapKeys && !inconsistentMapKeyAnnotationTypes; 319 case SET: 320 break; 321 default: 322 throw new AssertionError(); 323 } 324 break; 325 case MEMBERS_INJECTION: 326 if (!all(resolvedBinding.bindings(), Binding.Type.MEMBERS_INJECTION)) { 327 throw new IllegalArgumentException( 328 "members injection binding keys should never have contribution bindings"); 329 } 330 if (resolvedBinding.bindings().size() > 1) { 331 reportDuplicateBindings(path); 332 return false; 333 } 334 return validateMembersInjectionBinding(getOnlyElement(resolvedBinding.bindings()), path); 335 default: 336 throw new AssertionError(); 337 } 338 return true; 339 } 340 341 /** Ensures that if the request isn't nullable, then each contribution is also not nullable. */ validateNullability( DependencyRequest request, Set<ContributionBinding> bindings)342 private boolean validateNullability( 343 DependencyRequest request, Set<ContributionBinding> bindings) { 344 if (request.isNullable()) { 345 return true; 346 } 347 348 // Note: the method signature will include the @Nullable in it! 349 /* TODO(sameb): Sometimes javac doesn't include the Element in its output. 350 * (Maybe this happens if the code was already compiled before this point?) 351 * ... we manually print out the request in that case, otherwise the error 352 * message is kind of useless. */ 353 String typeName = TypeNames.forTypeMirror(request.key().type()).toString(); 354 355 boolean valid = true; 356 for (ContributionBinding binding : bindings) { 357 if (binding.nullableType().isPresent()) { 358 reportBuilder.addItem( 359 nullableToNonNullable(typeName, contributionBindingFormatter.format(binding)) 360 + "\n at: " 361 + dependencyRequestFormatter.format(request), 362 nullableValidationType, 363 request.requestElement()); 364 valid = false; 365 } 366 } 367 return valid; 368 } 369 370 /** 371 * Returns {@code true} (and reports errors) if {@code mapBindings} has more than one binding 372 * for the same map key. 373 */ hasDuplicateMapKeys( Deque<ResolvedRequest> path, Set<ContributionBinding> mapBindings)374 private boolean hasDuplicateMapKeys( 375 Deque<ResolvedRequest> path, Set<ContributionBinding> mapBindings) { 376 boolean hasDuplicateMapKeys = false; 377 for (Collection<ContributionBinding> mapBindingsForMapKey : 378 indexMapBindingsByMapKey(mapBindings).asMap().values()) { 379 if (mapBindingsForMapKey.size() > 1) { 380 hasDuplicateMapKeys = true; 381 reportDuplicateMapKeys(path, mapBindingsForMapKey); 382 } 383 } 384 return hasDuplicateMapKeys; 385 } 386 387 /** 388 * Returns {@code true} (and reports errors) if {@code mapBindings} uses more than one 389 * {@link MapKey} annotation type. 390 */ hasInconsistentMapKeyAnnotationTypes( Deque<ResolvedRequest> path, Set<ContributionBinding> contributionBindings)391 private boolean hasInconsistentMapKeyAnnotationTypes( 392 Deque<ResolvedRequest> path, Set<ContributionBinding> contributionBindings) { 393 ImmutableSetMultimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> 394 mapBindingsByAnnotationType = indexMapBindingsByAnnotationType(contributionBindings); 395 if (mapBindingsByAnnotationType.keySet().size() > 1) { 396 reportInconsistentMapKeyAnnotations(path, mapBindingsByAnnotationType); 397 return true; 398 } 399 return false; 400 } 401 402 /** 403 * Validates a members injection binding, returning false (and reporting the error) if it wasn't 404 * valid. 405 */ validateMembersInjectionBinding( Binding binding, final Deque<ResolvedRequest> path)406 private boolean validateMembersInjectionBinding( 407 Binding binding, final Deque<ResolvedRequest> path) { 408 return binding 409 .key() 410 .type() 411 .accept( 412 new SimpleTypeVisitor6<Boolean, Void>() { 413 @Override 414 protected Boolean defaultAction(TypeMirror e, Void p) { 415 reportBuilder.addError( 416 "Invalid members injection request.", path.peek().request().requestElement()); 417 return false; 418 } 419 420 @Override 421 public Boolean visitDeclared(DeclaredType type, Void ignored) { 422 // If the key has type arguments, validate that each type argument is declared. 423 // Otherwise the type argument may be a wildcard (or other type), and we can't 424 // resolve that to actual types. If the arg was an array, validate the type 425 // of the array. 426 for (TypeMirror arg : type.getTypeArguments()) { 427 boolean declared; 428 switch (arg.getKind()) { 429 case ARRAY: 430 declared = 431 MoreTypes.asArray(arg) 432 .getComponentType() 433 .accept( 434 new SimpleTypeVisitor6<Boolean, Void>() { 435 @Override 436 protected Boolean defaultAction(TypeMirror e, Void p) { 437 return false; 438 } 439 440 @Override 441 public Boolean visitDeclared(DeclaredType t, Void p) { 442 for (TypeMirror arg : t.getTypeArguments()) { 443 if (!arg.accept(this, null)) { 444 return false; 445 } 446 } 447 return true; 448 } 449 450 @Override 451 public Boolean visitArray(ArrayType t, Void p) { 452 return t.getComponentType().accept(this, null); 453 } 454 455 @Override 456 public Boolean visitPrimitive(PrimitiveType t, Void p) { 457 return true; 458 } 459 }, 460 null); 461 break; 462 case DECLARED: 463 declared = true; 464 break; 465 default: 466 declared = false; 467 } 468 if (!declared) { 469 ImmutableList<String> printableDependencyPath = 470 FluentIterable.from(path) 471 .transform(REQUEST_FROM_RESOLVED_REQUEST) 472 .transform(dependencyRequestFormatter) 473 .filter(Predicates.not(Predicates.equalTo(""))) 474 .toList() 475 .reverse(); 476 reportBuilder.addError( 477 String.format( 478 MEMBERS_INJECTION_WITH_UNBOUNDED_TYPE, 479 arg.toString(), 480 type.toString(), 481 Joiner.on('\n').join(printableDependencyPath)), 482 path.peek().request().requestElement()); 483 return false; 484 } 485 } 486 487 TypeElement element = MoreElements.asType(type.asElement()); 488 // Also validate that the key is not the erasure of a generic type. 489 // If it is, that means the user referred to Foo<T> as just 'Foo', 490 // which we don't allow. (This is a judgement call -- we *could* 491 // allow it and instantiate the type bounds... but we don't.) 492 if (!MoreTypes.asDeclared(element.asType()).getTypeArguments().isEmpty() 493 && types.isSameType(types.erasure(element.asType()), type)) { 494 ImmutableList<String> printableDependencyPath = 495 FluentIterable.from(path) 496 .transform(REQUEST_FROM_RESOLVED_REQUEST) 497 .transform(dependencyRequestFormatter) 498 .filter(Predicates.not(Predicates.equalTo(""))) 499 .toList() 500 .reverse(); 501 reportBuilder.addError( 502 String.format( 503 ErrorMessages.MEMBERS_INJECTION_WITH_RAW_TYPE, 504 type.toString(), 505 Joiner.on('\n').join(printableDependencyPath)), 506 path.peek().request().requestElement()); 507 return false; 508 } 509 510 return true; // valid 511 } 512 }, 513 null); 514 } 515 516 /** 517 * Validates that component dependencies do not form a cycle. 518 */ validateComponentHierarchy()519 private void validateComponentHierarchy() { 520 ComponentDescriptor descriptor = subject.componentDescriptor(); 521 TypeElement componentType = descriptor.componentDefinitionType(); 522 validateComponentHierarchy(componentType, componentType, new ArrayDeque<TypeElement>()); 523 } 524 525 /** 526 * Recursive method to validate that component dependencies do not form a cycle. 527 */ validateComponentHierarchy( TypeElement rootComponent, TypeElement componentType, Deque<TypeElement> componentStack)528 private void validateComponentHierarchy( 529 TypeElement rootComponent, 530 TypeElement componentType, 531 Deque<TypeElement> componentStack) { 532 533 if (componentStack.contains(componentType)) { 534 // Current component has already appeared in the component chain. 535 StringBuilder message = new StringBuilder(); 536 message.append(rootComponent.getQualifiedName()); 537 message.append(" contains a cycle in its component dependencies:\n"); 538 componentStack.push(componentType); 539 appendIndentedComponentsList(message, componentStack); 540 componentStack.pop(); 541 reportBuilder.addItem(message.toString(), 542 scopeCycleValidationType.diagnosticKind().get(), 543 rootComponent, getAnnotationMirror(rootComponent, Component.class).get()); 544 } else { 545 Optional<AnnotationMirror> componentAnnotation = 546 getAnnotationMirror(componentType, Component.class); 547 if (componentAnnotation.isPresent()) { 548 componentStack.push(componentType); 549 550 ImmutableSet<TypeElement> dependencies = 551 MoreTypes.asTypeElements(getComponentDependencies(componentAnnotation.get())); 552 for (TypeElement dependency : dependencies) { 553 validateComponentHierarchy(rootComponent, dependency, componentStack); 554 } 555 556 componentStack.pop(); 557 } 558 } 559 } 560 561 /** 562 * Validates that among the dependencies are at most one scoped dependency, 563 * that there are no cycles within the scoping chain, and that singleton 564 * components have no scoped dependencies. 565 */ validateDependencyScopes()566 private void validateDependencyScopes() { 567 ComponentDescriptor descriptor = subject.componentDescriptor(); 568 Scope scope = descriptor.scope(); 569 ImmutableSet<TypeElement> scopedDependencies = scopedTypesIn(descriptor.dependencies()); 570 if (scope.isPresent()) { 571 // Dagger 1.x scope compatibility requires this be suppress-able. 572 if (scopeCycleValidationType.diagnosticKind().isPresent() 573 && scope.isSingleton()) { 574 // Singleton is a special-case representing the longest lifetime, and therefore 575 // @Singleton components may not depend on scoped components 576 if (!scopedDependencies.isEmpty()) { 577 StringBuilder message = new StringBuilder( 578 "This @Singleton component cannot depend on scoped components:\n"); 579 appendIndentedComponentsList(message, scopedDependencies); 580 reportBuilder.addItem(message.toString(), 581 scopeCycleValidationType.diagnosticKind().get(), 582 descriptor.componentDefinitionType(), 583 descriptor.componentAnnotation()); 584 } 585 } else if (scopedDependencies.size() > 1) { 586 // Scoped components may depend on at most one scoped component. 587 StringBuilder message = new StringBuilder(scope.getReadableSource()) 588 .append(' ') 589 .append(descriptor.componentDefinitionType().getQualifiedName()) 590 .append(" depends on more than one scoped component:\n"); 591 appendIndentedComponentsList(message, scopedDependencies); 592 reportBuilder.addError( 593 message.toString(), 594 descriptor.componentDefinitionType(), 595 descriptor.componentAnnotation()); 596 } else { 597 // Dagger 1.x scope compatibility requires this be suppress-able. 598 if (!scopeCycleValidationType.equals(ValidationType.NONE)) { 599 validateScopeHierarchy(descriptor.componentDefinitionType(), 600 descriptor.componentDefinitionType(), 601 new ArrayDeque<Scope>(), 602 new ArrayDeque<TypeElement>()); 603 } 604 } 605 } else { 606 // Scopeless components may not depend on scoped components. 607 if (!scopedDependencies.isEmpty()) { 608 StringBuilder message = 609 new StringBuilder(descriptor.componentDefinitionType().getQualifiedName()) 610 .append(" (unscoped) cannot depend on scoped components:\n"); 611 appendIndentedComponentsList(message, scopedDependencies); 612 reportBuilder.addError( 613 message.toString(), 614 descriptor.componentDefinitionType(), 615 descriptor.componentAnnotation()); 616 } 617 } 618 } 619 validateBuilders()620 private void validateBuilders() { 621 ComponentDescriptor componentDesc = subject.componentDescriptor(); 622 if (!componentDesc.builderSpec().isPresent()) { 623 // If no builder, nothing to validate. 624 return; 625 } 626 627 Set<TypeElement> availableDependencies = subject.availableDependencies(); 628 Set<TypeElement> requiredDependencies = 629 Sets.filter( 630 availableDependencies, 631 new Predicate<TypeElement>() { 632 @Override 633 public boolean apply(TypeElement input) { 634 return !Util.componentCanMakeNewInstances(input); 635 } 636 }); 637 final BuilderSpec spec = componentDesc.builderSpec().get(); 638 Map<TypeElement, ExecutableElement> allSetters = spec.methodMap(); 639 640 ErrorMessages.ComponentBuilderMessages msgs = 641 ErrorMessages.builderMsgsFor(subject.componentDescriptor().kind()); 642 Set<TypeElement> extraSetters = Sets.difference(allSetters.keySet(), availableDependencies); 643 if (!extraSetters.isEmpty()) { 644 Collection<ExecutableElement> excessMethods = 645 Maps.filterKeys(allSetters, Predicates.in(extraSetters)).values(); 646 Iterable<String> formatted = FluentIterable.from(excessMethods).transform( 647 new Function<ExecutableElement, String>() { 648 @Override public String apply(ExecutableElement input) { 649 return methodSignatureFormatter.format(input, 650 Optional.of(MoreTypes.asDeclared(spec.builderDefinitionType().asType()))); 651 }}); 652 reportBuilder.addError( 653 String.format(msgs.extraSetters(), formatted), spec.builderDefinitionType()); 654 } 655 656 Set<TypeElement> missingSetters = Sets.difference(requiredDependencies, allSetters.keySet()); 657 if (!missingSetters.isEmpty()) { 658 reportBuilder.addError( 659 String.format(msgs.missingSetters(), missingSetters), spec.builderDefinitionType()); 660 } 661 } 662 663 /** 664 * Validates that scopes do not participate in a scoping cycle - that is to say, scoped 665 * components are in a hierarchical relationship terminating with Singleton. 666 * 667 * <p>As a side-effect, this means scoped components cannot have a dependency cycle between 668 * themselves, since a component's presence within its own dependency path implies a cyclical 669 * relationship between scopes. However, cycles in component dependencies are explicitly 670 * checked in {@link #validateComponentHierarchy()}. 671 */ validateScopeHierarchy(TypeElement rootComponent, TypeElement componentType, Deque<Scope> scopeStack, Deque<TypeElement> scopedDependencyStack)672 private void validateScopeHierarchy(TypeElement rootComponent, 673 TypeElement componentType, 674 Deque<Scope> scopeStack, 675 Deque<TypeElement> scopedDependencyStack) { 676 Scope scope = Scope.scopeOf(componentType); 677 if (scope.isPresent()) { 678 if (scopeStack.contains(scope)) { 679 scopedDependencyStack.push(componentType); 680 // Current scope has already appeared in the component chain. 681 StringBuilder message = new StringBuilder(); 682 message.append(rootComponent.getQualifiedName()); 683 message.append(" depends on scoped components in a non-hierarchical scope ordering:\n"); 684 appendIndentedComponentsList(message, scopedDependencyStack); 685 if (scopeCycleValidationType.diagnosticKind().isPresent()) { 686 reportBuilder.addItem(message.toString(), 687 scopeCycleValidationType.diagnosticKind().get(), 688 rootComponent, getAnnotationMirror(rootComponent, Component.class).get()); 689 } 690 scopedDependencyStack.pop(); 691 } else { 692 Optional<AnnotationMirror> componentAnnotation = 693 getAnnotationMirror(componentType, Component.class); 694 if (componentAnnotation.isPresent()) { 695 ImmutableSet<TypeElement> scopedDependencies = scopedTypesIn( 696 MoreTypes.asTypeElements(getComponentDependencies(componentAnnotation.get()))); 697 if (scopedDependencies.size() == 1) { 698 // empty can be ignored (base-case), and > 1 is a different error reported separately. 699 scopeStack.push(scope); 700 scopedDependencyStack.push(componentType); 701 validateScopeHierarchy(rootComponent, getOnlyElement(scopedDependencies), 702 scopeStack, scopedDependencyStack); 703 scopedDependencyStack.pop(); 704 scopeStack.pop(); 705 } 706 } // else: we skip component dependencies which are not components 707 } 708 } 709 } 710 711 /** 712 * Validates that the scope (if any) of this component are compatible with the scopes of the 713 * bindings available in this component 714 */ validateComponentScope()715 void validateComponentScope() { 716 ImmutableMap<BindingKey, ResolvedBindings> resolvedBindings = subject.resolvedBindings(); 717 Scope componentScope = subject.componentDescriptor().scope(); 718 ImmutableSet.Builder<String> incompatiblyScopedMethodsBuilder = ImmutableSet.builder(); 719 for (ResolvedBindings bindings : resolvedBindings.values()) { 720 if (bindings.bindingKey().kind().equals(BindingKey.Kind.CONTRIBUTION)) { 721 for (ContributionBinding contributionBinding : bindings.ownedContributionBindings()) { 722 Scope bindingScope = contributionBinding.scope(); 723 if (bindingScope.isPresent() && !bindingScope.equals(componentScope)) { 724 // Scoped components cannot reference bindings to @Provides methods or @Inject 725 // types decorated by a different scope annotation. Unscoped components cannot 726 // reference to scoped @Provides methods or @Inject types decorated by any 727 // scope annotation. 728 switch (contributionBinding.bindingKind()) { 729 case PROVISION: 730 ExecutableElement provisionMethod = 731 MoreElements.asExecutable(contributionBinding.bindingElement()); 732 incompatiblyScopedMethodsBuilder.add( 733 methodSignatureFormatter.format(provisionMethod)); 734 break; 735 case INJECTION: 736 incompatiblyScopedMethodsBuilder.add( 737 bindingScope.getReadableSource() 738 + " class " 739 + contributionBinding.bindingTypeElement().getQualifiedName()); 740 break; 741 default: 742 throw new IllegalStateException(); 743 } 744 } 745 } 746 } 747 } 748 ImmutableSet<String> incompatiblyScopedMethods = incompatiblyScopedMethodsBuilder.build(); 749 if (!incompatiblyScopedMethods.isEmpty()) { 750 TypeElement componentType = subject.componentDescriptor().componentDefinitionType(); 751 StringBuilder message = new StringBuilder(componentType.getQualifiedName()); 752 if (componentScope.isPresent()) { 753 message.append(" scoped with "); 754 message.append(componentScope.getReadableSource()); 755 message.append(" may not reference bindings with different scopes:\n"); 756 } else { 757 message.append(" (unscoped) may not reference scoped bindings:\n"); 758 } 759 for (String method : incompatiblyScopedMethods) { 760 message.append(ErrorMessages.INDENT).append(method).append("\n"); 761 } 762 reportBuilder.addError( 763 message.toString(), componentType, subject.componentDescriptor().componentAnnotation()); 764 } 765 } 766 767 @SuppressWarnings("resource") // Appendable is a StringBuilder. reportProviderMayNotDependOnProducer(Deque<ResolvedRequest> path)768 private void reportProviderMayNotDependOnProducer(Deque<ResolvedRequest> path) { 769 StringBuilder errorMessage = new StringBuilder(); 770 if (path.size() == 1) { 771 new Formatter(errorMessage) 772 .format( 773 ErrorMessages.PROVIDER_ENTRY_POINT_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, 774 formatRootRequestKey(path)); 775 } else { 776 ImmutableSet<? extends Binding> dependentProvisions = 777 provisionsDependingOnLatestRequest(path); 778 // TODO(beder): Consider displaying all dependent provisions in the error message. If we do 779 // that, should we display all productions that depend on them also? 780 new Formatter(errorMessage).format(ErrorMessages.PROVIDER_MAY_NOT_DEPEND_ON_PRODUCER_FORMAT, 781 keyFormatter.format(dependentProvisions.iterator().next().key())); 782 } 783 reportBuilder.addError(errorMessage.toString(), path.getLast().request().requestElement()); 784 } 785 reportMissingBinding(Deque<ResolvedRequest> path)786 private void reportMissingBinding(Deque<ResolvedRequest> path) { 787 Key key = path.peek().request().key(); 788 BindingKey bindingKey = path.peek().request().bindingKey(); 789 boolean requiresContributionMethod = !key.isValidImplicitProvisionKey(types); 790 boolean requiresProvision = doesPathRequireProvisionOnly(path); 791 StringBuilder errorMessage = new StringBuilder(); 792 String requiresErrorMessageFormat = requiresContributionMethod 793 ? requiresProvision 794 ? REQUIRES_PROVIDER_FORMAT 795 : REQUIRES_PROVIDER_OR_PRODUCER_FORMAT 796 : requiresProvision 797 ? REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT 798 : REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT; 799 errorMessage.append(String.format(requiresErrorMessageFormat, keyFormatter.format(key))); 800 if (key.isValidMembersInjectionKey() 801 && !injectBindingRegistry.getOrFindMembersInjectionBinding(key).injectionSites() 802 .isEmpty()) { 803 errorMessage.append(" ").append(ErrorMessages.MEMBERS_INJECTION_DOES_NOT_IMPLY_PROVISION); 804 } 805 ImmutableList<String> printableDependencyPath = 806 FluentIterable.from(path) 807 .transform(REQUEST_FROM_RESOLVED_REQUEST) 808 .transform(dependencyRequestFormatter) 809 .filter(Predicates.not(Predicates.equalTo(""))) 810 .toList() 811 .reverse(); 812 for (String dependency : 813 printableDependencyPath.subList(1, printableDependencyPath.size())) { 814 errorMessage.append('\n').append(dependency); 815 } 816 for (String suggestion : MissingBindingSuggestions.forKey(topLevelGraph, bindingKey)) { 817 errorMessage.append('\n').append(suggestion); 818 } 819 reportBuilder.addError(errorMessage.toString(), path.getLast().request().requestElement()); 820 } 821 822 @SuppressWarnings("resource") // Appendable is a StringBuilder. reportDuplicateBindings(Deque<ResolvedRequest> path)823 private void reportDuplicateBindings(Deque<ResolvedRequest> path) { 824 ResolvedBindings resolvedBinding = path.peek().binding(); 825 StringBuilder builder = new StringBuilder(); 826 new Formatter(builder) 827 .format(ErrorMessages.DUPLICATE_BINDINGS_FOR_KEY_FORMAT, formatRootRequestKey(path)); 828 for (ContributionBinding binding : 829 Iterables.limit(resolvedBinding.contributionBindings(), DUPLICATE_SIZE_LIMIT)) { 830 builder.append('\n').append(INDENT).append(contributionBindingFormatter.format(binding)); 831 } 832 int numberOfOtherBindings = 833 resolvedBinding.contributionBindings().size() - DUPLICATE_SIZE_LIMIT; 834 if (numberOfOtherBindings > 0) { 835 builder.append('\n').append(INDENT) 836 .append("and ").append(numberOfOtherBindings).append(" other"); 837 } 838 if (numberOfOtherBindings > 1) { 839 builder.append('s'); 840 } 841 reportBuilder.addError(builder.toString(), path.getLast().request().requestElement()); 842 } 843 844 @SuppressWarnings("resource") // Appendable is a StringBuilder. reportMultipleBindingTypes(Deque<ResolvedRequest> path)845 private void reportMultipleBindingTypes(Deque<ResolvedRequest> path) { 846 ResolvedBindings resolvedBinding = path.peek().binding(); 847 StringBuilder builder = new StringBuilder(); 848 new Formatter(builder) 849 .format(ErrorMessages.MULTIPLE_BINDING_TYPES_FOR_KEY_FORMAT, formatRootRequestKey(path)); 850 ImmutableListMultimap<ContributionType, ContributionBinding> bindingsByType = 851 ContributionBinding.contributionTypesFor(resolvedBinding.contributionBindings()); 852 for (ContributionType type : 853 Ordering.natural().immutableSortedCopy(bindingsByType.keySet())) { 854 builder.append(INDENT); 855 builder.append(formatBindingType(type)); 856 builder.append(" bindings:\n"); 857 for (ContributionBinding binding : bindingsByType.get(type)) { 858 builder 859 .append(INDENT) 860 .append(INDENT) 861 .append(contributionBindingFormatter.format(binding)) 862 .append('\n'); 863 } 864 } 865 reportBuilder.addError(builder.toString(), path.getLast().request().requestElement()); 866 } 867 reportDuplicateMapKeys( Deque<ResolvedRequest> path, Collection<ContributionBinding> mapBindings)868 private void reportDuplicateMapKeys( 869 Deque<ResolvedRequest> path, Collection<ContributionBinding> mapBindings) { 870 StringBuilder builder = new StringBuilder(); 871 builder.append(duplicateMapKeysError(formatRootRequestKey(path))); 872 appendBindings(builder, mapBindings, 1); 873 reportBuilder.addError(builder.toString(), path.getLast().request().requestElement()); 874 } 875 reportInconsistentMapKeyAnnotations( Deque<ResolvedRequest> path, Multimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> mapBindingsByAnnotationType)876 private void reportInconsistentMapKeyAnnotations( 877 Deque<ResolvedRequest> path, 878 Multimap<Equivalence.Wrapper<DeclaredType>, ContributionBinding> 879 mapBindingsByAnnotationType) { 880 StringBuilder builder = 881 new StringBuilder(inconsistentMapKeyAnnotationsError(formatRootRequestKey(path))); 882 for (Map.Entry<Equivalence.Wrapper<DeclaredType>, Collection<ContributionBinding>> entry : 883 mapBindingsByAnnotationType.asMap().entrySet()) { 884 DeclaredType annotationType = entry.getKey().get(); 885 Collection<ContributionBinding> bindings = entry.getValue(); 886 887 builder 888 .append('\n') 889 .append(INDENT) 890 .append(annotationType) 891 .append(':'); 892 893 appendBindings(builder, bindings, 2); 894 } 895 reportBuilder.addError(builder.toString(), path.getLast().request().requestElement()); 896 } 897 898 /** 899 * Reports a cycle in the binding path. 900 * 901 * @param bindingPath the binding path, starting with the component provision dependency, and 902 * ending with the binding that depends on {@code request} 903 * @param request the request that would have been added to the binding path if its 904 * {@linkplain DependencyRequest#bindingKey() binding key} wasn't already in it 905 * @param indexOfDuplicatedKey the index of the dependency request in {@code bindingPath} whose 906 * {@linkplain DependencyRequest#bindingKey() binding key} matches {@code request}'s 907 */ reportCycle( Iterable<ResolvedRequest> bindingPath, DependencyRequest request, int indexOfDuplicatedKey)908 private void reportCycle( 909 Iterable<ResolvedRequest> bindingPath, 910 DependencyRequest request, 911 int indexOfDuplicatedKey) { 912 ImmutableList<DependencyRequest> requestPath = 913 FluentIterable.from(bindingPath) 914 .transform(REQUEST_FROM_RESOLVED_REQUEST) 915 .append(request) 916 .toList(); 917 Element rootRequestElement = requestPath.get(0).requestElement(); 918 ImmutableList<DependencyRequest> cycle = 919 requestPath.subList(indexOfDuplicatedKey, requestPath.size()); 920 Diagnostic.Kind kind = cycleHasProviderOrLazy(cycle) ? WARNING : ERROR; 921 if (kind == WARNING 922 && (suppressCycleWarnings(rootRequestElement) 923 || suppressCycleWarnings(rootRequestElement.getEnclosingElement()) 924 || suppressCycleWarnings(cycle))) { 925 return; 926 } 927 // TODO(cgruber): Provide a hint for the start and end of the cycle. 928 TypeElement componentType = MoreElements.asType(rootRequestElement.getEnclosingElement()); 929 reportBuilder.addItem( 930 String.format( 931 ErrorMessages.CONTAINS_DEPENDENCY_CYCLE_FORMAT, 932 componentType.getQualifiedName(), 933 rootRequestElement.getSimpleName(), 934 Joiner.on("\n") 935 .join( 936 FluentIterable.from(requestPath) 937 .transform(dependencyRequestFormatter) 938 .filter(not(equalTo(""))) 939 .skip(1))), 940 kind, 941 rootRequestElement); 942 } 943 944 /** 945 * Returns {@code true} if any step of a dependency cycle after the first is a {@link Provider} 946 * or {@link Lazy} or a {@code Map<K, Provider<V>>}. 947 * 948 * <p>If an implicit {@link Provider} dependency on {@code Map<K, Provider<V>>} is immediately 949 * preceded by a dependency on {@code Map<K, V>}, which means that the map's {@link Provider}s' 950 * {@link Provider#get() get()} methods are called during provision and so the cycle is not 951 * really broken. 952 */ cycleHasProviderOrLazy(ImmutableList<DependencyRequest> cycle)953 private boolean cycleHasProviderOrLazy(ImmutableList<DependencyRequest> cycle) { 954 DependencyRequest lastDependencyRequest = cycle.get(0); 955 for (DependencyRequest dependencyRequest : skip(cycle, 1)) { 956 switch (dependencyRequest.kind()) { 957 case PROVIDER: 958 if (!isImplicitProviderMapForValueMap(dependencyRequest, lastDependencyRequest)) { 959 return true; 960 } 961 break; 962 963 case LAZY: 964 return true; 965 966 case INSTANCE: 967 if (isMapWithProvidedValues(dependencyRequest.key().type())) { 968 return true; 969 } else { 970 break; 971 } 972 973 default: 974 break; 975 } 976 lastDependencyRequest = dependencyRequest; 977 } 978 return false; 979 } 980 981 /** 982 * Returns {@code true} if {@code maybeValueMapRequest}'s key type is {@code Map<K, V>} and 983 * {@code maybeProviderMapRequest}'s key type is {@code Map<K, Provider<V>>}, and both keys have 984 * the same qualifier. 985 */ isImplicitProviderMapForValueMap( DependencyRequest maybeProviderMapRequest, DependencyRequest maybeValueMapRequest)986 private boolean isImplicitProviderMapForValueMap( 987 DependencyRequest maybeProviderMapRequest, DependencyRequest maybeValueMapRequest) { 988 TypeMirror maybeProviderMapRequestType = maybeProviderMapRequest.key().type(); 989 TypeMirror maybeValueMapRequestType = maybeValueMapRequest.key().type(); 990 return maybeProviderMapRequest 991 .key() 992 .wrappedQualifier() 993 .equals(maybeValueMapRequest.key().wrappedQualifier()) 994 && isMapWithProvidedValues(maybeProviderMapRequestType) 995 && isMapWithNonProvidedValues(maybeValueMapRequestType) 996 && types.isSameType( 997 getKeyTypeOfMap(asDeclared(maybeProviderMapRequestType)), 998 getKeyTypeOfMap(asDeclared(maybeValueMapRequestType))) 999 && types.isSameType( 1000 getProvidedValueTypeOfMap(asDeclared(maybeProviderMapRequestType)), 1001 getValueTypeOfMap(asDeclared(maybeValueMapRequestType))); 1002 } 1003 } 1004 1005 private boolean suppressCycleWarnings(Element requestElement) { 1006 SuppressWarnings suppressions = requestElement.getAnnotation(SuppressWarnings.class); 1007 return suppressions != null && Arrays.asList(suppressions.value()).contains("dependency-cycle"); 1008 } 1009 1010 private boolean suppressCycleWarnings(ImmutableList<DependencyRequest> pathElements) { 1011 for (DependencyRequest dependencyRequest : pathElements) { 1012 if (suppressCycleWarnings(dependencyRequest.requestElement())) { 1013 return true; 1014 } 1015 } 1016 return false; 1017 } 1018 1019 ValidationReport<TypeElement> validate(BindingGraph subject) { 1020 Validation validation = new Validation(subject); 1021 validation.validateSubgraph(); 1022 return validation.buildReport(); 1023 } 1024 1025 /** 1026 * Append and format a list of indented component types (with their scope annotations) 1027 */ 1028 private void appendIndentedComponentsList(StringBuilder message, Iterable<TypeElement> types) { 1029 for (TypeElement scopedComponent : types) { 1030 message.append(INDENT); 1031 Scope scope = Scope.scopeOf(scopedComponent); 1032 if (scope.isPresent()) { 1033 message.append(scope.getReadableSource()).append(' '); 1034 } 1035 message.append(stripCommonTypePrefixes(scopedComponent.getQualifiedName().toString())) 1036 .append('\n'); 1037 } 1038 } 1039 1040 /** 1041 * Returns a set of type elements containing only those found in the input set that have 1042 * a scoping annotation. 1043 */ 1044 private ImmutableSet<TypeElement> scopedTypesIn(Set<TypeElement> types) { 1045 return FluentIterable.from(types).filter(new Predicate<TypeElement>() { 1046 @Override public boolean apply(TypeElement input) { 1047 return Scope.scopeOf(input).isPresent(); 1048 } 1049 }).toSet(); 1050 } 1051 1052 /** 1053 * Returns whether the given dependency path would require the most recent request to be resolved 1054 * by only provision bindings. 1055 */ 1056 private boolean doesPathRequireProvisionOnly(Deque<ResolvedRequest> path) { 1057 if (path.size() == 1) { 1058 // if this is an entry-point, then we check the request 1059 switch (path.peek().request().kind()) { 1060 case INSTANCE: 1061 case PROVIDER: 1062 case LAZY: 1063 case MEMBERS_INJECTOR: 1064 return true; 1065 case PRODUCER: 1066 case PRODUCED: 1067 case FUTURE: 1068 return false; 1069 default: 1070 throw new AssertionError(); 1071 } 1072 } 1073 // otherwise, the second-most-recent bindings determine whether the most recent one must be a 1074 // provision 1075 return !provisionsDependingOnLatestRequest(path).isEmpty(); 1076 } 1077 1078 /** 1079 * Returns any provision bindings resolved for the second-most-recent request in the given path; 1080 * that is, returns those provision bindings that depend on the latest request in the path. 1081 */ 1082 private ImmutableSet<? extends Binding> provisionsDependingOnLatestRequest( 1083 Deque<ResolvedRequest> path) { 1084 Iterator<ResolvedRequest> iterator = path.iterator(); 1085 final DependencyRequest request = iterator.next().request(); 1086 ResolvedRequest previousResolvedRequest = iterator.next(); 1087 return FluentIterable.from(previousResolvedRequest.binding().bindings()) 1088 .filter(Binding.Type.PROVISION) 1089 .filter( 1090 new Predicate<Binding>() { 1091 @Override 1092 public boolean apply(Binding binding) { 1093 return binding.implicitDependencies().contains(request); 1094 } 1095 }) 1096 .toSet(); 1097 } 1098 1099 private String formatBindingType(ContributionType type) { 1100 switch (type) { 1101 case MAP: 1102 return "Map"; 1103 case SET: 1104 return "Set"; 1105 case UNIQUE: 1106 return "Unique"; 1107 default: 1108 throw new IllegalStateException("Unknown binding type: " + type); 1109 } 1110 } 1111 1112 private String formatRootRequestKey(Deque<ResolvedRequest> path) { 1113 return keyFormatter.format(path.peek().request().key()); 1114 } 1115 1116 private void appendBindings( 1117 StringBuilder builder, Collection<ContributionBinding> bindings, int indentLevel) { 1118 for (ContributionBinding binding : Iterables.limit(bindings, DUPLICATE_SIZE_LIMIT)) { 1119 builder.append('\n'); 1120 for (int i = 0; i < indentLevel; i++) { 1121 builder.append(INDENT); 1122 } 1123 builder.append(contributionBindingFormatter.format(binding)); 1124 } 1125 int numberOfOtherBindings = bindings.size() - DUPLICATE_SIZE_LIMIT; 1126 if (numberOfOtherBindings > 0) { 1127 builder.append('\n'); 1128 for (int i = 0; i < indentLevel; i++) { 1129 builder.append(INDENT); 1130 } 1131 builder.append("and ").append(numberOfOtherBindings).append(" other"); 1132 } 1133 if (numberOfOtherBindings > 1) { 1134 builder.append('s'); 1135 } 1136 } 1137 1138 @AutoValue 1139 abstract static class ResolvedRequest { 1140 abstract DependencyRequest request(); 1141 abstract ResolvedBindings binding(); 1142 1143 static ResolvedRequest create(DependencyRequest request, BindingGraph graph) { 1144 BindingKey bindingKey = request.bindingKey(); 1145 ResolvedBindings resolvedBindings = graph.resolvedBindings().get(bindingKey); 1146 return new AutoValue_BindingGraphValidator_ResolvedRequest( 1147 request, 1148 resolvedBindings == null 1149 ? ResolvedBindings.noBindings(bindingKey, graph.componentDescriptor()) 1150 : resolvedBindings); 1151 } 1152 } 1153 1154 private static final Function<ResolvedRequest, DependencyRequest> REQUEST_FROM_RESOLVED_REQUEST = 1155 new Function<ResolvedRequest, DependencyRequest>() { 1156 @Override public DependencyRequest apply(ResolvedRequest resolvedRequest) { 1157 return resolvedRequest.request(); 1158 } 1159 }; 1160 } 1161