• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.binding;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static dagger.internal.codegen.binding.SourceFiles.simpleVariableName;
22 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
23 import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
24 import static dagger.internal.codegen.xprocessing.XTypeElements.isNested;
25 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
26 
27 import androidx.room.compiler.processing.XElement;
28 import androidx.room.compiler.processing.XMethodElement;
29 import androidx.room.compiler.processing.XType;
30 import androidx.room.compiler.processing.XTypeElement;
31 import com.google.auto.value.AutoValue;
32 import com.squareup.javapoet.ParameterSpec;
33 import com.squareup.javapoet.TypeName;
34 import dagger.internal.codegen.javapoet.TypeNames;
35 import dagger.internal.codegen.model.BindingKind;
36 import dagger.internal.codegen.model.Key;
37 import dagger.internal.codegen.xprocessing.XTypeElements;
38 import java.util.Optional;
39 
40 /** A type that a component needs an instance of. */
41 @AutoValue
42 public abstract class ComponentRequirement {
43   /** The kind of the {@link ComponentRequirement}. */
44   public enum Kind {
45     /** A type listed in the component's {@code dependencies} attribute. */
46     DEPENDENCY,
47 
48     /** A type listed in the component or subcomponent's {@code modules} attribute. */
49     MODULE,
50 
51     /**
52      * An object that is passed to a builder's {@link dagger.BindsInstance @BindsInstance} method.
53      */
54     BOUND_INSTANCE,
55     ;
56 
isBoundInstance()57     public boolean isBoundInstance() {
58       return equals(BOUND_INSTANCE);
59     }
60 
isModule()61     public boolean isModule() {
62       return equals(MODULE);
63     }
64   }
65 
66   private XType type;
67 
68   /** The kind of requirement. */
kind()69   public abstract Kind kind();
70 
71   /** Returns true if this is a {@link Kind#BOUND_INSTANCE} requirement. */
72   // TODO(ronshapiro): consider removing this and inlining the usages
isBoundInstance()73   final boolean isBoundInstance() {
74     return kind().isBoundInstance();
75   }
76 
77   /** The type of the instance the component must have. */
typeName()78   abstract TypeName typeName();
79 
80   /** The type of the instance the component must have. */
type()81   public XType type() {
82     return type;
83   }
84 
85   /** The element associated with the type of this requirement. */
typeElement()86   public XTypeElement typeElement() {
87     return type.getTypeElement();
88   }
89 
90   /** The action a component builder should take if it {@code null} is passed. */
91   public enum NullPolicy {
92     /** Make a new instance. */
93     NEW,
94     /** Throw an exception. */
95     THROW,
96     /** Allow use of null values. */
97     ALLOW,
98   }
99 
100   /**
101    * An override for the requirement's null policy. If set, this is used as the null policy instead
102    * of the default behavior in {@link #nullPolicy}.
103    *
104    * <p>Some implementations' null policy can be determined upon construction (e.g., for binding
105    * instances), but others' require Elements which must wait until {@link #nullPolicy} is called.
106    */
overrideNullPolicy()107   abstract Optional<NullPolicy> overrideNullPolicy();
108 
109   /** The requirement's null policy. */
nullPolicy()110   public NullPolicy nullPolicy() {
111     if (overrideNullPolicy().isPresent()) {
112       return overrideNullPolicy().get();
113     }
114     switch (kind()) {
115       case MODULE:
116         return componentCanMakeNewInstances(typeElement())
117             ? NullPolicy.NEW
118             : requiresAPassedInstance() ? NullPolicy.THROW : NullPolicy.ALLOW;
119       case DEPENDENCY:
120       case BOUND_INSTANCE:
121         return NullPolicy.THROW;
122     }
123     throw new AssertionError();
124   }
125 
126   /**
127    * Returns true if the passed {@link ComponentRequirement} requires a passed instance in order to
128    * be used within a component.
129    */
requiresAPassedInstance()130   public boolean requiresAPassedInstance() {
131     if (!kind().isModule()) {
132       // Bound instances and dependencies always require the user to provide an instance.
133       return true;
134     }
135     return requiresModuleInstance() && !componentCanMakeNewInstances(typeElement());
136   }
137 
138   /**
139    * Returns {@code true} if an instance is needed for this (module) requirement.
140    *
141    * <p>An instance is only needed if there is a binding method on the module that is neither {@code
142    * abstract} nor {@code static}; if all bindings are one of those, then there should be no
143    * possible dependency on instance state in the module's bindings.
144    *
145    * <p>Alternatively, if the module is a Kotlin Object then the binding methods are considered
146    * {@code static}, requiring no module instance.
147    */
requiresModuleInstance()148   public boolean requiresModuleInstance() {
149     if (typeElement().isKotlinObject() || typeElement().isCompanionObject()) {
150       return false;
151     }
152     return XTypeElements.getAllNonPrivateInstanceMethods(typeElement()).stream()
153         .filter(this::isBindingMethod)
154         .anyMatch(method -> !method.isAbstract() && !method.isStatic());
155   }
156 
isBindingMethod(XMethodElement method)157   private boolean isBindingMethod(XMethodElement method) {
158     // TODO(cgdecker): At the very least, we should have utility methods to consolidate this stuff
159     // in one place; listing individual annotations all over the place is brittle.
160     return hasAnyAnnotation(
161         method,
162         TypeNames.PROVIDES,
163         TypeNames.PRODUCES,
164         // TODO(ronshapiro): it would be cool to have internal meta-annotations that could describe
165         // these, like @AbstractBindingMethod
166         TypeNames.BINDS,
167         TypeNames.MULTIBINDS,
168         TypeNames.BINDS_OPTIONAL_OF);
169   }
170 
171   /** The key for this requirement, if one is available. */
key()172   public abstract Optional<Key> key();
173 
174   /** Returns the name for this requirement that could be used as a variable. */
variableName()175   public abstract String variableName();
176 
177   /** Returns a parameter spec for this requirement. */
toParameterSpec()178   public ParameterSpec toParameterSpec() {
179     return ParameterSpec.builder(type().getTypeName(), variableName()).build();
180   }
181 
forDependency(XType type)182   public static ComponentRequirement forDependency(XType type) {
183     checkArgument(isDeclared(checkNotNull(type)));
184     return create(Kind.DEPENDENCY, type);
185   }
186 
forModule(XType type)187   public static ComponentRequirement forModule(XType type) {
188     checkArgument(isDeclared(checkNotNull(type)));
189     return create(Kind.MODULE, type);
190   }
191 
forBoundInstance(ContributionBinding binding)192   public static ComponentRequirement forBoundInstance(ContributionBinding binding) {
193     checkArgument(binding.kind().equals(BindingKind.BOUND_INSTANCE));
194     return forBoundInstance(binding.key(), binding.isNullable(), binding.bindingElement().get());
195   }
196 
forBoundInstance( Key key, boolean nullable, XElement elementForVariableName)197   static ComponentRequirement forBoundInstance(
198       Key key, boolean nullable, XElement elementForVariableName) {
199     return create(
200         Kind.BOUND_INSTANCE,
201         key.type().xprocessing(),
202         nullable ? Optional.of(NullPolicy.ALLOW) : Optional.empty(),
203         Optional.of(key),
204         getSimpleName(elementForVariableName));
205   }
206 
create(Kind kind, XType type)207   private static ComponentRequirement create(Kind kind, XType type) {
208     return create(
209         kind,
210         type,
211         Optional.empty(),
212         Optional.empty(),
213         simpleVariableName(type.getTypeElement().getClassName()));
214   }
215 
create( Kind kind, XType type, Optional<NullPolicy> overrideNullPolicy, Optional<Key> key, String variableName)216   private static ComponentRequirement create(
217       Kind kind,
218       XType type,
219       Optional<NullPolicy> overrideNullPolicy,
220       Optional<Key> key,
221       String variableName) {
222     ComponentRequirement requirement =
223         new AutoValue_ComponentRequirement(
224             kind, type.getTypeName(), overrideNullPolicy, key, variableName);
225     requirement.type = type;
226     return requirement;
227   }
228 
229   /**
230    * Returns true if and only if a component can instantiate new instances (typically of a module)
231    * rather than requiring that they be passed.
232    */
233   // TODO(bcorso): Should this method throw if its called knowing that an instance is not needed?
componentCanMakeNewInstances(XTypeElement typeElement)234   public static boolean componentCanMakeNewInstances(XTypeElement typeElement) {
235     // TODO(gak): still need checks for visibility
236     return typeElement.isClass()
237         && !typeElement.isAbstract()
238         && !requiresEnclosingInstance(typeElement)
239         && hasVisibleDefaultConstructor(typeElement);
240   }
241 
requiresEnclosingInstance(XTypeElement typeElement)242   private static boolean requiresEnclosingInstance(XTypeElement typeElement) {
243     return isNested(typeElement) && !typeElement.isStatic();
244   }
245 
hasVisibleDefaultConstructor(XTypeElement typeElement)246   private static boolean hasVisibleDefaultConstructor(XTypeElement typeElement) {
247     return typeElement.getConstructors().stream()
248         .anyMatch(constructor -> !constructor.isPrivate() && constructor.getParameters().isEmpty());
249   }
250 }
251