1 /* 2 * Copyright (C) 2019 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.hilt.processor.internal; 18 19 import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils.getMetadataUtil; 22 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 24 import static javax.lang.model.element.Modifier.PUBLIC; 25 26 import androidx.room.compiler.processing.JavaPoetExtKt; 27 import androidx.room.compiler.processing.XAnnotation; 28 import androidx.room.compiler.processing.XAnnotationValue; 29 import androidx.room.compiler.processing.XConstructorElement; 30 import androidx.room.compiler.processing.XElement; 31 import androidx.room.compiler.processing.XExecutableElement; 32 import androidx.room.compiler.processing.XFiler.Mode; 33 import androidx.room.compiler.processing.XHasModifiers; 34 import androidx.room.compiler.processing.XProcessingEnv; 35 import androidx.room.compiler.processing.XType; 36 import androidx.room.compiler.processing.XTypeElement; 37 import com.google.common.base.CaseFormat; 38 import com.google.common.base.Joiner; 39 import com.google.common.base.Preconditions; 40 import com.google.common.collect.ImmutableList; 41 import com.google.common.collect.ImmutableMap; 42 import com.google.common.collect.ImmutableSet; 43 import com.squareup.javapoet.AnnotationSpec; 44 import com.squareup.javapoet.ClassName; 45 import com.squareup.javapoet.JavaFile; 46 import com.squareup.javapoet.MethodSpec; 47 import com.squareup.javapoet.ParameterizedTypeName; 48 import com.squareup.javapoet.TypeName; 49 import com.squareup.javapoet.TypeSpec; 50 import dagger.internal.codegen.xprocessing.XAnnotations; 51 import dagger.internal.codegen.xprocessing.XElements; 52 import dagger.internal.codegen.xprocessing.XTypes; 53 import java.util.List; 54 import java.util.Objects; 55 import java.util.Optional; 56 import javax.lang.model.element.AnnotationMirror; 57 import javax.lang.model.element.Element; 58 59 /** Static helper methods for writing a processor. */ 60 public final class Processors { 61 62 public static final String CONSTRUCTOR_NAME = "<init>"; 63 64 public static final String STATIC_INITIALIZER_NAME = "<clinit>"; 65 66 /** Generates the aggregating metadata class for an aggregating annotation. */ generateAggregatingClass( String aggregatingPackage, AnnotationSpec aggregatingAnnotation, XTypeElement originatingElement, Class<?> generatorClass)67 public static void generateAggregatingClass( 68 String aggregatingPackage, 69 AnnotationSpec aggregatingAnnotation, 70 XTypeElement originatingElement, 71 Class<?> generatorClass) { 72 generateAggregatingClass( 73 aggregatingPackage, 74 aggregatingAnnotation, 75 originatingElement, 76 generatorClass, 77 Mode.Isolating); 78 } 79 80 /** Generates the aggregating metadata class for an aggregating annotation. */ generateAggregatingClass( String aggregatingPackage, AnnotationSpec aggregatingAnnotation, XTypeElement originatingElement, Class<?> generatorClass, Mode mode)81 public static void generateAggregatingClass( 82 String aggregatingPackage, 83 AnnotationSpec aggregatingAnnotation, 84 XTypeElement originatingElement, 85 Class<?> generatorClass, 86 Mode mode) { 87 ClassName name = 88 ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(originatingElement)); 89 XProcessingEnv env = getProcessingEnv(originatingElement); 90 TypeSpec.Builder builder = 91 TypeSpec.classBuilder(name) 92 .addModifiers(PUBLIC) 93 .addAnnotation(aggregatingAnnotation) 94 .addJavadoc("This class should only be referenced by generated code! ") 95 .addJavadoc("This class aggregates information across multiple compilations.\n"); 96 JavaPoetExtKt.addOriginatingElement(builder, originatingElement); 97 addGeneratedAnnotation(builder, env, generatorClass); 98 99 env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode); 100 } 101 102 /** Returns a map from {@link XAnnotation} attribute name to {@link XAnnotationValue}s */ getAnnotationValues(XAnnotation annotation)103 public static ImmutableMap<String, XAnnotationValue> getAnnotationValues(XAnnotation annotation) { 104 ImmutableMap.Builder<String, XAnnotationValue> annotationMembers = ImmutableMap.builder(); 105 for (XAnnotationValue value : annotation.getAnnotationValues()) { 106 annotationMembers.put(value.getName(), value); 107 } 108 return annotationMembers.build(); 109 } 110 111 /** Returns a list of {@link XTypeElement}s for a class attribute on an annotation. */ getAnnotationClassValues( XAnnotation annotation, String key)112 public static ImmutableList<XTypeElement> getAnnotationClassValues( 113 XAnnotation annotation, String key) { 114 ImmutableList<XTypeElement> values = XAnnotations.getAsTypeElementList(annotation, key); 115 116 ProcessorErrors.checkState( 117 values.size() >= 1, 118 annotation.getTypeElement(), 119 "@%s, '%s' class is invalid or missing: %s", 120 annotation.getName(), 121 key, 122 XAnnotations.toStableString(annotation)); 123 124 return values; 125 } 126 getOptionalAnnotationClassValues( XAnnotation annotation, String key)127 public static ImmutableList<XTypeElement> getOptionalAnnotationClassValues( 128 XAnnotation annotation, String key) { 129 return getOptionalAnnotationValues(annotation, key).stream() 130 .filter(XAnnotationValue::hasTypeValue) 131 .map( 132 annotationValue -> { 133 try { 134 return annotationValue.asType(); 135 } catch (TypeNotPresentException e) { 136 // TODO(b/277367118): we may need a way to ignore error types in XProcessing. 137 // TODO(b/278560196): we should throw ErrorTypeException and clean up broken tests. 138 return null; 139 } 140 }) 141 .filter(Objects::nonNull) 142 .map(XType::getTypeElement) 143 .collect(toImmutableList()); 144 } 145 146 private static ImmutableList<XAnnotationValue> getOptionalAnnotationValues( 147 XAnnotation annotation, String key) { 148 return annotation.getAnnotationValues().stream() 149 .filter(annotationValue -> annotationValue.getName().equals(key)) 150 .collect(toOptional()) 151 .map( 152 annotationValue -> 153 (annotationValue.hasListValue() 154 ? ImmutableList.copyOf(annotationValue.asAnnotationValueList()) 155 : ImmutableList.of(annotationValue))) 156 .orElse(ImmutableList.of()); 157 } 158 159 public static XTypeElement getTopLevelType(XElement originalElement) { 160 checkNotNull(originalElement); 161 for (XElement e = originalElement; e != null; e = e.getEnclosingElement()) { 162 if (isTopLevel(e)) { 163 return XElements.asTypeElement(e); 164 } 165 } 166 throw new IllegalStateException( 167 "Cannot find a top-level type for " + XElements.toStableString(originalElement)); 168 } 169 170 public static boolean isTopLevel(XElement element) { 171 return element.getEnclosingElement() == null; 172 } 173 174 /** Returns true if the given element has an annotation with the given class name. */ 175 public static boolean hasAnnotation(Element element, ClassName className) { 176 return getAnnotationMirrorOptional(element, className).isPresent(); 177 } 178 179 /** Returns true if the given element has an annotation that is an error kind. */ 180 public static boolean hasErrorTypeAnnotation(XElement element) { 181 for (XAnnotation annotation : element.getAllAnnotations()) { 182 if (annotation.getType().isError()) { 183 return true; 184 } 185 } 186 return false; 187 } 188 189 /** 190 * Returns the annotation mirror from the given element that corresponds to the given class. 191 * 192 * @throws IllegalArgumentException if 2 or more annotations are found. 193 * @return {@link Optional#empty()} if no annotation is found on the element. 194 */ 195 static Optional<AnnotationMirror> getAnnotationMirrorOptional( 196 Element element, ClassName className) { 197 return element.getAnnotationMirrors().stream() 198 .filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className)) 199 .collect(toOptional()); 200 } 201 202 /** 203 * Returns the name of a class, including prefixing with enclosing class names. i.e. for inner 204 * class Foo enclosed by Bar, returns Bar_Foo instead of just Foo 205 */ 206 public static String getEnclosedName(ClassName name) { 207 return Joiner.on('_').join(name.simpleNames()); 208 } 209 210 /** 211 * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with 212 * {@code _}. 213 */ 214 public static ClassName getEnclosedClassName(ClassName className) { 215 return ClassName.get(className.packageName(), getEnclosedName(className)); 216 } 217 218 /** 219 * Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with 220 * {@code _}. 221 */ 222 public static ClassName getEnclosedClassName(XTypeElement typeElement) { 223 return getEnclosedClassName(typeElement.getClassName()); 224 } 225 226 /** 227 * Returns the fully qualified class name, with _ instead of . For elements that are not type 228 * elements, this continues to append the simple name of elements. For example, 229 * foo_bar_Outer_Inner_fooMethod. 230 */ 231 public static String getFullEnclosedName(XElement element) { 232 Preconditions.checkNotNull(element); 233 String qualifiedName = ""; 234 while (element != null) { 235 if (element.getEnclosingElement() == null) { 236 qualifiedName = 237 element.getClosestMemberContainer().asClassName().getCanonicalName() + qualifiedName; 238 } else { 239 // This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11 240 // contains newly added "module" enclosing elements of packages, which adds an additional 241 // "_" prefix to the name due to an empty module element compared with jdk8. 242 if (!XElements.getSimpleName(element).isEmpty()) { 243 qualifiedName = "." + XElements.getSimpleName(element) + qualifiedName; 244 } 245 } 246 element = element.getEnclosingElement(); 247 } 248 return qualifiedName.replace('.', '_'); 249 } 250 251 /** Appends the given string to the end of the class name. */ 252 public static ClassName append(ClassName name, String suffix) { 253 return name.peerClass(name.simpleName() + suffix); 254 } 255 256 /** Prepends the given string to the beginning of the class name. */ 257 public static ClassName prepend(ClassName name, String prefix) { 258 return name.peerClass(prefix + name.simpleName()); 259 } 260 261 /** 262 * Removes the string {@code suffix} from the simple name of {@code type} and returns it. 263 * 264 * @throws BadInputException if the simple name of {@code type} does not end with {@code suffix} 265 */ 266 public static ClassName removeNameSuffix(XTypeElement type, String suffix) { 267 ClassName originalName = type.getClassName(); 268 String originalSimpleName = originalName.simpleName(); 269 ProcessorErrors.checkState( 270 originalSimpleName.endsWith(suffix), 271 type, 272 "Name of type %s must end with '%s'", 273 originalName, 274 suffix); 275 String withoutSuffix = 276 originalSimpleName.substring(0, originalSimpleName.length() - suffix.length()); 277 return originalName.peerClass(withoutSuffix); 278 } 279 280 /** Returns {@code true} if element inherits directly or indirectly from the className. */ 281 public static boolean isAssignableFrom(XTypeElement element, ClassName className) { 282 return isAssignableFromAnyOf(element, ImmutableSet.of(className)); 283 } 284 285 /** Returns {@code true} if element inherits directly or indirectly from any of the classNames. */ 286 public static boolean isAssignableFromAnyOf( 287 XTypeElement element, ImmutableSet<ClassName> classNames) { 288 for (ClassName className : classNames) { 289 if (element.getClassName().equals(className)) { 290 return true; 291 } 292 } 293 294 XType superClass = element.getSuperClass(); 295 // None type is returned if this is an interface or Object 296 // Error type is returned for classes that are generated by this processor 297 if (superClass != null && !superClass.isNone() && !superClass.isError()) { 298 Preconditions.checkState(XTypes.isDeclared(superClass)); 299 if (isAssignableFromAnyOf(superClass.getTypeElement(), classNames)) { 300 return true; 301 } 302 } 303 304 for (XType iface : element.getSuperInterfaces()) { 305 // Skip errors and keep looking. This is especially needed for classes generated by this 306 // processor. 307 if (iface.isError()) { 308 continue; 309 } 310 Preconditions.checkState( 311 XTypes.isDeclared(iface), "Interface type is %s", XTypes.getKindName(iface)); 312 if (isAssignableFromAnyOf(iface.getTypeElement(), classNames)) { 313 return true; 314 } 315 } 316 317 return false; 318 } 319 320 /** Returns MapKey annotated annotations found on an element. */ 321 public static ImmutableList<XAnnotation> getMapKeyAnnotations(XElement element) { 322 // Normally, we wouldn't need to handle Kotlin metadata because map keys are typically used 323 // only on methods. However, with @BindValueIntoMap, this can be used on fields so we need 324 // to check annotations on the property as well, just like with qualifiers. 325 return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.MAP_KEY); 326 } 327 328 /** Returns Qualifier annotated annotations found on an element. */ 329 public static ImmutableList<XAnnotation> getQualifierAnnotations(XElement element) { 330 return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.QUALIFIER); 331 } 332 333 /** Returns Scope annotated annotations found on an element. */ 334 public static ImmutableList<XAnnotation> getScopeAnnotations(XElement element) { 335 return ImmutableList.copyOf( 336 element.getAnnotationsAnnotatedWith(ClassNames.SCOPE)); 337 } 338 339 /** 340 * Shortcut for converting from upper camel to lower camel case 341 * 342 * <p>Example: "SomeString" => "someString" 343 */ 344 public static String upperToLowerCamel(String upperCamel) { 345 return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel); 346 } 347 348 /** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */ 349 public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) { 350 MethodSpec.Builder builder; 351 352 if (methodSpec.isConstructor()) { 353 // Constructors cannot have return types 354 builder = MethodSpec.constructorBuilder(); 355 } else { 356 builder = MethodSpec.methodBuilder(methodSpec.name) 357 .returns(methodSpec.returnType); 358 } 359 360 return builder 361 .addAnnotations(methodSpec.annotations) 362 .addModifiers(methodSpec.modifiers) 363 .addParameters(methodSpec.parameters) 364 .addExceptions(methodSpec.exceptions) 365 .addJavadoc(methodSpec.javadoc.toString()) 366 .addTypeVariables(methodSpec.typeVariables); 367 } 368 369 /** 370 * Returns true if the given method is annotated with one of the annotations Dagger recognizes for 371 * abstract methods (e.g. @Binds). 372 */ 373 public static boolean hasDaggerAbstractMethodAnnotation(XExecutableElement method) { 374 return method.hasAnnotation(ClassNames.BINDS) 375 || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF) 376 || method.hasAnnotation(ClassNames.MULTIBINDS) 377 || method.hasAnnotation(ClassNames.CONTRIBUTES_ANDROID_INJECTOR); 378 } 379 380 public static boolean requiresModuleInstance(XTypeElement module) { 381 // Binding methods that lack ABSTRACT or STATIC require module instantiation. 382 // Required by Dagger. See b/31489617. 383 return module.getDeclaredMethods().stream() 384 .filter(Processors::isBindingMethod) 385 .anyMatch(method -> !method.isAbstract() && !method.isStatic()) 386 && !module.isKotlinObject(); 387 } 388 389 public static boolean hasVisibleEmptyConstructor(XTypeElement type) { 390 List<XConstructorElement> constructors = type.getConstructors(); 391 return constructors.isEmpty() 392 || constructors.stream() 393 .filter(constructor -> constructor.getParameters().isEmpty()) 394 .anyMatch( 395 constructor -> 396 !constructor.isPrivate() 397 ); 398 } 399 400 private static boolean isBindingMethod(XExecutableElement method) { 401 return method.hasAnnotation(ClassNames.PROVIDES) 402 || method.hasAnnotation(ClassNames.BINDS) 403 || method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF) 404 || method.hasAnnotation(ClassNames.MULTIBINDS); 405 } 406 407 public static void addGeneratedAnnotation( 408 TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, Class<?> generatorClass) { 409 addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName()); 410 } 411 412 public static void addGeneratedAnnotation( 413 TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, String generatorClass) { 414 XTypeElement annotation = env.findGeneratedAnnotation(); 415 if (annotation != null) { 416 typeSpecBuilder.addAnnotation( 417 AnnotationSpec.builder(annotation.getClassName()) 418 .addMember("value", "$S", generatorClass) 419 .build()); 420 } 421 } 422 423 public static AnnotationSpec getOriginatingElementAnnotation(XTypeElement element) { 424 TypeName rawType = rawTypeName(getTopLevelType(element).getClassName()); 425 return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT) 426 .addMember("topLevelClass", "$T.class", rawType) 427 .build(); 428 } 429 430 /** 431 * Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a 432 * parameterized type, it returns the argument unchanged. 433 */ 434 public static TypeName rawTypeName(TypeName typeName) { 435 return (typeName instanceof ParameterizedTypeName) 436 ? ((ParameterizedTypeName) typeName).rawType 437 : typeName; 438 } 439 440 public static Optional<XTypeElement> getOriginatingTestElement(XElement element) { 441 XTypeElement topLevelType = getOriginatingTopLevelType(element); 442 return topLevelType.hasAnnotation(ClassNames.HILT_ANDROID_TEST) 443 ? Optional.of(topLevelType) 444 : Optional.empty(); 445 } 446 447 private static XTypeElement getOriginatingTopLevelType(XElement element) { 448 XTypeElement topLevelType = getTopLevelType(element); 449 if (topLevelType.hasAnnotation(ClassNames.ORIGINATING_ELEMENT)) { 450 return getOriginatingTopLevelType( 451 XAnnotations.getAsTypeElement( 452 topLevelType.getAnnotation(ClassNames.ORIGINATING_ELEMENT), "topLevelClass")); 453 } 454 return topLevelType; 455 } 456 457 public static boolean hasJavaPackagePrivateVisibility(XHasModifiers element) { 458 return !element.isPrivate() 459 && !element.isProtected() 460 && !element.isInternal() 461 && !element.isPublic(); 462 } 463 464 private Processors() {} 465 } 466