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 */
16package dagger.internal.codegen;
17
18import com.google.auto.common.MoreElements;
19import com.google.auto.common.MoreTypes;
20import com.google.auto.value.AutoValue;
21import com.google.common.base.Equivalence;
22import com.google.common.base.Function;
23import com.google.common.base.Joiner;
24import com.google.common.base.Optional;
25import com.google.common.base.Predicate;
26import com.google.common.base.Predicates;
27import com.google.common.collect.FluentIterable;
28import com.google.common.collect.ImmutableList;
29import com.google.common.collect.ImmutableListMultimap;
30import com.google.common.collect.ImmutableMap;
31import com.google.common.collect.ImmutableSet;
32import com.google.common.collect.ImmutableSetMultimap;
33import com.google.common.collect.Iterables;
34import com.google.common.collect.Maps;
35import com.google.common.collect.Multimap;
36import com.google.common.collect.Ordering;
37import com.google.common.collect.Sets;
38import dagger.Component;
39import dagger.Lazy;
40import dagger.MapKey;
41import dagger.internal.codegen.ComponentDescriptor.BuilderSpec;
42import dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor;
43import dagger.internal.codegen.ContributionBinding.ContributionType;
44import dagger.internal.codegen.writer.TypeNames;
45import java.util.ArrayDeque;
46import java.util.Arrays;
47import java.util.Collection;
48import java.util.Deque;
49import java.util.Formatter;
50import java.util.HashSet;
51import java.util.Iterator;
52import java.util.LinkedHashSet;
53import java.util.Map;
54import java.util.Set;
55import javax.inject.Provider;
56import javax.lang.model.element.AnnotationMirror;
57import javax.lang.model.element.Element;
58import javax.lang.model.element.ExecutableElement;
59import javax.lang.model.element.TypeElement;
60import javax.lang.model.type.ArrayType;
61import javax.lang.model.type.DeclaredType;
62import javax.lang.model.type.ExecutableType;
63import javax.lang.model.type.PrimitiveType;
64import javax.lang.model.type.TypeMirror;
65import javax.lang.model.util.SimpleTypeVisitor6;
66import javax.lang.model.util.Types;
67import javax.tools.Diagnostic;
68
69import static com.google.auto.common.MoreElements.getAnnotationMirror;
70import static com.google.auto.common.MoreTypes.asDeclared;
71import static com.google.auto.common.MoreTypes.asExecutable;
72import static com.google.auto.common.MoreTypes.asTypeElements;
73import static com.google.common.base.Predicates.equalTo;
74import static com.google.common.base.Predicates.in;
75import static com.google.common.base.Predicates.not;
76import static com.google.common.base.Verify.verify;
77import static com.google.common.collect.Iterables.all;
78import static com.google.common.collect.Iterables.any;
79import static com.google.common.collect.Iterables.getOnlyElement;
80import static com.google.common.collect.Iterables.indexOf;
81import static com.google.common.collect.Iterables.skip;
82import static com.google.common.collect.Maps.filterKeys;
83import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodDescriptor.isOfKind;
84import static dagger.internal.codegen.ComponentDescriptor.ComponentMethodKind.SUBCOMPONENT;
85import static dagger.internal.codegen.ConfigurationAnnotations.getComponentDependencies;
86import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByAnnotationType;
87import static dagger.internal.codegen.ContributionBinding.indexMapBindingsByMapKey;
88import static dagger.internal.codegen.ErrorMessages.DUPLICATE_SIZE_LIMIT;
89import static dagger.internal.codegen.ErrorMessages.INDENT;
90import static dagger.internal.codegen.ErrorMessages.MEMBERS_INJECTION_WITH_UNBOUNDED_TYPE;
91import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_FORMAT;
92import static dagger.internal.codegen.ErrorMessages.REQUIRES_AT_INJECT_CONSTRUCTOR_OR_PROVIDER_OR_PRODUCER_FORMAT;
93import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_FORMAT;
94import static dagger.internal.codegen.ErrorMessages.REQUIRES_PROVIDER_OR_PRODUCER_FORMAT;
95import static dagger.internal.codegen.ErrorMessages.duplicateMapKeysError;
96import static dagger.internal.codegen.ErrorMessages.inconsistentMapKeyAnnotationsError;
97import static dagger.internal.codegen.ErrorMessages.nullableToNonNullable;
98import static dagger.internal.codegen.ErrorMessages.stripCommonTypePrefixes;
99import static dagger.internal.codegen.Util.componentCanMakeNewInstances;
100import static dagger.internal.codegen.Util.getKeyTypeOfMap;
101import static dagger.internal.codegen.Util.getProvidedValueTypeOfMap;
102import static dagger.internal.codegen.Util.getValueTypeOfMap;
103import static dagger.internal.codegen.Util.isMapWithNonProvidedValues;
104import static dagger.internal.codegen.Util.isMapWithProvidedValues;
105import static javax.tools.Diagnostic.Kind.ERROR;
106import static javax.tools.Diagnostic.Kind.WARNING;
107
108public 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
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
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
150    Validation(BindingGraph topLevelGraph) {
151      this(topLevelGraph, topLevelGraph);
152    }
153
154    ValidationReport<TypeElement> buildReport() {
155      return reportBuilder.build();
156    }
157
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
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
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.
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     */
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. */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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.
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
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.
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.
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
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
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     */
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     */
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     */
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