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