/*
 * 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 static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PROTECTED;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.ARRAY;
import static javax.lang.model.type.TypeKind.ERROR;
import static javax.lang.model.type.TypeKind.NONE;
import static javax.lang.model.type.TypeKind.VOID;

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.ClassUtils;
import io.micronaut.inject.processing.JavaModelUtils;

import edu.umd.cs.findbugs.annotations.Nullable;
import javax.inject.Inject;
import javax.lang.model.element.*;
import javax.lang.model.type.*;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

Provides utility method for working with the annotation processor AST.
Author:Graeme Rocher
Since:1.0
/** * Provides utility method for working with the annotation processor AST. * * @author Graeme Rocher * @since 1.0 */
@Internal public class ModelUtils { private final Elements elementUtils; private final Types typeUtils;
Params:
/** * @param elementUtils The {@link Elements} * @param typeUtils The {@link Types} */
protected ModelUtils(Elements elementUtils, Types typeUtils) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; }
Returns:The type utilities
/** * @return The type utilities */
public Types getTypeUtils() { return typeUtils; }
Obtains the TypeElement for an given element.
Params:
  • element – The element
Returns:The TypeElement
/** * 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.
Params:
  • typeElement – The type element
Returns:The class name
/** * 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.
Params:
  • field – The field
Returns:An optional setter method
/** * Resolves a setter method for a field. * * @param field The field * @return An optional setter method */
Optional<ExecutableElement> 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<ExecutableElement> methods = ElementFilter.methodsIn(elements); return methods.stream() .filter(method -> { String methodName = method.getSimpleName().toString(); if (getterName.equals(methodName)) { Set<Modifier> 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.
Params:
  • field – The field
Returns:An optional setter method
/** * Resolves a setter method for a field. * * @param field The field * @return An optional setter method */
Optional<ExecutableElement> 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<ExecutableElement> methods = ElementFilter.methodsIn(elements); return methods.stream() .filter(method -> { String methodName = method.getSimpleName().toString(); if (setterName.equals(methodName)) { Set<Modifier> 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.
Params:
  • field – The field in question
Returns:The getter name
/** * 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.
Params:
  • fieldName – The field name
Returns:The setter name
/** * 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.
Params:
  • classElement – The class element
  • annotationUtils – The annotation utilities
Returns:The constructor
/** * The constructor inject for the given class element. * * @param classElement The class element * @param annotationUtils The annotation utilities * @return The constructor */
@Nullable public ExecutableElement concreteConstructorFor(TypeElement classElement, AnnotationUtils annotationUtils) { List<ExecutableElement> constructors = findNonPrivateConstructors(classElement); if (constructors.isEmpty()) { return null; } if (constructors.size() == 1) { return constructors.get(0); } Optional<ExecutableElement> element = constructors.stream().filter(ctor -> { final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(ctor); return annotationMetadata.hasStereotype(Inject.class) || annotationMetadata.hasStereotype(Creator.class); } ).findFirst(); if (!element.isPresent()) { element = constructors.stream().filter(ctor -> ctor.getModifiers().contains(PUBLIC) ).findFirst(); } return element.orElse(null); }
The static method or Kotlin companion method to execute to construct the given class element.
Params:
  • classElement – The class element
  • annotationUtils – The annotation utilities
Returns:The creator method
/** * 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<ExecutableElement> 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<ExecutableElement> 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); }
Params:
  • classElement – The class element
Returns:True if the element has a non private 0 arg constructor
/** * @param classElement The class element * @return True if the element has a non private 0 arg constructor */
public ExecutableElement defaultConstructorFor(TypeElement classElement) { List<ExecutableElement> 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); }
Params:
  • classElement – The class element
  • annotationUtils – The annotation utils
Returns:A static creator with no args, or 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<ExecutableElement> 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); }
Params:
Returns:A list of ExecutableElement
/** * @param classElement The {@link TypeElement} * @return A list of {@link ExecutableElement} */
private List<ExecutableElement> findNonPrivateConstructors(TypeElement classElement) { List<ExecutableElement> ctors = ElementFilter.constructorsIn(classElement.getEnclosedElements()); return ctors.stream() .filter(ctor -> !ctor.getModifiers().contains(PRIVATE)) .collect(Collectors.toList()); } private List<ExecutableElement> findNonPrivateStaticCreators(TypeElement classElement, AnnotationUtils annotationUtils) { List<? extends Element> enclosedElements = classElement.getEnclosedElements(); List<ExecutableElement> 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; }
Finds a no argument method of the given name.
Params:
  • classElement – The class element
  • methodName – The method name
Returns:The executable element
/** * Finds a no argument method of the given name. * * @param classElement The class element * @param methodName The method name * @return The executable element */
Optional<ExecutableElement> findAccessibleNoArgumentInstanceMethod(TypeElement classElement, String methodName) { return ElementFilter.methodsIn(elementUtils.getAllMembers(classElement)) .stream().filter(m -> m.getSimpleName().toString().equals(methodName) && !isPrivate(m) && !isStatic(m)) .findFirst(); }
Obtains the class for a given primitive type name.
Params:
  • primitiveType – The primitive type name
Returns:The primtitive type class
/** * Obtains the class for a given primitive type name. * * @param primitiveType The primitive type name * @return The primtitive type class */
Class<?> classOfPrimitiveFor(String primitiveType) { return ClassUtils.getPrimitiveType(primitiveType).orElseThrow(() -> new IllegalArgumentException("Unknown primitive type: " + primitiveType)); }
Obtains the class for the given primitive type array.
Params:
  • primitiveType – The primitive type
Returns:The class
/** * Obtains the class for the given primitive type array. * * @param primitiveType The primitive type * @return The class */
Class<?> classOfPrimitiveArrayFor(String primitiveType) { return ClassUtils.arrayTypeForPrimitive(primitiveType) .orElseThrow(() -> new IllegalArgumentException("Unsupported primitive type " + primitiveType)); }
Obtains the super type element for a given type element.
Params:
  • element – The type element
Returns:The super type element or null if none exists
/** * 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(); }
Params:
Returns:The resolved type reference or the qualified name for the type element
/** * @param typeElement The {@link TypeElement} * @return The resolved type reference or the qualified name for the type element */
Object resolveTypeReference(TypeElement typeElement) { TypeMirror type = typeElement.asType(); if (type != null) { return resolveTypeReference(type); } else { return typeElement.getQualifiedName().toString(); } }
Return whether the given element is the java.lang.Object class.
Params:
  • element – The element
Returns:True if it is java.lang.Object
/** * Return whether the given element is the java.lang.Object class. * * @param element The element * @return True if it is java.lang.Object */
boolean isObjectClass(TypeElement element) { return element.getSuperclass().getKind() == NONE; }
Resolves a type reference for the given element. A type reference is either a reference to the concrete Class or a String representing the type name.
Params:
  • element – The element
Returns:The type reference
/** * Resolves a type reference for the given element. A type reference is either a reference to the concrete * {@link Class} or a String representing the type name. * * @param element The element * @return The type reference */
@Nullable Object resolveTypeReference(Element element) { if (element instanceof TypeElement) { TypeElement typeElement = (TypeElement) element; return resolveTypeReferenceForTypeElement(typeElement); } return null; }
Resolves a type reference for the given type element. A type reference is either a reference to the concrete Class or a String representing the type name.
Params:
  • typeElement – The type
Returns:The type reference
/** * Resolves a type reference for the given type element. A type reference is either a reference to the concrete * {@link Class} or a String representing the type name. * * @param typeElement The type * @return The type reference */
String resolveTypeReferenceForTypeElement(TypeElement typeElement) { return JavaModelUtils.getClassName(typeElement); }
Resolves a type name for the given name.
Params:
  • type – The type
Returns:The type reference
/** * Resolves a type name for the given name. * * @param type The type * @return The type reference */
String resolveTypeName(TypeMirror type) { Object reference = resolveTypeReference(type); if (reference instanceof Class) { return ((Class) reference).getName(); } return reference.toString(); }
Resolves a type reference for the given type mirror. A type reference is either a reference to the concrete Class or a String representing the type name.
Params:
  • type – The type
Returns:The type reference
/** * 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 */
Object resolveTypeReference(TypeMirror type) { Object result = Void.TYPE; if (type.getKind().isPrimitive()) { result = resolvePrimitiveTypeReference(type); } else if (type.getKind() == ARRAY) { ArrayType arrayType = (ArrayType) type; TypeMirror componentType = arrayType.getComponentType(); if (componentType.getKind().isPrimitive()) { result = classOfPrimitiveArrayFor(resolvePrimitiveTypeReference(componentType).getName()); } else { final TypeMirror erased = typeUtils.erasure(componentType); final Element e = typeUtils.asElement(erased); if (e instanceof TypeElement) { result = resolveTypeReferenceForTypeElement((TypeElement) e) + "[]"; } } } else if (type.getKind() != VOID && type.getKind() != ERROR) { final TypeMirror erased = typeUtils.erasure(type); final Element element = typeUtils.asElement(erased); if (element instanceof TypeElement) { TypeElement te = (TypeElement) element; result = resolveTypeReferenceForTypeElement(te); } } return result; }
Returns whether an element is package private.
Params:
  • element – The element
Returns:True if it is package provide
/** * Returns whether an element is package private. * * @param element The element * @return True if it is package provide */
boolean isPackagePrivate(Element element) { Set<Modifier> modifiers = element.getModifiers(); return !(modifiers.contains(PUBLIC) || modifiers.contains(PROTECTED) || modifiers.contains(PRIVATE)); }
Return whether the given method or field is inherited but not public.
Params:
  • concreteClass – The concrete class
  • declaringClass – The declaring class of the field
  • methodOrField – The method or field
Returns:True if it is inherited and not public
/** * 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)); }
Tests if candidate method is overridden from a given class or subclass.
Params:
  • overridden – the candidate overridden method
  • classElement – the type element that may contain the overriding method, either directly or in a subclass
  • strict – Whether to use strict checks for overriding and not include logic to handle method overloading
Returns:the overriding method
/** * 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 */
Optional<ExecutableElement> overridingOrHidingMethod(ExecutableElement overridden, TypeElement classElement, boolean strict) { List<ExecutableElement> 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.
Params:
  • element – The element
Returns:True if it is private
/** * 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.
Params:
  • element – The element
Returns:True if it is protected
/** * 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.
Params:
  • element – The element
Returns:True if it is public
/** * 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.
Params:
  • element – The element
Returns:True if it is abstract
/** * 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.
Params:
  • element – The element
Returns:True if it is static
/** * 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.
Params:
  • element – The element
Returns:True if it is final
/** * 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.
Params:
  • mirror – The mirror
Returns:True if it is
/** * 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()); } private Class resolvePrimitiveTypeReference(TypeMirror type) { Class result; if (type instanceof DeclaredType) { DeclaredType dt = (DeclaredType) type; result = classOfPrimitiveFor(dt.asElement().getSimpleName().toString()); } else { if (type instanceof PrimitiveType) { PrimitiveType pt = (PrimitiveType) type; TypeKind kind = pt.getKind(); switch (kind) { case VOID: return void.class; case INT: return int.class; case BYTE: return byte.class; case CHAR: return char.class; case LONG: return long.class; case FLOAT: return float.class; case SHORT: return short.class; case DOUBLE: return double.class; case BOOLEAN: return boolean.class; default: result = classOfPrimitiveFor(type.toString()); } } else { result = classOfPrimitiveFor(type.toString()); } } return result; }
The Java APT throws an internal exception {code com.sun.tools.javac.code.Symbol$CompletionFailure} if a class is missing from the classpath and Element.getKind() is called. This method handles exceptions when calling the getKind() method to avoid this scenario and should be used instead of Element.getKind().
Params:
  • element – The element
  • expected – The expected kind
Returns:The kind if it is resolvable and matches the expected kind
/** * 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<ElementKind> resolveKind(Element element, ElementKind expected) { final Optional<ElementKind> 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 Element.getKind() is called. This method handles exceptions when calling the getKind() method to avoid this scenario and should be used instead of Element.getKind().
Params:
  • element – The element
Returns:The kind if it is resolvable
/** * 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<ElementKind> 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(); } }