io.micronaut.annotation.processing.ModelUtils Maven / Gradle / Ivy
/*
* Copyright 2017-2019 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
*
* 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 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.Internal;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.inject.processing.JavaModelUtils;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
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.Objects;
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
*/
@Internal
public class ModelUtils {
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}
*/
final TypeElement classElementFor(Element element) {
while (!(JavaModelUtils.isClass(element) || JavaModelUtils.isInterface(element))) {
element = element.getEnclosingElement();
}
return (TypeElement) element;
}
/**
* 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 findSetterMethodFor(Element field) {
String name = field.getSimpleName().toString();
if (field.asType().getKind() == TypeKind.BOOLEAN) {
if (name.length() > 2 && Character.isUpperCase(name.charAt(2))) {
name = name.replaceFirst("^(is)(.+)", "$2");
}
}
String setterName = setterNameFor(name);
// FIXME refine this to discover one of possible overloaded methods with correct signature (i.e. single arg of field type)
TypeElement typeElement = classElementFor(field);
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 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
* @return The constructor
*/
@Nullable
ExecutableElement concreteConstructorFor(TypeElement classElement) {
List constructors = findNonPrivateConstructors(classElement);
if (constructors.isEmpty()) {
return null;
}
if (constructors.size() == 1) {
return constructors.get(0);
}
Optional element = constructors.stream().filter(ctor ->
Objects.nonNull(ctor.getAnnotation(Inject.class))
).findFirst();
if (!element.isPresent()) {
element = constructors.stream().filter(ctor ->
ctor.getModifiers().contains(PUBLIC)
).findFirst();
}
return element.orElse(null);
}
/**
* @param classElement The {@link TypeElement}
* @return A list of {@link ExecutableElement}
*/
List findNonPrivateConstructors(TypeElement classElement) {
List ctors =
ElementFilter.constructorsIn(classElement.getEnclosedElements());
return ctors.stream()
.filter(ctor -> !ctor.getModifiers().contains(PRIVATE))
.collect(Collectors.toList());
}
/**
* 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.
*
* @param primitiveType The primitive type
* @return The class
*/
Class> classOfPrimitiveArrayFor(String primitiveType) {
try {
switch (primitiveType) {
case "byte":
return Class.forName("[B");
case "int":
return Class.forName("[I");
case "short":
return Class.forName("[S");
case "long":
return Class.forName("[J");
case "float":
return Class.forName("[F");
case "double":
return Class.forName("[D");
case "char":
return Class.forName("[C");
case "boolean":
return Class.forName("[Z");
default:
// this can never occur
throw new IllegalArgumentException("Unsupported primitive type " + primitiveType);
}
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
}
/**
* 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();
}
/**
* @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.
*
* @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
* {@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
* {@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.
*
* @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
* {@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.
*
* @param element The element
* @return True if it is package provide
*/
boolean isPackagePrivate(Element element) {
Set 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.
*
* @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.
*
* @param overridden the candidate overridden method
* @param classElement the type element that may contain the overriding method, either directly or in a subclass
* @return the overriding method
*/
Optional overridingOrHidingMethod(ExecutableElement overridden, TypeElement classElement) {
List methods = ElementFilter.methodsIn(elementUtils.getAllMembers(classElement));
for (ExecutableElement method : methods) {
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);
}
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());
}
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy