Home | History | Annotate | Download | only in codegen
      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    */
     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    */
     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    */
     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    */
    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    */
    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    */
    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 
    182     MapKeySnippet(ClassName mapKeyCreator) {
    183       this.mapKeyCreator = mapKeyCreator;
    184     }
    185 
    186     @Override
    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
    193     public Snippet visitAnnotation(AnnotationMirror a, AnnotationValue p) {
    194       return annotationSnippet(a, this);
    195     }
    196 
    197     @Override
    198     public Snippet visitType(TypeMirror t, AnnotationValue p) {
    199       return Snippet.format("%s.class", TypeNames.forTypeMirror(t));
    200     }
    201 
    202     @Override
    203     public Snippet visitString(String s, AnnotationValue p) {
    204       return Snippet.format("%s", p);
    205     }
    206 
    207     @Override
    208     public Snippet visitByte(byte b, AnnotationValue p) {
    209       return Snippet.format("(byte) %s", b);
    210     }
    211 
    212     @Override
    213     public Snippet visitChar(char c, AnnotationValue p) {
    214       return Snippet.format("%s", p);
    215     }
    216 
    217     @Override
    218     public Snippet visitDouble(double d, AnnotationValue p) {
    219       return Snippet.format("%sD", d);
    220     }
    221 
    222     @Override
    223     public Snippet visitFloat(float f, AnnotationValue p) {
    224       return Snippet.format("%sF", f);
    225     }
    226 
    227     @Override
    228     public Snippet visitInt(int i, AnnotationValue p) {
    229       return Snippet.format("(int) %s", i);
    230     }
    231 
    232     @Override
    233     public Snippet visitLong(long i, AnnotationValue p) {
    234       return Snippet.format("%sL", i);
    235     }
    236 
    237     @Override
    238     public Snippet visitShort(short s, AnnotationValue p) {
    239       return Snippet.format("(short) %s", s);
    240     }
    241 
    242     @Override
    243     protected Snippet defaultAction(Object o, AnnotationValue p) {
    244       return Snippet.format("%s", o);
    245     }
    246 
    247     @Override
    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 
    265     MapKeySnippetExceptArrays(ClassName mapKeyCreator) {
    266       super(mapKeyCreator);
    267     }
    268 
    269     @Override
    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    */
    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