io.micronaut.annotation.processing.ModelUtils Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original 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
*
* https://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 io.micronaut.annotation.processing;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.processing.JavaModelUtils;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
import static javax.lang.model.element.Modifier.*;
import static javax.lang.model.type.TypeKind.NONE;
/**
* Provides utility method for working with the annotation processor AST.
*
* @author Graeme Rocher
* @since 1.0
*/
@Internal
public class ModelUtils {
private static final Method RECORD_COMPONENTS_METHODS = ReflectionUtils.findMethod(TypeElement.class, "getRecordComponents").orElse(null);
private final Elements elementUtils;
private final Types typeUtils;
/**
* @param elementUtils The {@link Elements}
* @param typeUtils The {@link Types}
*/
protected ModelUtils(Elements elementUtils, Types typeUtils) {
this.elementUtils = elementUtils;
this.typeUtils = typeUtils;
}
/**
* @return The type utilities
*/
public Types getTypeUtils() {
return typeUtils;
}
/**
* Obtains the {@link TypeElement} for an given element.
*
* @param element The element
* @return The {@link TypeElement}
*/
@Nullable public final TypeElement classElementFor(Element element) {
while (element != null && !(JavaModelUtils.isClassOrInterface(element) || JavaModelUtils.isRecord(element) || JavaModelUtils.isEnum(element))) {
element = element.getEnclosingElement();
}
if (element instanceof TypeElement) {
return (TypeElement) element;
}
return null;
}
/**
* The binary name of the type as a String.
*
* @param typeElement The type element
* @return The class name
*/
String simpleBinaryNameFor(TypeElement typeElement) {
Name elementBinaryName = elementUtils.getBinaryName(typeElement);
PackageElement packageElement = elementUtils.getPackageOf(typeElement);
String packageName = packageElement.getQualifiedName().toString();
return elementBinaryName.toString().replaceFirst(packageName + "\\.", "");
}
/**
* Resolves a setter method for a field.
*
* @param field The field
* @return An optional setter method
*/
Optional findGetterMethodFor(Element field) {
// FIXME refine this to discover one of possible overloaded methods with correct signature (i.e. single arg of field type)
TypeElement typeElement = classElementFor(field);
if (typeElement == null) {
return Optional.empty();
}
String getterName = getterNameFor(field);
List extends Element> elements = typeElement.getEnclosedElements();
List methods = ElementFilter.methodsIn(elements);
return methods.stream()
.filter(method -> {
String methodName = method.getSimpleName().toString();
if (getterName.equals(methodName)) {
Set modifiers = method.getModifiers();
return
// it's not static
!modifiers.contains(STATIC)
// it's either public or package visibility
&& modifiers.contains(PUBLIC)
|| !(modifiers.contains(PRIVATE) || modifiers.contains(PROTECTED));
}
return false;
})
.findFirst();
}
/**
* Resolves a setter method for a field.
*
* @param field The field
* @return An optional setter method
*/
Optional findSetterMethodFor(Element field) {
String name = field.getSimpleName().toString();
if (field.asType().getKind() == TypeKind.BOOLEAN && name.length() > 2 && Character.isUpperCase(name.charAt(2))) {
name = name.replaceFirst("^(is)(.+)", "$2");
}
// FIXME refine this to discover one of possible overloaded methods with correct signature (i.e. single arg of field type)
TypeElement typeElement = classElementFor(field);
if (typeElement == null) {
return Optional.empty();
}
String setterName = setterNameFor(name);
List extends Element> elements = typeElement.getEnclosedElements();
List methods = ElementFilter.methodsIn(elements);
return methods.stream()
.filter(method -> {
String methodName = method.getSimpleName().toString();
if (setterName.equals(methodName)) {
Set modifiers = method.getModifiers();
return
// it's not static
!modifiers.contains(STATIC)
// it's either public or package visibility
&& modifiers.contains(PUBLIC)
|| !(modifiers.contains(PRIVATE) || modifiers.contains(PROTECTED));
}
return false;
})
.findFirst();
}
/**
* The name of a getter for the given field.
*
* @param field The field in question
* @return The getter name
*/
String getterNameFor(Element field) {
String methodNamePrefix = "get";
if (field.asType().getKind() == TypeKind.BOOLEAN) {
methodNamePrefix = "is";
}
return methodNamePrefix + NameUtils.capitalize(field.getSimpleName().toString());
}
/**
* The name of a setter for the given field name.
*
* @param fieldName The field name
* @return The setter name
*/
String setterNameFor(String fieldName) {
return "set" + NameUtils.capitalize(fieldName);
}
/**
* The constructor inject for the given class element.
*
* @param classElement The class element
* @param annotationUtils The annotation utilities
* @return The constructor
*/
@Nullable
@SuppressWarnings("java:S1119")
public ExecutableElement concreteConstructorFor(TypeElement classElement, AnnotationUtils annotationUtils) {
if (JavaModelUtils.isRecord(classElement)) {
final List constructors = ElementFilter
.constructorsIn(classElement.getEnclosedElements());
Optional element = findAnnotatedConstructor(annotationUtils, constructors);
if (element.isPresent()) {
return element.get();
} else {
if (RECORD_COMPONENTS_METHODS != null) {
List recordComponents = ReflectionUtils.invokeMethod(classElement, RECORD_COMPONENTS_METHODS);
if (recordComponents != null) {
// pick the constructor with the same number of arguments
// and where the types match the record components
constructorSearch: for (ExecutableElement constructor : constructors) {
List extends VariableElement> parameters = constructor.getParameters();
if (parameters.size() == recordComponents.size()) {
for (int i = 0; i < parameters.size(); i++) {
VariableElement vt = parameters.get(i);
Element rct = recordComponents.get(i);
TypeMirror leftType = typeUtils.erasure(vt.asType());
TypeMirror rightType = typeUtils.erasure(rct.asType());
if (!leftType.equals(rightType)) {
// types don't match, continue searching constructors
continue constructorSearch;
}
}
} else {
continue;
}
return constructor;
}
}
}
// with records the record constructor is always the last constructor
return constructors.get(constructors.size() - 1);
}
} else {
List constructors = findNonPrivateConstructors(classElement);
if (constructors.isEmpty()) {
return null;
}
if (constructors.size() == 1) {
return constructors.get(0);
}
Optional element = findAnnotatedConstructor(annotationUtils, constructors);
if (!element.isPresent()) {
element = constructors.stream().filter(ctor ->
ctor.getModifiers().contains(PUBLIC)
).findFirst();
}
return element.orElse(null);
}
}
private Optional findAnnotatedConstructor(AnnotationUtils annotationUtils, List constructors) {
return constructors.stream().filter(ctor -> {
final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(ctor);
return annotationMetadata.hasStereotype(AnnotationUtil.INJECT) || annotationMetadata.hasStereotype(Creator.class);
}
).findFirst();
}
/**
* The static method or Kotlin companion method to execute to
* construct the given class element.
*
* @param classElement The class element
* @param annotationUtils The annotation utilities
* @return The creator method
*/
@Nullable
public ExecutableElement staticCreatorFor(TypeElement classElement, AnnotationUtils annotationUtils) {
List creators = findNonPrivateStaticCreators(classElement, annotationUtils);
if (creators.isEmpty()) {
return null;
}
if (creators.size() == 1) {
return creators.get(0);
}
//Can be multiple static @Creator methods. Prefer one with args here. The no arg method (if present) will
//be picked up by staticDefaultCreatorFor
List withArgs = creators.stream().filter(method -> !method.getParameters().isEmpty()).collect(Collectors.toList());
if (withArgs.size() == 1) {
return withArgs.get(0);
} else {
creators = withArgs;
}
return creators.stream().filter(method -> method.getModifiers().contains(PUBLIC)).findFirst().orElse(null);
}
/**
* @param classElement The class element
* @return True if the element has a non private 0 arg constructor
*/
public ExecutableElement defaultConstructorFor(TypeElement classElement) {
List constructors = findNonPrivateConstructors(classElement)
.stream().filter(ctor -> ctor.getParameters().isEmpty()).collect(Collectors.toList());
if (constructors.isEmpty()) {
return null;
}
if (constructors.size() == 1) {
return constructors.get(0);
}
return constructors.stream().filter(method -> method.getModifiers().contains(PUBLIC)).findFirst().orElse(null);
}
/**
* @param classElement The class element
* @param annotationUtils The annotation utils
* @return A static creator with no args, or null
*/
public ExecutableElement defaultStaticCreatorFor(TypeElement classElement, AnnotationUtils annotationUtils) {
List creators = findNonPrivateStaticCreators(classElement, annotationUtils)
.stream().filter(ctor -> ctor.getParameters().isEmpty()).collect(Collectors.toList());
if (creators.isEmpty()) {
return null;
}
if (creators.size() == 1) {
return creators.get(0);
}
return creators.stream().filter(method -> method.getModifiers().contains(PUBLIC)).findFirst().orElse(null);
}
/**
* Return whether the given element is the java.lang.Object class.
*
* @param element The element
* @return True if it is java.lang.Object
*/
public boolean isObjectClass(TypeElement element) {
return element.getSuperclass().getKind() == NONE;
}
/**
* @param classElement The {@link TypeElement}
* @return A list of {@link ExecutableElement}
*/
private List findNonPrivateConstructors(TypeElement classElement) {
List ctors =
ElementFilter.constructorsIn(classElement.getEnclosedElements());
return ctors.stream()
.filter(ctor -> !ctor.getModifiers().contains(PRIVATE))
.collect(Collectors.toList());
}
private List findNonPrivateStaticCreators(TypeElement classElement, AnnotationUtils annotationUtils) {
List extends Element> enclosedElements = classElement.getEnclosedElements();
List staticCreators = ElementFilter.methodsIn(enclosedElements)
.stream()
.filter(method -> method.getModifiers().contains(STATIC))
.filter(method -> !method.getModifiers().contains(PRIVATE))
.filter(method -> typeUtils.isAssignable(typeUtils.erasure(method.getReturnType()), classElement.asType()))
.filter(method -> {
final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(method);
return annotationMetadata.hasStereotype(Creator.class);
})
.collect(Collectors.toList());
if (staticCreators.isEmpty()) {
TypeElement companionClass = ElementFilter.typesIn(enclosedElements)
.stream()
.filter(type -> type.getSimpleName().toString().equals("Companion"))
.filter(type -> type.getModifiers().contains(STATIC))
.findFirst().orElse(null);
if (companionClass != null) {
staticCreators = ElementFilter.methodsIn(companionClass.getEnclosedElements())
.stream()
.filter(method -> !method.getModifiers().contains(PRIVATE))
.filter(method -> method.getReturnType().equals(classElement.asType()))
.filter(method -> {
final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(method);
return annotationMetadata.hasStereotype(Creator.class);
})
.collect(Collectors.toList());
} else if (classElement.getKind() == ElementKind.ENUM) {
staticCreators = ElementFilter.methodsIn(classElement.getEnclosedElements())
.stream()
.filter(method -> method.getModifiers().contains(STATIC))
.filter(method -> !method.getModifiers().contains(PRIVATE))
.filter(method -> method.getReturnType().equals(classElement.asType()))
.filter(method -> method.getSimpleName().toString().equals("valueOf"))
.collect(Collectors.toList());
}
}
return staticCreators;
}
/**
* Obtains the super type element for a given type element.
*
* @param element The type element
* @return The super type element or null if none exists
*/
TypeElement superClassFor(TypeElement element) {
TypeMirror superclass = element.getSuperclass();
if (superclass.getKind() == TypeKind.NONE) {
return null;
}
DeclaredType kind = (DeclaredType) superclass;
return (TypeElement) kind.asElement();
}
/**
* Resolves a type name for the given name.
*
* @param type The type
* @return The type reference
*/
String resolveTypeName(TypeMirror type) {
TypeMirror typeMirror = resolveTypeReference(type);
if (typeMirror.getKind().isPrimitive()) {
return typeMirror.toString();
}
TypeElement typeElement = (TypeElement) typeUtils.asElement(typeMirror);
if (typeElement != null) {
return elementUtils.getBinaryName(typeElement).toString();
} else {
return typeUtils.erasure(typeMirror).toString();
}
}
/**
* Resolves a type reference for the given type mirror. A type reference is either a reference to the concrete
* {@link Class} or a String representing the type name.
*
* @param type The type
* @return The type reference
*/
TypeMirror resolveTypeReference(TypeMirror type) {
TypeKind typeKind = type.getKind();
if (typeKind.isPrimitive()) {
return type;
} else if (typeKind == TypeKind.DECLARED) {
DeclaredType dt = (DeclaredType) type;
if (dt.getTypeArguments().isEmpty()) {
return dt;
}
return typeUtils.erasure(type);
} else {
return typeUtils.erasure(type);
}
}
/**
* Returns whether an element is package private.
*
* @param element The element
* @return True if it is package provide
*/
public boolean isPackagePrivate(Element element) {
Set modifiers = element.getModifiers();
return !(modifiers.contains(PUBLIC)
|| modifiers.contains(PROTECTED)
|| modifiers.contains(PRIVATE));
}
/**
* @param aClass A class
* @return All the interfaces
*/
public Set getAllInterfaces(TypeElement aClass) {
SortedSet interfaces = new TreeSet<>((o1, o2) -> {
if (typeUtils.isSubtype(o1.asType(), o2.asType())) {
return -1;
} else if (o1.equals(o2)) {
return 0;
} else {
return 1;
}
});
return populateInterfaces(aClass, interfaces);
}
/**
* @param aClass A class
* @param interfaces The interfaces
* @return A set with the interfaces
*/
@SuppressWarnings("Duplicates")
private Set populateInterfaces(TypeElement aClass, Set interfaces) {
for (TypeMirror anInterface : aClass.getInterfaces()) {
final Element e = typeUtils.asElement(anInterface);
if (e instanceof TypeElement) {
final TypeElement te = (TypeElement) e;
if (!interfaces.contains(te)) {
interfaces.add(te);
populateInterfaces(te, interfaces);
}
}
}
if (aClass.getKind() != ElementKind.INTERFACE) {
TypeMirror superclass = aClass.getSuperclass();
while (superclass != null) {
final Element e = typeUtils.asElement(superclass);
if (e instanceof TypeElement) {
TypeElement superTypeElement = (TypeElement) e;
populateInterfaces(superTypeElement, interfaces);
superclass = superTypeElement.getSuperclass();
} else {
break;
}
}
}
return interfaces;
}
/**
* Return whether the given method or field is inherited but not public.
*
* @param concreteClass The concrete class
* @param declaringClass The declaring class of the field
* @param methodOrField The method or field
* @return True if it is inherited and not public
*/
boolean isInheritedAndNotPublic(TypeElement concreteClass, TypeElement declaringClass, Element methodOrField) {
PackageElement packageOfDeclaringClass = elementUtils.getPackageOf(declaringClass);
PackageElement packageOfConcreteClass = elementUtils.getPackageOf(concreteClass);
return declaringClass != concreteClass &&
!packageOfDeclaringClass.getQualifiedName().equals(packageOfConcreteClass.getQualifiedName())
&& (isProtected(methodOrField) || !isPublic(methodOrField));
}
/**
* Return whether the given method or field is inherited but not public.
*
* @param concreteClass The concrete class
* @param declaringClass The declaring class of the field
* @param methodOrField The method or field
* @return True if it is inherited and not public
*/
boolean isInheritedAndNotPublic(ClassElement concreteClass, ClassElement declaringClass, io.micronaut.inject.ast.Element methodOrField) {
String packageOfDeclaringClass = declaringClass.getPackageName();
String packageOfConcreteClass = concreteClass.getPackageName();
return declaringClass != concreteClass &&
!packageOfDeclaringClass.equals(packageOfConcreteClass)
&& (methodOrField.isProtected() || !methodOrField.isPublic());
}
/**
* Tests if candidate method is overridden from a given class or subclass.
*
* @param overridden the candidate overridden method
* @param classElement the type element that may contain the overriding method, either directly or in a subclass
* @param strict Whether to use strict checks for overriding and not include logic to handle method overloading
* @return the overriding method
*/
public Optional overridingOrHidingMethod(ExecutableElement overridden, TypeElement classElement, boolean strict) {
List methods = ElementFilter.methodsIn(elementUtils.getAllMembers(classElement));
for (ExecutableElement method : methods) {
if (strict) {
if (elementUtils.overrides(method, overridden, classElement)) {
return Optional.of(method);
}
} else {
if (!method.equals(overridden) &&
method.getSimpleName().equals(overridden.getSimpleName())) {
return Optional.of(method);
}
}
}
// might be looking for a package private & packages differ method in a superclass
// that is not visible to the most concrete subclass, really!
// e.g. see injectPackagePrivateMethod4() for SpareTire -> Tire -> RoundThing in Inject tck
// check the superclass until we reach Object, then bail out with empty if necessary.
TypeElement superClass = superClassFor(classElement);
if (superClass != null && !isObjectClass(superClass)) {
return overridingOrHidingMethod(overridden, superClass, strict);
}
return Optional.empty();
}
/**
* Return whether the element is private.
*
* @param element The element
* @return True if it is private
*/
boolean isPrivate(Element element) {
return element.getModifiers().contains(PRIVATE);
}
/**
* Return whether the element is protected.
*
* @param element The element
* @return True if it is protected
*/
boolean isProtected(Element element) {
return element.getModifiers().contains(PROTECTED);
}
/**
* Return whether the element is public.
*
* @param element The element
* @return True if it is public
*/
boolean isPublic(Element element) {
return element.getModifiers().contains(PUBLIC);
}
/**
* Return whether the element is abstract.
*
* @param element The element
* @return True if it is abstract
*/
boolean isAbstract(Element element) {
return element.getModifiers().contains(ABSTRACT);
}
/**
* Return whether the element is static.
*
* @param element The element
* @return True if it is static
*/
boolean isStatic(Element element) {
return element.getModifiers().contains(STATIC);
}
/**
* Return whether the element is final.
*
* @param element The element
* @return True if it is final
*/
boolean isFinal(Element element) {
return element.getModifiers().contains(FINAL);
}
/**
* Is the given type mirror an optional.
*
* @param mirror The mirror
* @return True if it is
*/
boolean isOptional(TypeMirror mirror) {
return typeUtils.erasure(mirror).toString().equals(Optional.class.getName());
}
/**
* The Java APT throws an internal exception {code com.sun.tools.javac.code.Symbol$CompletionFailure} if a class is missing from the classpath and {@link Element#getKind()} is called. This method
* handles exceptions when calling the getKind() method to avoid this scenario and should be used instead of {@link Element#getKind()}.
*
* @param element The element
* @param expected The expected kind
* @return The kind if it is resolvable and matches the expected kind
*/
public Optional resolveKind(Element element, ElementKind expected) {
final Optional elementKind = resolveKind(element);
if (elementKind.isPresent() && elementKind.get() == expected) {
return elementKind;
}
return Optional.empty();
}
/**
* The Java APT throws an internal exception {code com.sun.tools.javac.code.Symbol$CompletionFailure} if a class is missing from the classpath and {@link Element#getKind()} is called. This method
* handles exceptions when calling the getKind() method to avoid this scenario and should be used instead of {@link Element#getKind()}.
*
* @param element The element
* @return The kind if it is resolvable
*/
public Optional resolveKind(Element element) {
if (element != null) {
try {
final ElementKind kind = element.getKind();
return Optional.of(kind);
} catch (Exception e) {
// ignore and fall through to empty
}
}
return Optional.empty();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy