• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.base;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
21 import static dagger.internal.codegen.javapoet.TypeNames.lazyOf;
22 import static dagger.internal.codegen.javapoet.TypeNames.listenableFutureOf;
23 import static dagger.internal.codegen.javapoet.TypeNames.producedOf;
24 import static dagger.internal.codegen.javapoet.TypeNames.producerOf;
25 import static dagger.internal.codegen.javapoet.TypeNames.providerOf;
26 import static dagger.internal.codegen.model.RequestKind.LAZY;
27 import static dagger.internal.codegen.model.RequestKind.PRODUCED;
28 import static dagger.internal.codegen.model.RequestKind.PRODUCER;
29 import static dagger.internal.codegen.model.RequestKind.PROVIDER;
30 import static dagger.internal.codegen.xprocessing.XProcessingEnvs.wrapType;
31 import static dagger.internal.codegen.xprocessing.XTypes.checkTypePresent;
32 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
33 import static dagger.internal.codegen.xprocessing.XTypes.isTypeOf;
34 import static dagger.internal.codegen.xprocessing.XTypes.unwrapType;
35 
36 import androidx.room.compiler.processing.XProcessingEnv;
37 import androidx.room.compiler.processing.XType;
38 import com.google.common.collect.ImmutableMap;
39 import com.squareup.javapoet.ClassName;
40 import com.squareup.javapoet.TypeName;
41 import dagger.internal.codegen.javapoet.TypeNames;
42 import dagger.internal.codegen.model.Binding;
43 import dagger.internal.codegen.model.BindingGraph;
44 import dagger.internal.codegen.model.BindingGraph.ComponentNode;
45 import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
46 import dagger.internal.codegen.model.BindingGraph.Node;
47 import dagger.internal.codegen.model.RequestKind;
48 
49 /** Utility methods for {@link RequestKind}s. */
50 public final class RequestKinds {
51   /** Returns the type of a request of this kind for a key with a given type. */
requestType( RequestKind requestKind, XType type, XProcessingEnv processingEnv)52   public static XType requestType(
53       RequestKind requestKind, XType type, XProcessingEnv processingEnv) {
54     switch (requestKind) {
55       case INSTANCE:
56         return type;
57 
58       case PROVIDER_OF_LAZY:
59         return wrapType(TypeNames.PROVIDER, requestType(LAZY, type, processingEnv), processingEnv);
60 
61       case FUTURE:
62         return wrapType(TypeNames.LISTENABLE_FUTURE, type, processingEnv);
63 
64       default:
65         return wrapType(frameworkClassName(requestKind), type, processingEnv);
66     }
67   }
68 
69   /** Returns the type of a request of this kind for a key with a given type. */
requestTypeName(RequestKind requestKind, TypeName keyType)70   public static TypeName requestTypeName(RequestKind requestKind, TypeName keyType) {
71     switch (requestKind) {
72       case INSTANCE:
73         return keyType;
74 
75       case PROVIDER:
76         return providerOf(keyType);
77 
78       case LAZY:
79         return lazyOf(keyType);
80 
81       case PROVIDER_OF_LAZY:
82         return providerOf(lazyOf(keyType));
83 
84       case PRODUCER:
85         return producerOf(keyType);
86 
87       case PRODUCED:
88         return producedOf(keyType);
89 
90       case FUTURE:
91         return listenableFutureOf(keyType);
92 
93       default:
94         throw new AssertionError(requestKind);
95     }
96   }
97 
98   private static final ImmutableMap<RequestKind, ClassName> FRAMEWORK_CLASSES =
99       ImmutableMap.of(
100           // Default to the javax Provider since that is what is used for the binding graph
101           // representation.
102           PROVIDER, TypeNames.PROVIDER,
103           LAZY, TypeNames.LAZY,
104           PRODUCER, TypeNames.PRODUCER,
105           PRODUCED, TypeNames.PRODUCED);
106 
107   /** Returns the {@link RequestKind} that matches the wrapping types (if any) of {@code type}. */
getRequestKind(XType type)108   public static RequestKind getRequestKind(XType type) {
109     checkTypePresent(type);
110     if (!isDeclared(type) || type.getTypeArguments().isEmpty()) {
111       // If the type is not a declared type (i.e. class or interface) with type arguments, then we
112       // know it can't be a parameterized type of one of the framework classes, so return INSTANCE.
113       return RequestKind.INSTANCE;
114     }
115 
116     if ((isTypeOf(type, TypeNames.PROVIDER) || isTypeOf(type, TypeNames.JAKARTA_PROVIDER))
117         && isTypeOf(unwrapType(type), TypeNames.LAZY)) {
118       return RequestKind.PROVIDER_OF_LAZY;
119     }
120 
121     if (isTypeOf(type, TypeNames.JAKARTA_PROVIDER)) {
122       return RequestKind.PROVIDER;
123     }
124 
125     return FRAMEWORK_CLASSES.keySet().stream()
126         .filter(kind -> isTypeOf(type, FRAMEWORK_CLASSES.get(kind)))
127         .collect(toOptional())
128         .orElse(RequestKind.INSTANCE);
129   }
130 
131   /**
132    * Unwraps the framework class(es) of {@code requestKind} from {@code type}. If {@code
133    * requestKind} is {@link RequestKind#INSTANCE}, this acts as an identity function.
134    *
135    * @throws TypeNotPresentException if {@code type} is an {@link javax.lang.model.type.ErrorType},
136    *     which may mean that the type will be generated in a later round of processing
137    * @throws IllegalArgumentException if {@code type} is not wrapped with {@code requestKind}'s
138    *     framework class(es).
139    */
extractKeyType(XType type)140   public static XType extractKeyType(XType type) {
141     return extractKeyType(getRequestKind(type), type);
142   }
143 
extractKeyType(RequestKind requestKind, XType type)144   private static XType extractKeyType(RequestKind requestKind, XType type) {
145     switch (requestKind) {
146       case INSTANCE:
147         return type;
148       case PROVIDER_OF_LAZY:
149         return extractKeyType(LAZY, extractKeyType(PROVIDER, type));
150       default:
151         return unwrapType(type);
152     }
153   }
154 
155   /**
156    * A dagger- or {@code javax.inject}-defined class for {@code requestKind} that that can wrap
157    * another type but share the same {@link dagger.internal.codegen.model.Key}.
158    *
159    * <p>For example, {@code Provider<String>} and {@code Lazy<String>} can both be requested if a
160    * key exists for {@code String}; they all share the same key.
161    *
162    * <p>This concept is not well defined and should probably be removed and inlined into the cases
163    * that need it. For example, {@link RequestKind#PROVIDER_OF_LAZY} has <em>2</em> wrapping
164    * classes, and {@link RequestKind#FUTURE} is wrapped with a {@link ListenableFuture}, but for
165    * historical/implementation reasons has not had an associated framework class.
166    */
frameworkClassName(RequestKind requestKind)167   public static ClassName frameworkClassName(RequestKind requestKind) {
168     checkArgument(
169         FRAMEWORK_CLASSES.containsKey(requestKind), "no framework class for %s", requestKind);
170     return FRAMEWORK_CLASSES.get(requestKind);
171   }
172 
173   /**
174    * Returns {@code true} if requests for {@code requestKind} can be satisfied by a production
175    * binding.
176    */
canBeSatisfiedByProductionBinding( RequestKind requestKind, boolean isEntryPoint)177   public static boolean canBeSatisfiedByProductionBinding(
178       RequestKind requestKind, boolean isEntryPoint) {
179     switch (requestKind) {
180       case PROVIDER:
181       case LAZY:
182       case PROVIDER_OF_LAZY:
183       case MEMBERS_INJECTION:
184         return false;
185       case PRODUCED: // TODO(b/337087142) Requires implementation for entry point.
186       case INSTANCE:
187         return !isEntryPoint;
188       case PRODUCER:
189       case FUTURE:
190         return true;
191     }
192     throw new AssertionError();
193   }
194 
dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph)195   public static boolean dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph) {
196     Node source = graph.network().incidentNodes(edge).source();
197     boolean isEntryPoint = source instanceof ComponentNode;
198     boolean isValidRequest =
199         canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind(), isEntryPoint);
200     if (isEntryPoint) {
201       return isValidRequest;
202     }
203     if (source instanceof Binding) {
204       return isValidRequest && ((Binding) source).isProduction();
205     }
206     throw new IllegalArgumentException(
207         "expected a dagger.internal.codegen.model.Binding or ComponentNode: " + source);
208   }
209 
RequestKinds()210   private RequestKinds() {}
211 }
212