• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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;
18 
19 import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations;
20 import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
21 import static com.google.common.base.Preconditions.checkArgument;
22 import static com.google.common.collect.Iterables.getOnlyElement;
23 import static com.squareup.javapoet.MethodSpec.methodBuilder;
24 import static dagger.internal.codegen.MapKeyAccessibility.isMapKeyPubliclyAccessible;
25 import static dagger.internal.codegen.SourceFiles.elementBasedClassName;
26 import static javax.lang.model.element.Modifier.PUBLIC;
27 import static javax.lang.model.element.Modifier.STATIC;
28 import static javax.lang.model.util.ElementFilter.methodsIn;
29 
30 import com.google.auto.common.MoreElements;
31 import com.google.auto.common.MoreTypes;
32 import com.google.common.collect.ImmutableSet;
33 import com.squareup.javapoet.ClassName;
34 import com.squareup.javapoet.CodeBlock;
35 import com.squareup.javapoet.MethodSpec;
36 import com.squareup.javapoet.TypeName;
37 import dagger.MapKey;
38 import dagger.internal.codegen.langmodel.DaggerElements;
39 import dagger.internal.codegen.langmodel.DaggerTypes;
40 import java.util.NoSuchElementException;
41 import java.util.Optional;
42 import javax.lang.model.element.AnnotationMirror;
43 import javax.lang.model.element.AnnotationValue;
44 import javax.lang.model.element.Element;
45 import javax.lang.model.element.ElementKind;
46 import javax.lang.model.element.ExecutableElement;
47 import javax.lang.model.element.TypeElement;
48 import javax.lang.model.type.ArrayType;
49 import javax.lang.model.type.DeclaredType;
50 import javax.lang.model.type.PrimitiveType;
51 import javax.lang.model.type.TypeMirror;
52 import javax.lang.model.util.SimpleTypeVisitor6;
53 
54 /**
55  * Methods for extracting {@link MapKey} annotations and key code blocks from binding elements.
56  */
57 final class MapKeys {
58 
59   /**
60    * If {@code bindingElement} is annotated with a {@link MapKey} annotation, returns it.
61    *
62    * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey}
63    *     annotation
64    */
getMapKey(Element bindingElement)65   static Optional<AnnotationMirror> getMapKey(Element bindingElement) {
66     ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(bindingElement);
67     return mapKeys.isEmpty()
68         ? Optional.empty()
69         : Optional.<AnnotationMirror>of(getOnlyElement(mapKeys));
70   }
71 
72   /**
73    * Returns all of the {@link MapKey} annotations that annotate {@code bindingElement}.
74    */
getMapKeys(Element bindingElement)75   static ImmutableSet<? extends AnnotationMirror> getMapKeys(Element bindingElement) {
76     return getAnnotatedAnnotations(bindingElement, MapKey.class);
77   }
78 
79   /**
80    * Returns the annotation value if {@code mapKey}'s type is annotated with
81    * {@link MapKey @MapKey(unwrapValue = true)}.
82    *
83    * @throws IllegalArgumentException if {@code mapKey}'s type is not annotated with
84    *     {@link MapKey @MapKey} at all.
85    */
unwrapValue(AnnotationMirror mapKey)86   static Optional<? extends AnnotationValue> unwrapValue(AnnotationMirror mapKey) {
87     MapKey mapKeyAnnotation = mapKey.getAnnotationType().asElement().getAnnotation(MapKey.class);
88     checkArgument(
89         mapKeyAnnotation != null, "%s is not annotated with @MapKey", mapKey.getAnnotationType());
90     return mapKeyAnnotation.unwrapValue()
91         ? Optional.of(getOnlyElement(getAnnotationValuesWithDefaults(mapKey).values()))
92         : Optional.empty();
93   }
94 
mapKeyType(AnnotationMirror mapKeyAnnotation, DaggerTypes types)95   static TypeMirror mapKeyType(AnnotationMirror mapKeyAnnotation, DaggerTypes types) {
96     return unwrapValue(mapKeyAnnotation).isPresent()
97         ? getUnwrappedMapKeyType(mapKeyAnnotation.getAnnotationType(), types)
98         : mapKeyAnnotation.getAnnotationType();
99   }
100 
101   /**
102    * Returns the map key type for an unwrapped {@link MapKey} annotation type. If the single member
103    * type is primitive, returns the boxed type.
104    *
105    * @throws IllegalArgumentException if {@code mapKeyAnnotationType} is not an annotation type or
106    *     has more than one member, or if its single member is an array
107    * @throws NoSuchElementException if the annotation has no members
108    */
getUnwrappedMapKeyType( final DeclaredType mapKeyAnnotationType, final DaggerTypes types)109   static DeclaredType getUnwrappedMapKeyType(
110       final DeclaredType mapKeyAnnotationType, final DaggerTypes types) {
111     checkArgument(
112         MoreTypes.asTypeElement(mapKeyAnnotationType).getKind() == ElementKind.ANNOTATION_TYPE,
113         "%s is not an annotation type",
114         mapKeyAnnotationType);
115 
116     final ExecutableElement onlyElement =
117         getOnlyElement(methodsIn(mapKeyAnnotationType.asElement().getEnclosedElements()));
118 
119     SimpleTypeVisitor6<DeclaredType, Void> keyTypeElementVisitor =
120         new SimpleTypeVisitor6<DeclaredType, Void>() {
121 
122           @Override
123           public DeclaredType visitArray(ArrayType t, Void p) {
124             throw new IllegalArgumentException(
125                 mapKeyAnnotationType + "." + onlyElement.getSimpleName() + " cannot be an array");
126           }
127 
128           @Override
129           public DeclaredType visitPrimitive(PrimitiveType t, Void p) {
130             return MoreTypes.asDeclared(types.boxedClass(t).asType());
131           }
132 
133           @Override
134           public DeclaredType visitDeclared(DeclaredType t, Void p) {
135             return t;
136           }
137         };
138     return keyTypeElementVisitor.visit(onlyElement.getReturnType());
139   }
140 
141   /**
142    * Returns a code block for {@code binding}'s {@link ContributionBinding#mapKeyAnnotation() map
143    * key}. If for whatever reason the map key is not accessible from within {@code requestingClass}
144    * (i.e. it has a package-private {@code enum} from a different package), this will return an
145    * invocation of a proxy-method giving it access.
146    *
147    * @throws IllegalStateException if {@code binding} is not a {@link dagger.multibindings.IntoMap
148    *     map} contribution.
149    */
getMapKeyExpression( ContributionBinding binding, ClassName requestingClass, DaggerElements elements)150   static CodeBlock getMapKeyExpression(
151       ContributionBinding binding, ClassName requestingClass, DaggerElements elements) {
152     AnnotationMirror mapKeyAnnotation = binding.mapKeyAnnotation().get();
153     return MapKeyAccessibility.isMapKeyAccessibleFrom(
154             mapKeyAnnotation, requestingClass.packageName())
155         ? directMapKeyExpression(mapKeyAnnotation, elements)
156         : CodeBlock.of("$T.create()", mapKeyProxyClassName(binding));
157   }
158 
159   /**
160    * Returns a code block for the map key annotation {@code mapKey}.
161    *
162    * <p>This method assumes the map key will be accessible in the context that the returned {@link
163    * CodeBlock} is used. Use {@link #getMapKeyExpression(ContributionBinding, ClassName,
164    * DaggerElements)} when that assumption is not guaranteed.
165    *
166    * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey}
167    *     annotation
168    * @throws IllegalStateException if {@code bindingElement} is not annotated with a {@code MapKey}
169    *     annotation
170    */
directMapKeyExpression( AnnotationMirror mapKey, DaggerElements elements)171   private static CodeBlock directMapKeyExpression(
172       AnnotationMirror mapKey, DaggerElements elements) {
173     Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey);
174     AnnotationExpression annotationExpression = new AnnotationExpression(mapKey);
175 
176     if (MoreTypes.asTypeElement(mapKey.getAnnotationType())
177         .getQualifiedName()
178         .contentEquals("dagger.android.AndroidInjectionKey")) {
179       TypeElement unwrappedType =
180           elements.checkTypePresent((String) unwrappedValue.get().getValue());
181       return CodeBlock.of(
182           "$T.of($S)",
183           ClassName.get("dagger.android.internal", "AndroidInjectionKeys"),
184           ClassName.get(unwrappedType).reflectionName());
185     }
186 
187     if (unwrappedValue.isPresent()) {
188       TypeMirror unwrappedValueType =
189           getOnlyElement(getAnnotationValuesWithDefaults(mapKey).keySet()).getReturnType();
190       return annotationExpression.getValueExpression(unwrappedValueType, unwrappedValue.get());
191     } else {
192       return annotationExpression.getAnnotationInstanceExpression();
193     }
194   }
195 
196   /**
197    * Returns the {@link ClassName} in which {@link #mapKeyFactoryMethod(ContributionBinding,
198    * DaggerTypes, DaggerElements)} is generated.
199    */
mapKeyProxyClassName(ContributionBinding binding)200   static ClassName mapKeyProxyClassName(ContributionBinding binding) {
201     return elementBasedClassName(
202         MoreElements.asExecutable(binding.bindingElement().get()), "MapKey");
203   }
204 
205   /**
206    * A {@code static create()} method to be added to {@link
207    * #mapKeyProxyClassName(ContributionBinding)} when the {@code @MapKey} annotation is not publicly
208    * accessible.
209    */
mapKeyFactoryMethod( ContributionBinding binding, DaggerTypes types, DaggerElements elements)210   static Optional<MethodSpec> mapKeyFactoryMethod(
211       ContributionBinding binding, DaggerTypes types, DaggerElements elements) {
212     return binding
213         .mapKeyAnnotation()
214         .filter(mapKey -> !isMapKeyPubliclyAccessible(mapKey))
215         .map(
216             mapKey ->
217                 methodBuilder("create")
218                     .addModifiers(PUBLIC, STATIC)
219                     .returns(TypeName.get(mapKeyType(mapKey, types)))
220                     .addStatement("return $L", directMapKeyExpression(mapKey, elements))
221                     .build());
222   }
223 
MapKeys()224   private MapKeys() {}
225 }
226