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  */
16 package dagger.internal.codegen;
17 
18 import com.google.auto.common.MoreTypes;
19 import com.google.common.base.Function;
20 import com.google.common.base.Optional;
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableSet;
23 import dagger.MapKey;
24 import dagger.internal.codegen.writer.ClassName;
25 import dagger.internal.codegen.writer.Snippet;
26 import dagger.internal.codegen.writer.TypeName;
27 import dagger.internal.codegen.writer.TypeNames;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.NoSuchElementException;
31 import javax.lang.model.element.AnnotationMirror;
32 import javax.lang.model.element.AnnotationValue;
33 import javax.lang.model.element.Element;
34 import javax.lang.model.element.ElementKind;
35 import javax.lang.model.element.ExecutableElement;
36 import javax.lang.model.element.TypeElement;
37 import javax.lang.model.element.VariableElement;
38 import javax.lang.model.type.ArrayType;
39 import javax.lang.model.type.DeclaredType;
40 import javax.lang.model.type.PrimitiveType;
41 import javax.lang.model.type.TypeMirror;
42 import javax.lang.model.util.SimpleAnnotationValueVisitor6;
43 import javax.lang.model.util.SimpleTypeVisitor6;
44 import javax.lang.model.util.Types;
45 
46 import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations;
47 import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults;
48 import static com.google.common.base.Preconditions.checkArgument;
49 import static com.google.common.collect.Iterables.getOnlyElement;
50 import static com.google.common.collect.Iterables.transform;
51 import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet;
52 import static javax.lang.model.util.ElementFilter.methodsIn;
53 
54 /**
55  * Methods for extracting {@link MapKey} annotations and key snippets 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<? extends AnnotationMirror> getMapKey(Element bindingElement) {
66     ImmutableSet<? extends AnnotationMirror> mapKeys = getMapKeys(bindingElement);
67     return mapKeys.isEmpty()
68         ? Optional.<AnnotationMirror>absent()
69         : Optional.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(mapKey.getElementValues().values()))
92         : Optional.<AnnotationValue>absent();
93   }
94 
95   /**
96    * Returns the map key type for an unwrapped {@link MapKey} annotation type. If the single member
97    * type is primitive, returns the boxed type.
98    *
99    * @throws IllegalArgumentException if {@code mapKeyAnnotationType} is not an annotation type or
100    *     has more than one member, or if its single member is an array
101    * @throws NoSuchElementException if the annotation has no members
102    */
getUnwrappedMapKeyType( final DeclaredType mapKeyAnnotationType, final Types types)103   public static DeclaredType getUnwrappedMapKeyType(
104       final DeclaredType mapKeyAnnotationType, final Types types) {
105     checkArgument(
106         MoreTypes.asTypeElement(mapKeyAnnotationType).getKind() == ElementKind.ANNOTATION_TYPE,
107         "%s is not an annotation type",
108         mapKeyAnnotationType);
109 
110     final ExecutableElement onlyElement =
111         getOnlyElement(methodsIn(mapKeyAnnotationType.asElement().getEnclosedElements()));
112 
113     SimpleTypeVisitor6<DeclaredType, Void> keyTypeElementVisitor =
114         new SimpleTypeVisitor6<DeclaredType, Void>() {
115 
116           @Override
117           public DeclaredType visitArray(ArrayType t, Void p) {
118             throw new IllegalArgumentException(
119                 mapKeyAnnotationType + "." + onlyElement.getSimpleName() + " cannot be an array");
120           }
121 
122           @Override
123           public DeclaredType visitPrimitive(PrimitiveType t, Void p) {
124             return MoreTypes.asDeclared(types.boxedClass(t).asType());
125           }
126 
127           @Override
128           public DeclaredType visitDeclared(DeclaredType t, Void p) {
129             return t;
130           }
131         };
132     return keyTypeElementVisitor.visit(onlyElement.getReturnType());
133   }
134 
135   /**
136    * Returns the name of the generated class that contains the static {@code create} methods for a
137    * {@link MapKey} annotation type.
138    */
getMapKeyCreatorClassName(TypeElement mapKeyType)139   public static ClassName getMapKeyCreatorClassName(TypeElement mapKeyType) {
140     ClassName mapKeyTypeName = ClassName.fromTypeElement(mapKeyType);
141     return mapKeyTypeName.topLevelClassName().peerNamed(mapKeyTypeName.classFileName() + "Creator");
142   }
143 
144   /**
145    * Returns a snippet for the map key specified by the {@link MapKey} annotation on
146    * {@code bindingElement}.
147    *
148    * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey}
149    *     annotation
150    * @throws IllegalStateException if {@code bindingElement} is not annotated with a {@code MapKey}
151    *     annotation
152    */
getMapKeySnippet(Element bindingElement)153   static Snippet getMapKeySnippet(Element bindingElement) {
154     AnnotationMirror mapKey = getMapKey(bindingElement).get();
155     ClassName mapKeyCreator =
156         getMapKeyCreatorClassName(MoreTypes.asTypeElement(mapKey.getAnnotationType()));
157     Optional<? extends AnnotationValue> unwrappedValue = unwrapValue(mapKey);
158     if (unwrappedValue.isPresent()) {
159       return new MapKeySnippetExceptArrays(mapKeyCreator)
160           .visit(unwrappedValue.get(), unwrappedValue.get());
161     } else {
162       return annotationSnippet(mapKey, new MapKeySnippet(mapKeyCreator));
163     }
164   }
165 
166   /**
167    * Returns a snippet to create the visited value in code. Expects its parameter to be a class with
168    * static creation methods for all nested annotation types.
169    *
170    * <p>Note that {@link AnnotationValue#toString()} is the source-code representation of the value
171    * <em>when used in an annotation</em>, which is not always the same as the representation needed
172    * when creating the value in a method body.
173    *
174    * <p>For example, inside an annotation, a nested array of {@code int}s is simply
175    * <code>{1, 2, 3}</code>, but in code it would have to be <code> new int[] {1, 2, 3}</code>.
176    */
177   private static class MapKeySnippet
178       extends SimpleAnnotationValueVisitor6<Snippet, AnnotationValue> {
179 
180     final ClassName mapKeyCreator;
181 
MapKeySnippet(ClassName mapKeyCreator)182     MapKeySnippet(ClassName mapKeyCreator) {
183       this.mapKeyCreator = mapKeyCreator;
184     }
185 
186     @Override
visitEnumConstant(VariableElement c, AnnotationValue p)187     public Snippet visitEnumConstant(VariableElement c, AnnotationValue p) {
188       return Snippet.format(
189           "%s.%s", TypeNames.forTypeMirror(c.getEnclosingElement().asType()), c.getSimpleName());
190     }
191 
192     @Override
visitAnnotation(AnnotationMirror a, AnnotationValue p)193     public Snippet visitAnnotation(AnnotationMirror a, AnnotationValue p) {
194       return annotationSnippet(a, this);
195     }
196 
197     @Override
visitType(TypeMirror t, AnnotationValue p)198     public Snippet visitType(TypeMirror t, AnnotationValue p) {
199       return Snippet.format("%s.class", TypeNames.forTypeMirror(t));
200     }
201 
202     @Override
visitString(String s, AnnotationValue p)203     public Snippet visitString(String s, AnnotationValue p) {
204       return Snippet.format("%s", p);
205     }
206 
207     @Override
visitByte(byte b, AnnotationValue p)208     public Snippet visitByte(byte b, AnnotationValue p) {
209       return Snippet.format("(byte) %s", b);
210     }
211 
212     @Override
visitChar(char c, AnnotationValue p)213     public Snippet visitChar(char c, AnnotationValue p) {
214       return Snippet.format("%s", p);
215     }
216 
217     @Override
visitDouble(double d, AnnotationValue p)218     public Snippet visitDouble(double d, AnnotationValue p) {
219       return Snippet.format("%sD", d);
220     }
221 
222     @Override
visitFloat(float f, AnnotationValue p)223     public Snippet visitFloat(float f, AnnotationValue p) {
224       return Snippet.format("%sF", f);
225     }
226 
227     @Override
visitInt(int i, AnnotationValue p)228     public Snippet visitInt(int i, AnnotationValue p) {
229       return Snippet.format("(int) %s", i);
230     }
231 
232     @Override
visitLong(long i, AnnotationValue p)233     public Snippet visitLong(long i, AnnotationValue p) {
234       return Snippet.format("%sL", i);
235     }
236 
237     @Override
visitShort(short s, AnnotationValue p)238     public Snippet visitShort(short s, AnnotationValue p) {
239       return Snippet.format("(short) %s", s);
240     }
241 
242     @Override
defaultAction(Object o, AnnotationValue p)243     protected Snippet defaultAction(Object o, AnnotationValue p) {
244       return Snippet.format("%s", o);
245     }
246 
247     @Override
visitArray(List<? extends AnnotationValue> values, AnnotationValue p)248     public Snippet visitArray(List<? extends AnnotationValue> values, AnnotationValue p) {
249       ImmutableList.Builder<Snippet> snippets = ImmutableList.builder();
250       for (int i = 0; i < values.size(); i++) {
251         snippets.add(this.visit(values.get(i), p));
252       }
253       return Snippet.format("{%s}", makeParametersSnippet(snippets.build()));
254     }
255   }
256 
257   /**
258    * Returns a snippet for the visited value. Expects its parameter to be a class with static
259    * creation methods for all nested annotation types.
260    *
261    * <p>Throws {@link IllegalArgumentException} if the visited value is an array.
262    */
263   private static class MapKeySnippetExceptArrays extends MapKeySnippet {
264 
MapKeySnippetExceptArrays(ClassName mapKeyCreator)265     MapKeySnippetExceptArrays(ClassName mapKeyCreator) {
266       super(mapKeyCreator);
267     }
268 
269     @Override
visitArray(List<? extends AnnotationValue> values, AnnotationValue p)270     public Snippet visitArray(List<? extends AnnotationValue> values, AnnotationValue p) {
271       throw new IllegalArgumentException("Cannot unwrap arrays");
272     }
273   }
274 
275   /**
276    * Returns a snippet that calls a static method on {@code mapKeySnippet.mapKeyCreator} to create
277    * an annotation from {@code mapKeyAnnotation}.
278    */
annotationSnippet( AnnotationMirror mapKeyAnnotation, final MapKeySnippet mapKeySnippet)279   private static Snippet annotationSnippet(
280       AnnotationMirror mapKeyAnnotation, final MapKeySnippet mapKeySnippet) {
281     return Snippet.format(
282         "%s.create%s(%s)",
283         mapKeySnippet.mapKeyCreator,
284         mapKeyAnnotation.getAnnotationType().asElement().getSimpleName(),
285         makeParametersSnippet(
286             transform(
287                 getAnnotationValuesWithDefaults(mapKeyAnnotation).entrySet(),
288                 new Function<Map.Entry<ExecutableElement, AnnotationValue>, Snippet>() {
289                   @Override
290                   public Snippet apply(Map.Entry<ExecutableElement, AnnotationValue> entry) {
291                     return ARRAY_LITERAL_PREFIX.visit(
292                         entry.getKey().getReturnType(),
293                         mapKeySnippet.visit(entry.getValue(), entry.getValue()));
294                   }
295                 })));
296   }
297 
298   /**
299    * If the visited type is an array, prefixes the parameter snippet with {@code new T[]}, where
300    * {@code T} is the raw array component type.
301    */
302   private static final SimpleTypeVisitor6<Snippet, Snippet> ARRAY_LITERAL_PREFIX =
303       new SimpleTypeVisitor6<Snippet, Snippet>() {
304 
305         @Override
306         public Snippet visitArray(ArrayType t, Snippet p) {
307           return Snippet.format("new %s[] %s", RAW_TYPE_NAME.visit(t.getComponentType()), p);
308         }
309 
310         @Override
311         protected Snippet defaultAction(TypeMirror e, Snippet p) {
312           return p;
313         }
314       };
315 
316   /**
317    * If the visited type is an array, returns the name of its raw component type; otherwise returns
318    * the name of the type itself.
319    */
320   private static final SimpleTypeVisitor6<TypeName, Void> RAW_TYPE_NAME =
321       new SimpleTypeVisitor6<TypeName, Void>() {
322         @Override
323         public TypeName visitDeclared(DeclaredType t, Void p) {
324           return ClassName.fromTypeElement(MoreTypes.asTypeElement(t));
325         }
326 
327         @Override
328         protected TypeName defaultAction(TypeMirror e, Void p) {
329           return TypeNames.forTypeMirror(e);
330         }
331       };
332 
333   private MapKeys() {}
334 }
335