dagger.hilt.processor.internal.Processors Maven / Gradle / Ivy
Show all versions of hilt-compiler Show documentation
/*
* Copyright (C) 2019 The Dagger Authors.
*
* 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.hilt.processor.internal;
import static androidx.room.compiler.processing.compat.XConverters.getProcessingEnv;
import static com.google.common.base.Preconditions.checkNotNull;
import static dagger.hilt.processor.internal.kotlin.KotlinMetadataUtils.getMetadataUtil;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static javax.lang.model.element.Modifier.PUBLIC;
import androidx.room.compiler.processing.JavaPoetExtKt;
import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XAnnotationValue;
import androidx.room.compiler.processing.XConstructorElement;
import androidx.room.compiler.processing.XElement;
import androidx.room.compiler.processing.XExecutableElement;
import androidx.room.compiler.processing.XFiler.Mode;
import androidx.room.compiler.processing.XHasModifiers;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
import com.google.common.base.CaseFormat;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import dagger.internal.codegen.xprocessing.XAnnotations;
import dagger.internal.codegen.xprocessing.XElements;
import dagger.internal.codegen.xprocessing.XTypes;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
/** Static helper methods for writing a processor. */
public final class Processors {
public static final String CONSTRUCTOR_NAME = "";
public static final String STATIC_INITIALIZER_NAME = "";
/** Generates the aggregating metadata class for an aggregating annotation. */
public static void generateAggregatingClass(
String aggregatingPackage,
AnnotationSpec aggregatingAnnotation,
XTypeElement originatingElement,
Class> generatorClass) {
generateAggregatingClass(
aggregatingPackage,
aggregatingAnnotation,
originatingElement,
generatorClass,
Mode.Isolating);
}
/** Generates the aggregating metadata class for an aggregating annotation. */
public static void generateAggregatingClass(
String aggregatingPackage,
AnnotationSpec aggregatingAnnotation,
XTypeElement originatingElement,
Class> generatorClass,
Mode mode) {
ClassName name =
ClassName.get(aggregatingPackage, "_" + getFullEnclosedName(originatingElement));
XProcessingEnv env = getProcessingEnv(originatingElement);
TypeSpec.Builder builder =
TypeSpec.classBuilder(name)
.addModifiers(PUBLIC)
.addAnnotation(aggregatingAnnotation)
.addJavadoc("This class should only be referenced by generated code! ")
.addJavadoc("This class aggregates information across multiple compilations.\n");
JavaPoetExtKt.addOriginatingElement(builder, originatingElement);
addGeneratedAnnotation(builder, env, generatorClass);
env.getFiler().write(JavaFile.builder(name.packageName(), builder.build()).build(), mode);
}
/** Returns a map from {@link XAnnotation} attribute name to {@link XAnnotationValue}s */
public static ImmutableMap getAnnotationValues(XAnnotation annotation) {
ImmutableMap.Builder annotationMembers = ImmutableMap.builder();
for (XAnnotationValue value : annotation.getAnnotationValues()) {
annotationMembers.put(value.getName(), value);
}
return annotationMembers.build();
}
/** Returns a list of {@link XTypeElement}s for a class attribute on an annotation. */
public static ImmutableList getAnnotationClassValues(
XAnnotation annotation, String key) {
ImmutableList values = XAnnotations.getAsTypeElementList(annotation, key);
ProcessorErrors.checkState(
values.size() >= 1,
annotation.getTypeElement(),
"@%s, '%s' class is invalid or missing: %s",
annotation.getName(),
key,
XAnnotations.toStableString(annotation));
return values;
}
public static ImmutableList getOptionalAnnotationClassValues(
XAnnotation annotation, String key) {
return getOptionalAnnotationValues(annotation, key).stream()
.filter(XAnnotationValue::hasTypeValue)
.map(
annotationValue -> {
try {
return annotationValue.asType();
} catch (TypeNotPresentException e) {
// TODO(b/277367118): we may need a way to ignore error types in XProcessing.
// TODO(b/278560196): we should throw ErrorTypeException and clean up broken tests.
return null;
}
})
.filter(Objects::nonNull)
.map(XType::getTypeElement)
.collect(toImmutableList());
}
private static ImmutableList getOptionalAnnotationValues(
XAnnotation annotation, String key) {
return annotation.getAnnotationValues().stream()
.filter(annotationValue -> annotationValue.getName().equals(key))
.collect(toOptional())
.map(
annotationValue ->
(annotationValue.hasListValue()
? ImmutableList.copyOf(annotationValue.asAnnotationValueList())
: ImmutableList.of(annotationValue)))
.orElse(ImmutableList.of());
}
public static XTypeElement getTopLevelType(XElement originalElement) {
checkNotNull(originalElement);
for (XElement e = originalElement; e != null; e = e.getEnclosingElement()) {
if (isTopLevel(e)) {
return XElements.asTypeElement(e);
}
}
throw new IllegalStateException(
"Cannot find a top-level type for " + XElements.toStableString(originalElement));
}
public static boolean isTopLevel(XElement element) {
return element.getEnclosingElement() == null;
}
/** Returns true if the given element has an annotation with the given class name. */
public static boolean hasAnnotation(Element element, ClassName className) {
return getAnnotationMirrorOptional(element, className).isPresent();
}
/** Returns true if the given element has an annotation that is an error kind. */
public static boolean hasErrorTypeAnnotation(XElement element) {
for (XAnnotation annotation : element.getAllAnnotations()) {
if (annotation.getType().isError()) {
return true;
}
}
return false;
}
/**
* Returns the annotation mirror from the given element that corresponds to the given class.
*
* @throws IllegalArgumentException if 2 or more annotations are found.
* @return {@link Optional#empty()} if no annotation is found on the element.
*/
static Optional getAnnotationMirrorOptional(
Element element, ClassName className) {
return element.getAnnotationMirrors().stream()
.filter(mirror -> ClassName.get(mirror.getAnnotationType()).equals(className))
.collect(toOptional());
}
/**
* Returns the name of a class, including prefixing with enclosing class names. i.e. for inner
* class Foo enclosed by Bar, returns Bar_Foo instead of just Foo
*/
public static String getEnclosedName(ClassName name) {
return Joiner.on('_').join(name.simpleNames());
}
/**
* Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
* {@code _}.
*/
public static ClassName getEnclosedClassName(ClassName className) {
return ClassName.get(className.packageName(), getEnclosedName(className));
}
/**
* Returns an equivalent class name with the {@code .} (dots) used for inner classes replaced with
* {@code _}.
*/
public static ClassName getEnclosedClassName(XTypeElement typeElement) {
return getEnclosedClassName(typeElement.getClassName());
}
/**
* Returns the fully qualified class name, with _ instead of . For elements that are not type
* elements, this continues to append the simple name of elements. For example,
* foo_bar_Outer_Inner_fooMethod.
*/
public static String getFullEnclosedName(XElement element) {
Preconditions.checkNotNull(element);
String qualifiedName = "";
while (element != null) {
if (element.getEnclosingElement() == null) {
qualifiedName =
element.getClosestMemberContainer().asClassName().getCanonicalName() + qualifiedName;
} else {
// This check is needed to keep the name stable when compiled with jdk8 vs jdk11. jdk11
// contains newly added "module" enclosing elements of packages, which adds an additional
// "_" prefix to the name due to an empty module element compared with jdk8.
if (!XElements.getSimpleName(element).isEmpty()) {
qualifiedName = "." + XElements.getSimpleName(element) + qualifiedName;
}
}
element = element.getEnclosingElement();
}
return qualifiedName.replace('.', '_');
}
/** Appends the given string to the end of the class name. */
public static ClassName append(ClassName name, String suffix) {
return name.peerClass(name.simpleName() + suffix);
}
/** Prepends the given string to the beginning of the class name. */
public static ClassName prepend(ClassName name, String prefix) {
return name.peerClass(prefix + name.simpleName());
}
/**
* Removes the string {@code suffix} from the simple name of {@code type} and returns it.
*
* @throws BadInputException if the simple name of {@code type} does not end with {@code suffix}
*/
public static ClassName removeNameSuffix(XTypeElement type, String suffix) {
ClassName originalName = type.getClassName();
String originalSimpleName = originalName.simpleName();
ProcessorErrors.checkState(
originalSimpleName.endsWith(suffix),
type,
"Name of type %s must end with '%s'",
originalName,
suffix);
String withoutSuffix =
originalSimpleName.substring(0, originalSimpleName.length() - suffix.length());
return originalName.peerClass(withoutSuffix);
}
/** Returns {@code true} if element inherits directly or indirectly from the className. */
public static boolean isAssignableFrom(XTypeElement element, ClassName className) {
return isAssignableFromAnyOf(element, ImmutableSet.of(className));
}
/** Returns {@code true} if element inherits directly or indirectly from any of the classNames. */
public static boolean isAssignableFromAnyOf(
XTypeElement element, ImmutableSet classNames) {
for (ClassName className : classNames) {
if (element.getClassName().equals(className)) {
return true;
}
}
XType superClass = element.getSuperClass();
// None type is returned if this is an interface or Object
// Error type is returned for classes that are generated by this processor
if (superClass != null && !superClass.isNone() && !superClass.isError()) {
Preconditions.checkState(XTypes.isDeclared(superClass));
if (isAssignableFromAnyOf(superClass.getTypeElement(), classNames)) {
return true;
}
}
for (XType iface : element.getSuperInterfaces()) {
// Skip errors and keep looking. This is especially needed for classes generated by this
// processor.
if (iface.isError()) {
continue;
}
Preconditions.checkState(
XTypes.isDeclared(iface), "Interface type is %s", XTypes.getKindName(iface));
if (isAssignableFromAnyOf(iface.getTypeElement(), classNames)) {
return true;
}
}
return false;
}
/** Returns MapKey annotated annotations found on an element. */
public static ImmutableList getMapKeyAnnotations(XElement element) {
// Normally, we wouldn't need to handle Kotlin metadata because map keys are typically used
// only on methods. However, with @BindValueIntoMap, this can be used on fields so we need
// to check annotations on the property as well, just like with qualifiers.
return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.MAP_KEY);
}
/** Returns Qualifier annotated annotations found on an element. */
public static ImmutableList getQualifierAnnotations(XElement element) {
return getMetadataUtil().getAnnotationsAnnotatedWith(element, ClassNames.QUALIFIER);
}
/** Returns Scope annotated annotations found on an element. */
public static ImmutableList getScopeAnnotations(XElement element) {
return ImmutableList.copyOf(
element.getAnnotationsAnnotatedWith(ClassNames.SCOPE));
}
/**
* Shortcut for converting from upper camel to lower camel case
*
* Example: "SomeString" => "someString"
*/
public static String upperToLowerCamel(String upperCamel) {
return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, upperCamel);
}
/** @return copy of the given MethodSpec as {@link MethodSpec.Builder} with method body removed */
public static MethodSpec.Builder copyMethodSpecWithoutBody(MethodSpec methodSpec) {
MethodSpec.Builder builder;
if (methodSpec.isConstructor()) {
// Constructors cannot have return types
builder = MethodSpec.constructorBuilder();
} else {
builder = MethodSpec.methodBuilder(methodSpec.name)
.returns(methodSpec.returnType);
}
return builder
.addAnnotations(methodSpec.annotations)
.addModifiers(methodSpec.modifiers)
.addParameters(methodSpec.parameters)
.addExceptions(methodSpec.exceptions)
.addJavadoc(methodSpec.javadoc.toString())
.addTypeVariables(methodSpec.typeVariables);
}
/**
* Returns true if the given method is annotated with one of the annotations Dagger recognizes for
* abstract methods (e.g. @Binds).
*/
public static boolean hasDaggerAbstractMethodAnnotation(XExecutableElement method) {
return method.hasAnnotation(ClassNames.BINDS)
|| method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
|| method.hasAnnotation(ClassNames.MULTIBINDS)
|| method.hasAnnotation(ClassNames.CONTRIBUTES_ANDROID_INJECTOR);
}
public static boolean requiresModuleInstance(XTypeElement module) {
// Binding methods that lack ABSTRACT or STATIC require module instantiation.
// Required by Dagger. See b/31489617.
return module.getDeclaredMethods().stream()
.filter(Processors::isBindingMethod)
.anyMatch(method -> !method.isAbstract() && !method.isStatic())
&& !module.isKotlinObject();
}
public static boolean hasVisibleEmptyConstructor(XTypeElement type) {
List constructors = type.getConstructors();
return constructors.isEmpty()
|| constructors.stream()
.filter(constructor -> constructor.getParameters().isEmpty())
.anyMatch(
constructor ->
!constructor.isPrivate()
);
}
private static boolean isBindingMethod(XExecutableElement method) {
return method.hasAnnotation(ClassNames.PROVIDES)
|| method.hasAnnotation(ClassNames.BINDS)
|| method.hasAnnotation(ClassNames.BINDS_OPTIONAL_OF)
|| method.hasAnnotation(ClassNames.MULTIBINDS);
}
public static void addGeneratedAnnotation(
TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, Class> generatorClass) {
addGeneratedAnnotation(typeSpecBuilder, env, generatorClass.getName());
}
public static void addGeneratedAnnotation(
TypeSpec.Builder typeSpecBuilder, XProcessingEnv env, String generatorClass) {
XTypeElement annotation = env.findGeneratedAnnotation();
if (annotation != null) {
typeSpecBuilder.addAnnotation(
AnnotationSpec.builder(annotation.getClassName())
.addMember("value", "$S", generatorClass)
.build());
}
}
public static AnnotationSpec getOriginatingElementAnnotation(XTypeElement element) {
TypeName rawType = rawTypeName(getTopLevelType(element).getClassName());
return AnnotationSpec.builder(ClassNames.ORIGINATING_ELEMENT)
.addMember("topLevelClass", "$T.class", rawType)
.build();
}
/**
* Returns the {@link TypeName} for the raw type of the given type name. If the argument isn't a
* parameterized type, it returns the argument unchanged.
*/
public static TypeName rawTypeName(TypeName typeName) {
return (typeName instanceof ParameterizedTypeName)
? ((ParameterizedTypeName) typeName).rawType
: typeName;
}
public static Optional getOriginatingTestElement(XElement element) {
XTypeElement topLevelType = getOriginatingTopLevelType(element);
return topLevelType.hasAnnotation(ClassNames.HILT_ANDROID_TEST)
? Optional.of(topLevelType)
: Optional.empty();
}
private static XTypeElement getOriginatingTopLevelType(XElement element) {
XTypeElement topLevelType = getTopLevelType(element);
if (topLevelType.hasAnnotation(ClassNames.ORIGINATING_ELEMENT)) {
return getOriginatingTopLevelType(
XAnnotations.getAsTypeElement(
topLevelType.getAnnotation(ClassNames.ORIGINATING_ELEMENT), "topLevelClass"));
}
return topLevelType;
}
public static boolean hasJavaPackagePrivateVisibility(XHasModifiers element) {
return !element.isPrivate()
&& !element.isProtected()
&& !element.isInternal()
&& !element.isPublic();
}
private Processors() {}
}