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