/* * Copyright (C) 2015 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen; import com.google.auto.common.MoreTypes; import com.google.common.base.Function; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import dagger.MapKey; import dagger.internal.codegen.writer.ClassName; import dagger.internal.codegen.writer.Snippet; import dagger.internal.codegen.writer.TypeName; import dagger.internal.codegen.writer.TypeNames; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; import static com.google.auto.common.AnnotationMirrors.getAnnotatedAnnotations; import static com.google.auto.common.AnnotationMirrors.getAnnotationValuesWithDefaults; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.collect.Iterables.transform; import static dagger.internal.codegen.writer.Snippet.makeParametersSnippet; import static javax.lang.model.util.ElementFilter.methodsIn; /** * Methods for extracting {@link MapKey} annotations and key snippets from binding elements. */ final class MapKeys { /** * If {@code bindingElement} is annotated with a {@link MapKey} annotation, returns it. * * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey} * annotation */ static Optional getMapKey(Element bindingElement) { ImmutableSet mapKeys = getMapKeys(bindingElement); return mapKeys.isEmpty() ? Optional.absent() : Optional.of(getOnlyElement(mapKeys)); } /** * Returns all of the {@link MapKey} annotations that annotate {@code bindingElement}. */ static ImmutableSet getMapKeys(Element bindingElement) { return getAnnotatedAnnotations(bindingElement, MapKey.class); } /** * Returns the annotation value if {@code mapKey}'s type is annotated with * {@link MapKey @MapKey(unwrapValue = true)}. * * @throws IllegalArgumentException if {@code mapKey}'s type is not annotated with * {@link MapKey @MapKey} at all. */ static Optional unwrapValue(AnnotationMirror mapKey) { MapKey mapKeyAnnotation = mapKey.getAnnotationType().asElement().getAnnotation(MapKey.class); checkArgument( mapKeyAnnotation != null, "%s is not annotated with @MapKey", mapKey.getAnnotationType()); return mapKeyAnnotation.unwrapValue() ? Optional.of(getOnlyElement(mapKey.getElementValues().values())) : Optional.absent(); } /** * Returns the map key type for an unwrapped {@link MapKey} annotation type. If the single member * type is primitive, returns the boxed type. * * @throws IllegalArgumentException if {@code mapKeyAnnotationType} is not an annotation type or * has more than one member, or if its single member is an array * @throws NoSuchElementException if the annotation has no members */ public static DeclaredType getUnwrappedMapKeyType( final DeclaredType mapKeyAnnotationType, final Types types) { checkArgument( MoreTypes.asTypeElement(mapKeyAnnotationType).getKind() == ElementKind.ANNOTATION_TYPE, "%s is not an annotation type", mapKeyAnnotationType); final ExecutableElement onlyElement = getOnlyElement(methodsIn(mapKeyAnnotationType.asElement().getEnclosedElements())); SimpleTypeVisitor6 keyTypeElementVisitor = new SimpleTypeVisitor6() { @Override public DeclaredType visitArray(ArrayType t, Void p) { throw new IllegalArgumentException( mapKeyAnnotationType + "." + onlyElement.getSimpleName() + " cannot be an array"); } @Override public DeclaredType visitPrimitive(PrimitiveType t, Void p) { return MoreTypes.asDeclared(types.boxedClass(t).asType()); } @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; } }; return keyTypeElementVisitor.visit(onlyElement.getReturnType()); } /** * Returns the name of the generated class that contains the static {@code create} methods for a * {@link MapKey} annotation type. */ public static ClassName getMapKeyCreatorClassName(TypeElement mapKeyType) { ClassName mapKeyTypeName = ClassName.fromTypeElement(mapKeyType); return mapKeyTypeName.topLevelClassName().peerNamed(mapKeyTypeName.classFileName() + "Creator"); } /** * Returns a snippet for the map key specified by the {@link MapKey} annotation on * {@code bindingElement}. * * @throws IllegalArgumentException if the element is annotated with more than one {@code MapKey} * annotation * @throws IllegalStateException if {@code bindingElement} is not annotated with a {@code MapKey} * annotation */ static Snippet getMapKeySnippet(Element bindingElement) { AnnotationMirror mapKey = getMapKey(bindingElement).get(); ClassName mapKeyCreator = getMapKeyCreatorClassName(MoreTypes.asTypeElement(mapKey.getAnnotationType())); Optional unwrappedValue = unwrapValue(mapKey); if (unwrappedValue.isPresent()) { return new MapKeySnippetExceptArrays(mapKeyCreator) .visit(unwrappedValue.get(), unwrappedValue.get()); } else { return annotationSnippet(mapKey, new MapKeySnippet(mapKeyCreator)); } } /** * Returns a snippet to create the visited value in code. Expects its parameter to be a class with * static creation methods for all nested annotation types. * *

Note that {@link AnnotationValue#toString()} is the source-code representation of the value * when used in an annotation, which is not always the same as the representation needed * when creating the value in a method body. * *

For example, inside an annotation, a nested array of {@code int}s is simply * {1, 2, 3}, but in code it would have to be new int[] {1, 2, 3}. */ private static class MapKeySnippet extends SimpleAnnotationValueVisitor6 { final ClassName mapKeyCreator; MapKeySnippet(ClassName mapKeyCreator) { this.mapKeyCreator = mapKeyCreator; } @Override public Snippet visitEnumConstant(VariableElement c, AnnotationValue p) { return Snippet.format( "%s.%s", TypeNames.forTypeMirror(c.getEnclosingElement().asType()), c.getSimpleName()); } @Override public Snippet visitAnnotation(AnnotationMirror a, AnnotationValue p) { return annotationSnippet(a, this); } @Override public Snippet visitType(TypeMirror t, AnnotationValue p) { return Snippet.format("%s.class", TypeNames.forTypeMirror(t)); } @Override public Snippet visitString(String s, AnnotationValue p) { return Snippet.format("%s", p); } @Override public Snippet visitByte(byte b, AnnotationValue p) { return Snippet.format("(byte) %s", b); } @Override public Snippet visitChar(char c, AnnotationValue p) { return Snippet.format("%s", p); } @Override public Snippet visitDouble(double d, AnnotationValue p) { return Snippet.format("%sD", d); } @Override public Snippet visitFloat(float f, AnnotationValue p) { return Snippet.format("%sF", f); } @Override public Snippet visitInt(int i, AnnotationValue p) { return Snippet.format("(int) %s", i); } @Override public Snippet visitLong(long i, AnnotationValue p) { return Snippet.format("%sL", i); } @Override public Snippet visitShort(short s, AnnotationValue p) { return Snippet.format("(short) %s", s); } @Override protected Snippet defaultAction(Object o, AnnotationValue p) { return Snippet.format("%s", o); } @Override public Snippet visitArray(List values, AnnotationValue p) { ImmutableList.Builder snippets = ImmutableList.builder(); for (int i = 0; i < values.size(); i++) { snippets.add(this.visit(values.get(i), p)); } return Snippet.format("{%s}", makeParametersSnippet(snippets.build())); } } /** * Returns a snippet for the visited value. Expects its parameter to be a class with static * creation methods for all nested annotation types. * *

Throws {@link IllegalArgumentException} if the visited value is an array. */ private static class MapKeySnippetExceptArrays extends MapKeySnippet { MapKeySnippetExceptArrays(ClassName mapKeyCreator) { super(mapKeyCreator); } @Override public Snippet visitArray(List values, AnnotationValue p) { throw new IllegalArgumentException("Cannot unwrap arrays"); } } /** * Returns a snippet that calls a static method on {@code mapKeySnippet.mapKeyCreator} to create * an annotation from {@code mapKeyAnnotation}. */ private static Snippet annotationSnippet( AnnotationMirror mapKeyAnnotation, final MapKeySnippet mapKeySnippet) { return Snippet.format( "%s.create%s(%s)", mapKeySnippet.mapKeyCreator, mapKeyAnnotation.getAnnotationType().asElement().getSimpleName(), makeParametersSnippet( transform( getAnnotationValuesWithDefaults(mapKeyAnnotation).entrySet(), new Function, Snippet>() { @Override public Snippet apply(Map.Entry entry) { return ARRAY_LITERAL_PREFIX.visit( entry.getKey().getReturnType(), mapKeySnippet.visit(entry.getValue(), entry.getValue())); } }))); } /** * If the visited type is an array, prefixes the parameter snippet with {@code new T[]}, where * {@code T} is the raw array component type. */ private static final SimpleTypeVisitor6 ARRAY_LITERAL_PREFIX = new SimpleTypeVisitor6() { @Override public Snippet visitArray(ArrayType t, Snippet p) { return Snippet.format("new %s[] %s", RAW_TYPE_NAME.visit(t.getComponentType()), p); } @Override protected Snippet defaultAction(TypeMirror e, Snippet p) { return p; } }; /** * If the visited type is an array, returns the name of its raw component type; otherwise returns * the name of the type itself. */ private static final SimpleTypeVisitor6 RAW_TYPE_NAME = new SimpleTypeVisitor6() { @Override public TypeName visitDeclared(DeclaredType t, Void p) { return ClassName.fromTypeElement(MoreTypes.asTypeElement(t)); } @Override protected TypeName defaultAction(TypeMirror e, Void p) { return TypeNames.forTypeMirror(e); } }; private MapKeys() {} }