All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.micronaut.annotation.processing.visitor.JavaClassElement Maven / Gradle / Ivy

/*
 * Copyright 2017-2022 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.visitor;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Creator;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.ast.ArrayableClassElement;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PackageElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.PropertyElementQuery;
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadata;
import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory;
import io.micronaut.inject.ast.annotation.MutableAnnotationMetadataDelegate;
import io.micronaut.inject.ast.utils.AstBeanPropertiesUtils;
import io.micronaut.inject.ast.utils.EnclosedElementsQuery;
import io.micronaut.inject.processing.JavaModelUtils;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.RecordComponentElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * A class element returning data from a {@link TypeElement}.
 *
 * @author James Kleeh
 * @author graemerocher
 * @author Denis Stepanov
 * @since 1.0
 */
@Internal
public class JavaClassElement extends AbstractJavaElement implements ArrayableClassElement {
    private static final String KOTLIN_METADATA = "kotlin.Metadata";
    private static final String PREFIX_IS = "is";
    protected final TypeElement classElement;
    protected final int arrayDimensions;

    @Nullable
    // Not null means raw type definition: "List myMethod()"
    // Null value means a class definition: "class List {}"
    final List typeArguments;

    private final boolean isTypeVariable;
    private List beanProperties;
    private String simpleName;
    private String name;
    private String packageName;
    @Nullable
    private Map resolvedTypeArguments;
    @Nullable
    private Map> resolvedAllTypeArguments;
    @Nullable
    private ClassElement resolvedSuperType;
    @Nullable
    private List resolvedInterfaces;
    private final JavaEnclosedElementsQuery enclosedElementsQuery = new JavaEnclosedElementsQuery(false);
    private final JavaEnclosedElementsQuery sourceEnclosedElementsQuery = new JavaEnclosedElementsQuery(true);
    @Nullable
    private ElementAnnotationMetadata elementTypeAnnotationMetadata;
    @Nullable
    private ClassElement theType;
    @Nullable
    private AnnotationMetadata annotationMetadata;

    /**
     * @param nativeType                The native type
     * @param annotationMetadataFactory The annotation metadata factory
     * @param visitorContext            The visitor context
     */
    @Internal
    public JavaClassElement(JavaNativeElement.Class nativeType, ElementAnnotationMetadataFactory annotationMetadataFactory, JavaVisitorContext visitorContext) {
        this(nativeType, annotationMetadataFactory, visitorContext, null, null, 0, false);
    }

    /**
     * @param nativeType                The native type
     * @param annotationMetadataFactory The annotation metadata factory
     * @param visitorContext            The visitor context
     * @param typeArguments             The declared type arguments
     * @param resolvedTypeArguments     The resolvedTypeArguments
     */
    JavaClassElement(JavaNativeElement.Class nativeType,
                     ElementAnnotationMetadataFactory annotationMetadataFactory,
                     JavaVisitorContext visitorContext,
                     List typeArguments,
                     @Nullable
                     Map resolvedTypeArguments) {
        this(nativeType, annotationMetadataFactory, visitorContext, typeArguments, resolvedTypeArguments, 0, false);
    }

    /**
     * @param nativeType                The native type
     * @param annotationMetadataFactory The annotation metadata factory
     * @param visitorContext            The visitor context
     * @param typeArguments             The declared type arguments
     * @param resolvedTypeArguments     The resolvedTypeArguments
     * @param arrayDimensions           The number of array dimensions
     */
    JavaClassElement(JavaNativeElement.Class nativeType,
                     ElementAnnotationMetadataFactory annotationMetadataFactory,
                     JavaVisitorContext visitorContext,
                     List typeArguments,
                     @Nullable
                     Map resolvedTypeArguments,
                     int arrayDimensions) {
        this(nativeType, annotationMetadataFactory, visitorContext, typeArguments, resolvedTypeArguments, arrayDimensions, false);
    }

    /**
     * @param nativeType                The {@link TypeElement}
     * @param annotationMetadataFactory The annotation metadata factory
     * @param visitorContext            The visitor context
     * @param typeArguments             The declared type arguments
     * @param resolvedTypeArguments     The resolvedTypeArguments
     * @param arrayDimensions           The number of array dimensions
     * @param isTypeVariable            Is the type a type variable
     */
    JavaClassElement(JavaNativeElement.Class nativeType,
                     ElementAnnotationMetadataFactory annotationMetadataFactory,
                     JavaVisitorContext visitorContext,
                     List typeArguments,
                     @Nullable
                     Map resolvedTypeArguments,
                     int arrayDimensions,
                     boolean isTypeVariable) {
        super(nativeType, annotationMetadataFactory, visitorContext);
        this.classElement = nativeType.element();
        this.typeArguments = typeArguments;
        this.resolvedTypeArguments = resolvedTypeArguments;
        this.arrayDimensions = arrayDimensions;
        this.isTypeVariable = isTypeVariable;
    }

    @Override
    public JavaNativeElement.Class getNativeType() {
        return (JavaNativeElement.Class) super.getNativeType();
    }

    @Override
    protected JavaClassElement copyThis() {
        return new JavaClassElement(getNativeType(), elementAnnotationMetadataFactory, visitorContext, typeArguments, resolvedTypeArguments, arrayDimensions);
    }

    @Override
    public ClassElement withTypeArguments(Map newTypeArguments) {
        return new JavaClassElement(getNativeType(), elementAnnotationMetadataFactory, visitorContext, typeArguments, newTypeArguments, arrayDimensions);
    }

    @Override
    public ClassElement withAnnotationMetadata(AnnotationMetadata annotationMetadata) {
        return (ClassElement) super.withAnnotationMetadata(annotationMetadata);
    }

    @Override
    protected MutableAnnotationMetadataDelegate getAnnotationMetadataToWrite() {
        if (getNativeType().typeMirror() == null) {
            return super.getAnnotationMetadataToWrite();
        }
        return getTypeAnnotationMetadata();
    }

    @Override
    public AnnotationMetadata getAnnotationMetadata() {
        if (presetAnnotationMetadata != null) {
            return presetAnnotationMetadata;
        }
        if (annotationMetadata == null) {
            if (getNativeType().typeMirror() == null) {
                annotationMetadata = super.getAnnotationMetadata();
            } else {
                annotationMetadata = new AnnotationMetadataHierarchy(true, super.getAnnotationMetadata(), getTypeAnnotationMetadata());
            }
        }
        return annotationMetadata;
    }

    @Override
    public MutableAnnotationMetadataDelegate getTypeAnnotationMetadata() {
        if (elementTypeAnnotationMetadata == null) {
            elementTypeAnnotationMetadata = elementAnnotationMetadataFactory.buildTypeAnnotations(this);
        }
        return elementTypeAnnotationMetadata;
    }

    @Override
    public boolean isTypeVariable() {
        return isTypeVariable;
    }

    @Override
    public String toString() {
        return getName();
    }

    @Override
    public boolean isInner() {
        return classElement.getNestingKind().isNested();
    }

    @Override
    public boolean isRecord() {
        return JavaModelUtils.isRecord(classElement);
    }

    @Override
    public boolean isPrimitive() {
        return ClassUtils.getPrimitiveType(getName()).isPresent();
    }

    @Override
    public Collection getInterfaces() {
        if (resolvedInterfaces == null) {
            resolvedInterfaces = classElement.getInterfaces().stream().map(mirror -> newClassElement(mirror, getTypeArguments())).toList();
        }
        return resolvedInterfaces;
    }

    @Override
    public Optional getSuperType() {
        if (resolvedSuperType == null) {
            final TypeMirror superclass = classElement.getSuperclass();
            if (superclass == null) {
                return Optional.empty();
            }
            final Element element = visitorContext.getTypes().asElement(superclass);
            if (element instanceof TypeElement superElement) {
                if (Object.class.getName().equals(superElement.getQualifiedName().toString())) {
                    return Optional.empty();
                }
                resolvedSuperType = newClassElement(superclass, getTypeArguments());
            }
        }
        return Optional.ofNullable(resolvedSuperType);
    }

    @Override
    public boolean isAbstract() {
        return classElement.getModifiers().contains(Modifier.ABSTRACT);
    }

    @Override
    public boolean isInterface() {
        return JavaModelUtils.isInterface(classElement);
    }

    @Override
    public List getBeanProperties() {
        if (beanProperties == null) {
            beanProperties = getBeanProperties(PropertyElementQuery.of(this));
        }
        return Collections.unmodifiableList(beanProperties);
    }

    @Override
    public List getBeanProperties(PropertyElementQuery propertyElementQuery) {
        if (isRecord()) {
            return AstBeanPropertiesUtils.resolveBeanProperties(propertyElementQuery,
                    this,
                    this::getRecordMethods,
                    this::getRecordFields,
                    true,
                    Collections.emptySet(),
                    methodElement -> Optional.empty(),
                    methodElement -> Optional.empty(),
                    this::mapToPropertyElement);
        }
        Function> customReaderPropertyNameResolver = methodElement -> Optional.empty();
        Function> customWriterPropertyNameResolver = methodElement -> Optional.empty();
        if (isKotlinClass(getNativeType().element())) {
            Set isProperties = getEnclosedElements(ElementQuery.ALL_METHODS)
                    .stream()
                    .map(io.micronaut.inject.ast.Element::getName)
                    .filter(method -> method.startsWith(PREFIX_IS))
                    .collect(Collectors.toSet());
            if (!isProperties.isEmpty()) {
                customReaderPropertyNameResolver = methodElement -> {
                    String methodName = methodElement.getSimpleName();
                    if (methodName.startsWith(PREFIX_IS)) {
                        return Optional.of(methodName);
                    }
                    return Optional.empty();
                };
                customWriterPropertyNameResolver = methodElement -> {
                    String methodName = methodElement.getSimpleName();
                    String propertyName = NameUtils.getPropertyNameForSetter(methodName);
                    String isPropertyName = PREFIX_IS + NameUtils.capitalize(propertyName);
                    if (isProperties.contains(isPropertyName)) {
                        return Optional.of(isPropertyName);
                    }
                    return Optional.empty();
                };
            }
        }
        return AstBeanPropertiesUtils.resolveBeanProperties(propertyElementQuery,
                this,
                () -> getEnclosedElements(ElementQuery.ALL_METHODS),
                () -> getEnclosedElements(ElementQuery.ALL_FIELDS),
                false,
                Collections.emptySet(),
                customReaderPropertyNameResolver,
                customWriterPropertyNameResolver,
                this::mapToPropertyElement);
    }

    private JavaPropertyElement mapToPropertyElement(AstBeanPropertiesUtils.BeanPropertyData value) {
        return new JavaPropertyElement(
                JavaClassElement.this,
                value.type,
                value.readAccessKind == null ? null : value.getter,
                value.writeAccessKind == null ? null : value.setter,
                value.field,
                elementAnnotationMetadataFactory,
                value.propertyName,
                value.readAccessKind == null ? PropertyElement.AccessKind.METHOD : PropertyElement.AccessKind.valueOf(value.readAccessKind.name()),
                value.writeAccessKind == null ? PropertyElement.AccessKind.METHOD : PropertyElement.AccessKind.valueOf(value.writeAccessKind.name()),
                value.isExcluded,
                visitorContext);
    }

    private List getRecordMethods() {
        Set recordComponents = new HashSet<>();
        List methodElements = new ArrayList<>();
        if (JavaModelUtils.isRecord(classElement)) {
            for (Element enclosedElement : classElement.getEnclosedElements()) {
                if (JavaModelUtils.isRecordComponent(enclosedElement) || enclosedElement instanceof ExecutableElement) {
                    if (enclosedElement.getKind() == ElementKind.CONSTRUCTOR) {
                        continue;
                    }
                    String name = enclosedElement.getSimpleName().toString();
                    if (enclosedElement instanceof ExecutableElement executableElement) {
                        if (recordComponents.contains(name)) {
                            methodElements.add(
                                new JavaMethodElement(
                                    JavaClassElement.this,
                                    new JavaNativeElement.Method(executableElement),
                                    elementAnnotationMetadataFactory,
                                    visitorContext)
                            );
                        }
                    } else if (enclosedElement instanceof VariableElement) {
                        recordComponents.add(name);
                    }
                }
            }
        }
        return methodElements;
    }

    private List getRecordFields() {
        List fieldElements = new ArrayList<>();
        if (JavaModelUtils.isRecord(classElement)) {
            for (Element enclosedElement : classElement.getEnclosedElements()) {
                if (!JavaModelUtils.isRecordComponent(enclosedElement) && enclosedElement instanceof VariableElement variableElement) {
                    fieldElements.add(
                        new JavaFieldElement(
                            JavaClassElement.this,
                            new JavaNativeElement.Variable(variableElement),
                            elementAnnotationMetadataFactory,
                            visitorContext)
                    );
                }
            }
        }
        return fieldElements;
    }

    private boolean isKotlinClass(Element element) {
        return element.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().asElement().toString().equals(KOTLIN_METADATA));
    }

    @Override
    public  List getEnclosedElements(@NonNull ElementQuery query) {
        return enclosedElementsQuery.getEnclosedElements(this, query);
    }

    /**
     * This method will produce the elements just like {@link #getEnclosedElements(ElementQuery)}
     * but the elements are constructed as the source ones.
     * {@link io.micronaut.inject.ast.ElementFactory#newSourceMethodElement(ClassElement, Object, ElementAnnotationMetadataFactory)}.
     *
     * @param query The query
     * @param    The element type
     * @return The list of elements
     */
    public final  List getSourceEnclosedElements(@NonNull ElementQuery query) {
        return sourceEnclosedElementsQuery.getEnclosedElements(this, query);
    }

    @Override
    public boolean isArray() {
        return arrayDimensions > 0;
    }

    @Override
    public int getArrayDimensions() {
        return arrayDimensions;
    }

    @Override
    public ClassElement withArrayDimensions(int arrayDimensions) {
        if (arrayDimensions == this.arrayDimensions) {
            return this;
        }
        return new JavaClassElement(getNativeType(), elementAnnotationMetadataFactory, visitorContext, typeArguments, resolvedTypeArguments, arrayDimensions, false);
    }

    @Override
    public String getSimpleName() {
        if (simpleName == null) {
            simpleName = JavaModelUtils.getClassNameWithoutPackage(classElement);
        }
        return simpleName;
    }

    @Override
    public String getName() {
        if (name == null) {
            name = JavaModelUtils.getClassName(classElement);
        }
        return name;
    }

    @Override
    public String getPackageName() {
        if (packageName == null) {
            packageName = JavaModelUtils.getPackageName(classElement);
        }
        return packageName;
    }

    @Override
    public PackageElement getPackage() {
        Element enclosingElement = classElement.getEnclosingElement();
        while (enclosingElement != null && enclosingElement.getKind() != ElementKind.PACKAGE) {
            enclosingElement = enclosingElement.getEnclosingElement();
        }
        if (enclosingElement instanceof javax.lang.model.element.PackageElement packageElement) {
            return new JavaPackageElement(
                    packageElement,
                    elementAnnotationMetadataFactory,
                    visitorContext
            );
        } else {
            return PackageElement.DEFAULT_PACKAGE;
        }
    }

    @Override
    public boolean isAssignable(String type) {
        if (getName().equals(type)) {
            return true; // Same type
        }
        TypeElement otherElement = visitorContext.getElements().getTypeElement(type);
        if (otherElement != null) {
            return isAssignable(otherElement);
        }
        return false;
    }

    @Override
    public boolean isAssignable(ClassElement type) {
        if (equals(type)) {
            return true; // Same type
        }
        if (type.isPrimitive()) {
            return isAssignable(type.getName());
        }
        if (type instanceof JavaClassElement javaClassElement) {
            return isAssignable(javaClassElement.getNativeType().element());
        }
        return isAssignable(type.getName());
    }

    @Override
    public Optional getOptionalValueType() {
        if (isAssignable(Optional.class)) {
            return getFirstTypeArgument().or(() -> visitorContext.getClassElement(Object.class));
        }
        if (isAssignable(OptionalLong.class)) {
            return visitorContext.getClassElement(Long.class);
        }
        if (isAssignable(OptionalDouble.class)) {
            return visitorContext.getClassElement(Double.class);
        }
        if (isAssignable(OptionalInt.class)) {
            return visitorContext.getClassElement(Integer.class);
        }
        return Optional.empty();
    }

    private boolean isAssignable(TypeElement otherElement) {
        Types types = visitorContext.getTypes();
        TypeMirror thisType = types.erasure(classElement.asType());
        TypeMirror thatType = types.erasure(otherElement.asType());
        return types.isAssignable(thisType, thatType);
    }

    @NonNull
    @Override
    @SuppressWarnings("java:S1119")
    public Optional getPrimaryConstructor() {
        if (JavaModelUtils.isRecord(classElement)) {
            Optional staticCreator = findStaticCreator();
            if (staticCreator.isPresent()) {
                return staticCreator;
            }
            if (isInner() && !isStatic()) {
                // only static inner classes can be constructed
                return Optional.empty();
            }
            List constructors = getAccessibleConstructors();
            Optional annotatedConstructor = constructors.stream()
                    .filter(c -> c.hasStereotype(AnnotationUtil.INJECT) || c.hasStereotype(Creator.class))
                    .findFirst();
            if (annotatedConstructor.isPresent()) {
                return annotatedConstructor.map(c -> c);
            }
            // with records the record constructor is always the last constructor
            List recordComponents = classElement.getRecordComponents();
            constructorSearch:
            for (ConstructorElement constructor : constructors) {
                ParameterElement[] parameters = constructor.getParameters();
                if (parameters.length == recordComponents.size()) {
                    for (int i = 0; i < parameters.length; i++) {
                        ParameterElement parameter = parameters[i];
                        RecordComponentElement rce = recordComponents.get(i);
                        VariableElement ve = ((JavaNativeElement.Variable) parameter.getNativeType()).element();
                        TypeMirror leftType = visitorContext.getTypes().erasure(ve.asType());
                        TypeMirror rightType = visitorContext.getTypes().erasure(rce.asType());
                        if (!leftType.equals(rightType)) {
                            // types don't match, continue searching constructors
                            continue constructorSearch;
                        }
                    }
                    return Optional.of(constructor);
                }
            }
            if (constructors.isEmpty()) {
                // constructor not accessible
                return Optional.empty();
            } else {
                return Optional.of(constructors.get(constructors.size() - 1));
            }
        }
        return ArrayableClassElement.super.getPrimaryConstructor();
    }

    @Override
    public List getAccessibleStaticCreators() {
        List staticCreators = new ArrayList<>(ArrayableClassElement.super.getAccessibleStaticCreators());
        if (!staticCreators.isEmpty()) {
            return staticCreators;
        }
        return visitorContext.getClassElement(getName() + "$Companion", elementAnnotationMetadataFactory)
                .filter(io.micronaut.inject.ast.Element::isStatic)
                .flatMap(typeElement -> typeElement.getEnclosedElements(ElementQuery.ALL_METHODS
                        .annotated(am -> am.hasStereotype(Creator.class))).stream().findFirst()
                )
                .filter(method -> !method.isPrivate() && method.getReturnType().equals(this))
                .stream().toList();
    }

    @Override
    public Optional getEnclosingType() {
        if (isInner()) {
            Element enclosingElement = this.classElement.getEnclosingElement();
            if (enclosingElement instanceof TypeElement typeElement) {
                return Optional.of(visitorContext.getElementFactory().newClassElement(
                        typeElement,
                        elementAnnotationMetadataFactory
                ));
            }
        }
        return Optional.empty();
    }

    @NonNull
    @Override
    public List getBoundGenericTypes() {
        if (typeArguments == null) {
            return Collections.emptyList();
        }
        return typeArguments.stream()
                .map(tm -> newClassElement(tm, getTypeArguments()))
                .toList();
    }

    @NonNull
    @Override
    public List getDeclaredGenericPlaceholders() {
        return classElement.getTypeParameters().stream()
                // we want the *declared* variables, so we don't pass in our genericsInfo.
                .map(tpe -> (GenericPlaceholderElement) newClassElement(tpe.asType(), Collections.emptyMap()))
                .toList();
    }

    @NonNull
    @Override
    public ClassElement getRawClassElement() {
        return visitorContext.getElementFactory().newClassElement(classElement, elementAnnotationMetadataFactory)
                .withArrayDimensions(getArrayDimensions());
    }

    @NonNull
    @Override
    public ClassElement withTypeArguments(@NonNull Collection typeArguments) {
        if (getTypeArguments().equals(typeArguments)) {
            return this;
        }
        Map boundByName = new LinkedHashMap<>();
        Iterator tpes = classElement.getTypeParameters().iterator();
        Iterator args = typeArguments.iterator();
        while (tpes.hasNext() && args.hasNext()) {
            ClassElement next = args.next();
            Object nativeType = next.getNativeType();
            if (nativeType instanceof Class aClass) {
                next = visitorContext.getClassElement(aClass).orElse(next);
            }
            boundByName.put(tpes.next().getSimpleName().toString(), next);
        }
        return withTypeArguments(boundByName);
    }

    @Override
    @NonNull
    public Map getTypeArguments() {
        if (resolvedTypeArguments == null) {
            resolvedTypeArguments = resolveTypeArguments(classElement, typeArguments);
        }
        return resolvedTypeArguments;
    }

    @NonNull
    @Override
    public Map> getAllTypeArguments() {
        if (resolvedAllTypeArguments == null) {
            resolvedAllTypeArguments = ArrayableClassElement.super.getAllTypeArguments();
        }
        return resolvedAllTypeArguments;
    }

    @Override
    public ClassElement getType() {
        if (theType == null) {
            if (getNativeType().typeMirror() == null) {
                theType = this;
            } else {
                // Strip the type mirror
                // This should eliminate type annotations
                theType = new JavaClassElement(new JavaNativeElement.Class(getNativeType().element()), elementAnnotationMetadataFactory, visitorContext, typeArguments, resolvedTypeArguments, arrayDimensions);
            }
        }
        return theType;
    }

    private final class JavaEnclosedElementsQuery extends EnclosedElementsQuery {

        private final boolean isSource;
        private List enclosedElements;

        private JavaEnclosedElementsQuery(boolean isSource) {
            this.isSource = isSource;
        }

        @Override
        protected boolean hasAnnotation(Element element, Class annotation) {
            return element.getAnnotation(annotation) != null;
        }

        @Override
        protected TypeElement getNativeClassType(ClassElement classElement) {
            return ((JavaClassElement) classElement).getNativeType().element();
        }

        @Override
        protected Element getNativeType(io.micronaut.inject.ast.Element element) {
            return ((AbstractJavaElement) element).getNativeType().element();
        }

        @Override
        protected String getElementName(Element element) {
            return element.getSimpleName().toString();
        }

        @Override
        protected Set getExcludedNativeElements(ElementQuery.Result result) {
            if (result.isExcludePropertyElements()) {
                Set excludeElements = new HashSet<>();
                for (PropertyElement excludePropertyElement : getBeanProperties()) {
                    excludePropertyElement.getReadMethod().ifPresent(methodElement -> excludeElements.add(((JavaNativeElement) methodElement.getNativeType()).element()));
                    excludePropertyElement.getWriteMethod().ifPresent(methodElement -> excludeElements.add(((JavaNativeElement) methodElement.getNativeType()).element()));
                    excludePropertyElement.getField().ifPresent(methodElement -> excludeElements.add(((JavaNativeElement) methodElement.getNativeType()).element()));
                }
                return excludeElements;
            }
            return Collections.emptySet();
        }

        @Override
        protected TypeElement getSuperClass(TypeElement classNode) {
            TypeMirror superclass = classNode.getSuperclass();
            if (superclass instanceof DeclaredType dt) {
                Element element = dt.asElement();
                if (element instanceof TypeElement typeElement) {
                    return typeElement;
                }
            }
            return null;
        }

        @Override
        protected Collection getInterfaces(TypeElement classNode) {
            List interfaces = classNode.getInterfaces();
            Collection result = new ArrayList<>(interfaces.size());
            for (TypeMirror ifaceMirror : interfaces) {
                final Element ifaceEl = visitorContext.getTypes().asElement(ifaceMirror);
                if (ifaceEl instanceof TypeElement element) {
                    result.add(element);
                }
            }
            return result;
        }

        @Override
        protected List getEnclosedElements(TypeElement classNode, ElementQuery.Result result, boolean includeAbstract) {
            List ee;
            if (classNode == classElement) {
                ee = getEnclosedElements();
            } else {
                ee = classNode.getEnclosedElements();
            }
            EnumSet elementKinds = getElementKind(result);
            List list = new ArrayList<>(ee.size());
            for (Element element : ee) {
                Set modifiers = element.getModifiers();
                if (elementKinds.contains(element.getKind()) && (includeAbstract || isNonAbstractMethod(modifiers))) {
                    list.add(element);
                }
            }
            return list;
        }

        private boolean isNonAbstractMethod(Set modifiers) {
            if (modifiers.contains(Modifier.DEFAULT)) {
                return true;
            }
            if (modifiers.contains(Modifier.PRIVATE)) {
                return true;
            }
            return !modifiers.contains(Modifier.ABSTRACT);
        }

        @Override
        protected boolean excludeClass(TypeElement classNode) {
            return classNode.getQualifiedName().toString().equals(Object.class.getName())
                    || classNode.getQualifiedName().toString().equals(Enum.class.getName());
        }

        @Override
        protected boolean isAbstractClass(TypeElement classNode) {
            return classNode.getModifiers().contains(Modifier.ABSTRACT);
        }

        @Override
        protected boolean isInterface(TypeElement classNode) {
            return classNode.getKind() == ElementKind.INTERFACE;
        }

        @Override
        protected io.micronaut.inject.ast.Element toAstElement(Element nativeType, Class elementType) {
            final JavaElementFactory elementFactory = visitorContext.getElementFactory();
            return switch (nativeType.getKind()) {
                case METHOD -> {
                    if (isSource) {
                        yield elementFactory.newSourceMethodElement(
                                JavaClassElement.this,
                                (ExecutableElement) nativeType,
                                elementAnnotationMetadataFactory
                        );
                    } else {
                        yield elementFactory.newMethodElement(
                                JavaClassElement.this,
                                (ExecutableElement) nativeType,
                                elementAnnotationMetadataFactory
                        );
                    }
                }
                case FIELD -> elementFactory.newFieldElement(
                        JavaClassElement.this,
                        (VariableElement) nativeType,
                        elementAnnotationMetadataFactory
                );
                case ENUM_CONSTANT -> elementFactory.newEnumConstantElement(
                        JavaClassElement.this,
                        (VariableElement) nativeType,
                        elementAnnotationMetadataFactory
                );
                case CONSTRUCTOR -> elementFactory.newConstructorElement(
                        JavaClassElement.this,
                        (ExecutableElement) nativeType,
                        elementAnnotationMetadataFactory
                );
                case CLASS, ENUM, RECORD, INTERFACE, ANNOTATION_TYPE -> {
                    if (isSource) {
                        yield elementFactory.newSourceClassElement(
                                (TypeElement) nativeType,
                                elementAnnotationMetadataFactory
                        );
                    } else {
                        yield elementFactory.newClassElement(
                                (TypeElement) nativeType,
                                elementAnnotationMetadataFactory
                        );
                    }
                }
                default -> throw new IllegalStateException("Unknown element: " + nativeType);
            };
        }

        private List getEnclosedElements() {
            if (enclosedElements == null) {
                enclosedElements = classElement.getEnclosedElements();
            }
            return enclosedElements;
        }

        private EnumSet getElementKind(ElementQuery.Result result) {
            Class elementType = result.getElementType();
            if (elementType == MemberElement.class) {
                return EnumSet.of(ElementKind.FIELD, ElementKind.METHOD);
            } else if (elementType == MethodElement.class) {
                return EnumSet.of(ElementKind.METHOD);
            } else if (elementType == FieldElement.class) {
                if (result.isIncludeEnumConstants()) {
                    return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT);
                }
                return EnumSet.of(ElementKind.FIELD);
            } else if (elementType == ConstructorElement.class) {
                return EnumSet.of(ElementKind.CONSTRUCTOR);
            } else if (elementType == ClassElement.class) {
                return EnumSet.of(ElementKind.CLASS, ElementKind.ENUM, ElementKind.RECORD, ElementKind.INTERFACE, ElementKind.ANNOTATION_TYPE);
            }
            throw new IllegalArgumentException("Unsupported element type for query: " + elementType);
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy