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.annotation.processing.AnnotationUtils;
import io.micronaut.annotation.processing.ModelUtils;
import io.micronaut.annotation.processing.PublicMethodVisitor;
import io.micronaut.annotation.processing.SuperclassAwareTypeVisitor;
import io.micronaut.core.annotation.AccessorsStyle;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.ast.ArrayableClassElement;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.ElementModifier;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.GenericPlaceholderElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.PackageElement;
import io.micronaut.inject.ast.PropertyElement;
import io.micronaut.inject.ast.WildcardElement;
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.Name;
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.type.TypeVariable;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * A class element returning data from a {@link TypeElement}.
 *
 * @author James Kleeh
 * @author graemerocher
 * @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 JavaVisitorContext visitorContext;
    final List typeArguments;
    private final int arrayDimensions;
    private final boolean isTypeVariable;
    private List beanProperties;
    private Map> genericTypeInfo;
    private List enclosedElements;
    private String simpleName;
    private String name;
    private String packageName;

    /**
     * @param classElement       The {@link TypeElement}
     * @param annotationMetadata The annotation metadata
     * @param visitorContext     The visitor context
     */
    @Internal
    public JavaClassElement(TypeElement classElement, AnnotationMetadata annotationMetadata, JavaVisitorContext visitorContext) {
        this(classElement, annotationMetadata, visitorContext, Collections.emptyList(), null, 0, false);
    }

    /**
     * @param classElement       The {@link TypeElement}
     * @param annotationMetadata The annotation metadata
     * @param visitorContext     The visitor context
     * @param typeArguments      The declared type arguments
     * @param genericsInfo       The generic type info
     */
    JavaClassElement(
            TypeElement classElement,
            AnnotationMetadata annotationMetadata,
            JavaVisitorContext visitorContext,
            List typeArguments,
            Map> genericsInfo) {
        this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, 0, false);
    }

    /**
     * @param classElement       The {@link TypeElement}
     * @param annotationMetadata The annotation metadata
     * @param visitorContext     The visitor context
     * @param typeArguments      The declared type arguments
     * @param genericsInfo       The generic type info
     * @param arrayDimensions    The number of array dimensions
     */
    JavaClassElement(
            TypeElement classElement,
            AnnotationMetadata annotationMetadata,
            JavaVisitorContext visitorContext,
            List typeArguments,
            Map> genericsInfo,
            int arrayDimensions) {
        this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, arrayDimensions, false);
    }

    /**
     * @param classElement       The {@link TypeElement}
     * @param annotationMetadata The annotation metadata
     * @param visitorContext     The visitor context
     * @param typeArguments      The declared type arguments
     * @param genericsInfo       The generic type info
     * @param isTypeVariable     Is the class element a type variable
     */
    JavaClassElement(
            TypeElement classElement,
            AnnotationMetadata annotationMetadata,
            JavaVisitorContext visitorContext,
            List typeArguments,
            Map> genericsInfo,
            boolean isTypeVariable) {
        this(classElement, annotationMetadata, visitorContext, typeArguments, genericsInfo, 0, isTypeVariable);
    }

    /**
     * @param classElement       The {@link TypeElement}
     * @param annotationMetadata The annotation metadata
     * @param visitorContext     The visitor context
     * @param typeArguments      The declared type arguments
     * @param genericsInfo       The generic type info
     * @param arrayDimensions    The number of array dimensions
     * @param isTypeVariable     Is the type a type variable
     */
    JavaClassElement(
            TypeElement classElement,
            AnnotationMetadata annotationMetadata,
            JavaVisitorContext visitorContext,
            List typeArguments,
            Map> genericsInfo,
            int arrayDimensions,
            boolean isTypeVariable) {
        super(classElement, annotationMetadata, visitorContext);
        this.classElement = classElement;
        this.visitorContext = visitorContext;
        this.typeArguments = typeArguments;
        this.genericTypeInfo = genericsInfo;
        this.arrayDimensions = arrayDimensions;
        this.isTypeVariable = isTypeVariable;
    }

    @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);
    }

    @NonNull
    @Override
    public Map getTypeArguments(@NonNull String type) {
        if (StringUtils.isNotEmpty(type)) {
            Map> data = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors());
            Map forType = data.get(type);
            if (forType != null) {
                Map typeArgs = new LinkedHashMap<>(forType.size());
                for (Map.Entry entry : forType.entrySet()) {
                    TypeMirror v = entry.getValue();
                    ClassElement ce = v != null ? mirrorToClassElement(v, visitorContext, Collections.emptyMap(), visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments()) : null;
                    if (ce == null) {
                        return Collections.emptyMap();
                    } else {
                        typeArgs.put(entry.getKey(), ce);
                    }
                }
                return Collections.unmodifiableMap(typeArgs);
            }
        }
        return Collections.emptyMap();
    }

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

    @Override
    public Collection getInterfaces() {
        final List interfaces = classElement.getInterfaces();
        if (!interfaces.isEmpty()) {
            return Collections.unmodifiableList(interfaces.stream().map((mirror) ->
                mirrorToClassElement(mirror, visitorContext, genericTypeInfo)).collect(Collectors.toList())
            );
        }
        return Collections.emptyList();
    }

    @Override
    public Optional getSuperType() {
        final TypeMirror superclass = classElement.getSuperclass();
        if (superclass != null) {
            final Element element = visitorContext.getTypes().asElement(superclass);

            if (element instanceof TypeElement) {
                TypeElement superElement = (TypeElement) element;
                if (!Object.class.getName().equals(superElement.getQualifiedName().toString())) {
                    // if super type has type arguments, then build a parameterized ClassElement
                    if (superclass instanceof DeclaredType && !((DeclaredType) superclass).getTypeArguments().isEmpty()) {
                        return Optional.of(
                                parameterizedClassElement(
                                    superclass,
                                    visitorContext,
                                    visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors())));
                    }
                    return Optional.of(
                            new JavaClassElement(
                                    superElement,
                                    visitorContext.getAnnotationUtils().getAnnotationMetadata(superElement),
                                    visitorContext
                            )
                    );
                }
            }
        }
        return Optional.empty();
    }

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

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

    @Override
    public List getBeanProperties() {
        if (this.beanProperties == null) {

            Map props = new LinkedHashMap<>();
            Map fields = new LinkedHashMap<>();

            if (isRecord()) {
                classElement.asType().accept(new SuperclassAwareTypeVisitor(visitorContext) {

                    @Override
                    protected boolean isAcceptable(Element element) {
                        return JavaModelUtils.isRecord(element);
                    }

                    @Override
                    public Object visitDeclared(DeclaredType type, Object o) {
                        Element element = type.asElement();
                        if (isAcceptable(element)) {
                            List enclosedElements = element.getEnclosedElements();
                            for (Element enclosedElement : enclosedElements) {
                                if (JavaModelUtils.isRecordComponent(enclosedElement) || enclosedElement instanceof ExecutableElement) {
                                    if (enclosedElement.getKind() != ElementKind.CONSTRUCTOR) {
                                        accept(type, enclosedElement, o);
                                    }
                                }
                            }
                        }
                        return o;
                    }

                    @Override
                    protected void accept(DeclaredType type, Element element, Object o) {
                        String name = element.getSimpleName().toString();
                        if (element instanceof ExecutableElement) {
                            BeanPropertyData beanPropertyData = props.get(name);
                            if (beanPropertyData != null) {
                                beanPropertyData.getter = (ExecutableElement) element;
                            }
                        } else {

                            props.computeIfAbsent(name, propertyName -> {

                                BeanPropertyData beanPropertyData = new BeanPropertyData(propertyName);
                                beanPropertyData.declaringType = JavaClassElement.this;
                                beanPropertyData.type = mirrorToClassElement(element.asType(), visitorContext, genericTypeInfo, true);
                                return beanPropertyData;
                            });
                        }
                    }

                }, null);
            } else {

                classElement.asType().accept(new PublicMethodVisitor(visitorContext) {

                    final String[] readPrefixes = getValue(AccessorsStyle.class, "readPrefixes", String[].class)
                            .orElse(new String[]{AccessorsStyle.DEFAULT_READ_PREFIX});
                    final String[] writePrefixes = getValue(AccessorsStyle.class, "writePrefixes", String[].class)
                            .orElse(new String[]{AccessorsStyle.DEFAULT_WRITE_PREFIX});

                    @Override
                    protected boolean isAcceptable(javax.lang.model.element.Element element) {
                        if (element.getKind() == ElementKind.FIELD) {
                            return true;
                        }
                        if (element.getKind() == ElementKind.METHOD && element instanceof ExecutableElement) {
                            Set modifiers = element.getModifiers();
                            if (modifiers.contains(Modifier.PUBLIC) && !modifiers.contains(Modifier.STATIC)) {
                                ExecutableElement executableElement = (ExecutableElement) element;
                                String methodName = executableElement.getSimpleName().toString();
                                if (methodName.contains("$")) {
                                    return false;
                                }

                                if (NameUtils.isReaderName(methodName, readPrefixes) && executableElement.getParameters().isEmpty()) {
                                    return true;
                                } else {
                                    return NameUtils.isWriterName(methodName, writePrefixes) && executableElement.getParameters().size() == 1;
                                }
                            }
                        }
                        return false;
                    }

                    @Override
                    protected void accept(DeclaredType declaringType, javax.lang.model.element.Element element, Object o) {

                        if (element instanceof VariableElement) {
                            fields.put(element.getSimpleName().toString(), (VariableElement) element);
                            return;
                        }

                        ExecutableElement executableElement = (ExecutableElement) element;
                        String methodName = executableElement.getSimpleName().toString();
                        final TypeElement declaringTypeElement = (TypeElement) executableElement.getEnclosingElement();

                        if (NameUtils.isReaderName(methodName, readPrefixes) && executableElement.getParameters().isEmpty()) {
                            String propertyName = isKotlinClass(element.getEnclosingElement()) && methodName.startsWith(PREFIX_IS) ?
                                methodName : NameUtils.getPropertyNameForGetter(methodName, readPrefixes);
                            TypeMirror returnType = executableElement.getReturnType();
                            ClassElement getterReturnType;
                            if (returnType instanceof TypeVariable) {
                                TypeVariable tv = (TypeVariable) returnType;
                                final String tvn = tv.toString();
                                final ClassElement classElement = getTypeArguments().get(tvn);
                                if (classElement != null) {
                                    getterReturnType = classElement;
                                } else {
                                    getterReturnType = mirrorToClassElement(returnType, visitorContext, JavaClassElement.this.genericTypeInfo, true);
                                }
                            } else {
                                getterReturnType = mirrorToClassElement(returnType, visitorContext, JavaClassElement.this.genericTypeInfo, true);
                            }

                            BeanPropertyData beanPropertyData = props.computeIfAbsent(propertyName, BeanPropertyData::new);
                            configureDeclaringType(declaringTypeElement, beanPropertyData);
                            beanPropertyData.type = getterReturnType;
                            beanPropertyData.getter = executableElement;
                            if (beanPropertyData.setter != null) {
                                TypeMirror typeMirror = beanPropertyData.setter.getParameters().get(0).asType();
                                ClassElement setterParameterType = mirrorToClassElement(typeMirror, visitorContext, JavaClassElement.this.genericTypeInfo, true);
                                if (!setterParameterType.isAssignable(getterReturnType)) {
                                    beanPropertyData.setter = null; // not a compatible setter
                                }
                            }
                        } else if (NameUtils.isWriterName(methodName, writePrefixes) && executableElement.getParameters().size() == 1) {
                            String propertyName = NameUtils.getPropertyNameForSetter(methodName, writePrefixes);
                            TypeMirror typeMirror = executableElement.getParameters().get(0).asType();
                            ClassElement setterParameterType = mirrorToClassElement(typeMirror, visitorContext, JavaClassElement.this.genericTypeInfo, true);

                            BeanPropertyData beanPropertyData = props.computeIfAbsent(propertyName, BeanPropertyData::new);
                            configureDeclaringType(declaringTypeElement, beanPropertyData);
                            ClassElement propertyType = beanPropertyData.type;
                            if (propertyType != null) {
                                if (propertyType.getName().equals(setterParameterType.getName())) {
                                    beanPropertyData.setter = executableElement;
                                }
                            } else {
                                beanPropertyData.setter = executableElement;
                            }
                        }
                    }

                    private void configureDeclaringType(TypeElement declaringTypeElement, BeanPropertyData beanPropertyData) {
                        if (beanPropertyData.declaringType == null && !classElement.equals(declaringTypeElement)) {
                            beanPropertyData.declaringType = mirrorToClassElement(
                                    declaringTypeElement.asType(),
                                    visitorContext,
                                    genericTypeInfo,
                                    true);
                        } else if (beanPropertyData.declaringType == null) {
                            beanPropertyData.declaringType = mirrorToClassElement(
                                    declaringTypeElement.asType(),
                                    visitorContext,
                                    genericTypeInfo,
                                    false);
                        }
                    }
                }, null);
            }

            if (!props.isEmpty()) {
                this.beanProperties = new ArrayList<>(props.size());
                for (Map.Entry entry : props.entrySet()) {
                    String propertyName = entry.getKey();
                    BeanPropertyData value = entry.getValue();
                    final VariableElement fieldElement = fields.get(propertyName);

                    if (value.getter != null) {
                        final AnnotationMetadata annotationMetadata;
                        List parents = new ArrayList<>();
                        if (fieldElement != null) {
                            parents.add(fieldElement);
                        }
                        if (value.setter != null) {
                            parents.add(value.setter);
                        }
                        if (!parents.isEmpty()) {
                            annotationMetadata = visitorContext.getAnnotationUtils().getAnnotationMetadata(parents, value.getter);
                        } else {
                            annotationMetadata = visitorContext
                                    .getAnnotationUtils()
                                    .newAnnotationBuilder().buildForMethod(value.getter);
                        }

                        JavaPropertyElement propertyElement = new JavaPropertyElement(
                                value.declaringType == null ? this : value.declaringType,
                                value.getter,
                                annotationMetadata,
                                propertyName,
                                value.type,
                                value.setter == null,
                                visitorContext) {

                            @Override
                            public ClassElement getGenericType() {
                                TypeMirror propertyType = value.getter.getReturnType();
                                Map> declaredGenericInfo = getGenericTypeInfo();
                                ClassElement typeElement = parameterizedClassElement(propertyType, visitorContext, declaredGenericInfo);
                                if (typeElement instanceof JavaClassElement && fieldElement != null) {
                                    TypeMirror fieldType = fieldElement.asType();
                                    if (visitorContext.getTypes().isAssignable(fieldType, propertyType)) {
                                        ClassElement fieldElement = parameterizedClassElement(fieldType, visitorContext, declaredGenericInfo);
                                        int typeGenericsSize = typeElement.getBoundGenericTypes().size();
                                        if (fieldElement instanceof JavaClassElement
                                                && typeGenericsSize > 0 && typeGenericsSize == fieldElement.getBoundGenericTypes().size()) {
                                            return ((JavaClassElement) typeElement).withBoundGenericTypeMirrors(((JavaClassElement) fieldElement).typeArguments);
                                        }
                                    }
                                }
                                return typeElement;
                            }

                            @Override
                            public Optional getDocumentation() {
                                Elements elements = visitorContext.getElements();
                                String docComment = elements.getDocComment(value.getter);
                                return Optional.ofNullable(docComment);
                            }

                            @Override
                            public Optional getWriteMethod() {
                                if (value.setter != null) {
                                    return Optional.of(new JavaMethodElement(
                                            JavaClassElement.this,
                                            value.setter,
                                            visitorContext.getAnnotationUtils().newAnnotationBuilder().buildForMethod(value.setter),
                                            visitorContext
                                    ));
                                }
                                return Optional.empty();
                            }

                            @Override
                            public Optional getReadMethod() {
                                return Optional.of(new JavaMethodElement(
                                        JavaClassElement.this,
                                        value.getter,
                                        annotationMetadata,
                                        visitorContext
                                ));
                            }
                        };
                         beanProperties.add(propertyElement);
                    }
                }
                this.beanProperties = Collections.unmodifiableList(beanProperties);
            } else {
                this.beanProperties = Collections.emptyList();
            }
        }
        return Collections.unmodifiableList(beanProperties);
    }

    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) {
        Objects.requireNonNull(query, "Query cannot be null");
        ElementQuery.Result result = query.result();
        ElementKind kind = getElementKind(result.getElementType());
        List resultingElements = new ArrayList<>();
        List enclosedElements = new ArrayList<>(getDeclaredEnclosedElements());

        boolean onlyDeclared = result.isOnlyDeclared();
        boolean onlyAbstract = result.isOnlyAbstract();
        boolean onlyConcrete = result.isOnlyConcrete();
        boolean onlyInstance = result.isOnlyInstance();
        boolean includeEnumConstants = result.isIncludeEnumConstants();
        boolean includeOverriddenMethods = result.isIncludeOverriddenMethods();
        boolean includeHiddenElements = result.isIncludeHiddenElements();

        if (!onlyDeclared) {
            Elements elements = visitorContext.getElements();

            TypeMirror superclass = classElement.getSuperclass();
            // traverse the super class true and add elements that are not overridden
            while (superclass instanceof DeclaredType) {
                DeclaredType dt = (DeclaredType) superclass;
                TypeElement element = (TypeElement) dt.asElement();
                // reached non-accessible class like Object, Enum, Record etc.
                if (element.getQualifiedName().toString().startsWith("java.lang.")) {
                    break;
                }
                List superElements = element.getEnclosedElements();

                List elementsToAdd = new ArrayList<>(superElements.size());
                superElements:
                for (Element superElement : superElements) {
                    ElementKind superKind = superElement.getKind();
                    if (superKind == kind) {
                        for (Element enclosedElement : enclosedElements) {
                            if (!includeHiddenElements && elements.hides(enclosedElement, superElement)) {
                                continue superElements;
                            } else if (enclosedElement.getKind() == ElementKind.METHOD && superElement.getKind() == ElementKind.METHOD) {
                                final ExecutableElement methodCandidate = (ExecutableElement) superElement;
                                if (!includeOverriddenMethods && elements.overrides((ExecutableElement) enclosedElement, methodCandidate, this.classElement)) {
                                    continue superElements;
                                }
                            }
                        }
                        // dependency injection method resolution requires extended overrides checks
                        if (result.isOnlyInjected() && superElement.getKind() == ElementKind.METHOD) {
                            final ExecutableElement methodCandidate = (ExecutableElement) superElement;
                            // check for extended override
                            final String thisClassName = this.classElement.getQualifiedName().toString();
                            final String declaringClassName = element.getQualifiedName().toString();
                            boolean isParent = !declaringClassName.equals(thisClassName);
                            final ModelUtils javaModelUtils = visitorContext.getModelUtils();
                            final ExecutableElement overridingMethod = javaModelUtils
                                    .overridingOrHidingMethod(methodCandidate, this.classElement, false)
                                    .orElse(methodCandidate);
                            TypeElement overridingClass = javaModelUtils.classElementFor(overridingMethod);
                            boolean overridden = isParent && overridingClass != null &&
                                    !overridingClass.getQualifiedName().toString().equals(declaringClassName);

                            boolean isPackagePrivate = javaModelUtils.isPackagePrivate(methodCandidate);
                            boolean isPrivate = methodCandidate.getModifiers().contains(Modifier.PRIVATE);
                            if (overridden && !(isPrivate || isPackagePrivate)) {
                                // bail out if the method has been overridden, since it will have already been handled
                                continue;
                            }
                            if (isParent && overridden) {

                                boolean overriddenInjected = overridden && visitorContext.getAnnotationUtils()
                                        .getAnnotationMetadata(overridingMethod).hasDeclaredAnnotation(
                                                AnnotationUtil.INJECT);
                                String packageOfOverridingClass = visitorContext.getElements().getPackageOf(overridingMethod).getQualifiedName().toString();
                                String packageOfDeclaringClass = visitorContext.getElements().getPackageOf(element).getQualifiedName().toString();
                                boolean isPackagePrivateAndPackagesDiffer = overridden && isPackagePrivate &&
                                        !packageOfOverridingClass.equals(packageOfDeclaringClass);
                                if (!overriddenInjected && !isPackagePrivateAndPackagesDiffer && !isPrivate) {
                                    // bail out if the overridden method is package private and in the same package
                                    // and is not annotated with @Inject
                                    continue;
                                }
                            }
                        }

                        if (onlyAbstract && !superElement.getModifiers().contains(Modifier.ABSTRACT)) {
                            continue;
                        } else if (onlyConcrete && superElement.getModifiers().contains(Modifier.ABSTRACT)) {
                            continue;
                        } else if (onlyInstance && superElement.getModifiers().contains(Modifier.STATIC)) {
                            continue;
                        }
                        elementsToAdd.add(superElement);
                    }
                }
                enclosedElements.addAll(elementsToAdd);
                superclass = element.getSuperclass();
            }

            if (kind == ElementKind.METHOD || kind == ElementKind.FIELD) {
                // if the element kind is interfaces then we need to go through interfaces as well
                Set allInterfaces = visitorContext.getModelUtils().getAllInterfaces(this.classElement);
                Collection interfacesToProcess = new ArrayList<>(allInterfaces.size());
                // Remove duplicates
                outer:
                for (TypeElement el : allInterfaces) {
                    for (TypeElement existingEl : interfacesToProcess) {
                        Name qualifiedName = existingEl.getQualifiedName();
                        if (qualifiedName.equals(el.getQualifiedName())) {
                            continue outer;
                        }
                    }
                    interfacesToProcess.add(el);
                }
                List elementsToAdd = new ArrayList<>(allInterfaces.size());
                for (TypeElement itfe : interfacesToProcess) {
                    List interfaceElements = itfe.getEnclosedElements();
                    interfaceElements:
                    for (Element interfaceElement : interfaceElements) {
                        if (interfaceElement.getKind() == ElementKind.METHOD) {
                            ExecutableElement ee = (ExecutableElement) interfaceElement;
                            if (onlyAbstract && ee.getModifiers().contains(Modifier.DEFAULT)) {
                                continue;
                            } else if (onlyConcrete && !ee.getModifiers().contains(Modifier.DEFAULT)) {
                                continue;
                            }

                            for (Element enclosedElement : enclosedElements) {
                                if (enclosedElement.getKind() == ElementKind.METHOD) {
                                    if (!includeOverriddenMethods && elements.overrides((ExecutableElement) enclosedElement, ee, this.classElement)) {
                                        continue interfaceElements;
                                    }
                                }
                            }
                        }
                        elementsToAdd.add(interfaceElement);
                    }
                }
                enclosedElements.addAll(elementsToAdd);
                elementsToAdd.clear();
            }
            if (onlyAbstract) {
                if (isInterface()) {
                    enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.DEFAULT));
                } else {
                    enclosedElements.removeIf((e) -> !e.getModifiers().contains(Modifier.ABSTRACT));
                }
            } else if (onlyConcrete) {
                if (isInterface()) {
                    enclosedElements.removeIf((e) -> !e.getModifiers().contains(Modifier.DEFAULT));
                } else {
                    enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.ABSTRACT));
                }
            }
        }
        if (onlyInstance) {
            enclosedElements.removeIf((e) -> e.getModifiers().contains(Modifier.STATIC));
        }
        List>> modifierPredicates = result.getModifierPredicates();
        List> namePredicates = result.getNamePredicates();
        List> annotationPredicates = result.getAnnotationPredicates();
        final List> typePredicates = result.getTypePredicates();
        boolean hasNamePredicates = !namePredicates.isEmpty();
        boolean hasModifierPredicates = !modifierPredicates.isEmpty();
        boolean hasAnnotationPredicates = !annotationPredicates.isEmpty();
        boolean hasTypePredicates = !typePredicates.isEmpty();
        boolean onlyAccessible = result.isOnlyAccessible();
        final JavaElementFactory elementFactory = visitorContext.getElementFactory();

        elementLoop:
        for (Element enclosedElement : enclosedElements) {
            ElementKind enclosedElementKind = enclosedElement.getKind();
            if (enclosedElementKind == kind
                    || includeEnumConstants && kind == ElementKind.FIELD && enclosedElementKind == ElementKind.ENUM_CONSTANT
                    || (enclosedElementKind == ElementKind.ENUM && kind == ElementKind.CLASS)) {
                String elementName = enclosedElement.getSimpleName().toString();
                if (onlyAccessible) {
                    // exclude private members
                    if (enclosedElement.getModifiers().contains(Modifier.PRIVATE)) {
                        continue;
                    } else if (elementName.startsWith("$")) {
                        // exclude synthetic members or bridge methods that start with $
                        continue;
                    } else {
                        Element enclosingElement = enclosedElement.getEnclosingElement();
                        final ClassElement onlyAccessibleFrom = result.getOnlyAccessibleFromType().orElse(this);
                        Object accessibleFrom = onlyAccessibleFrom.getNativeType();
                        // if the outer element of the enclosed element is not the current class
                        // we need to check if it package private and within a different package so it can be excluded
                        if (enclosingElement != accessibleFrom && visitorContext.getModelUtils().isPackagePrivate(enclosedElement)) {
                            if (enclosingElement instanceof TypeElement) {
                                Name qualifiedName = ((TypeElement) enclosingElement).getQualifiedName();
                                String packageName = NameUtils.getPackageName(qualifiedName.toString());
                                if (!packageName.equals(onlyAccessibleFrom.getPackageName())) {
                                    continue;
                                }
                            }
                        }
                    }
                }

                if (hasModifierPredicates) {
                    Set modifiers = enclosedElement
                            .getModifiers().stream().map(m -> ElementModifier.valueOf(m.name())).collect(Collectors.toSet());
                    for (Predicate> modifierPredicate : modifierPredicates) {
                        if (!modifierPredicate.test(modifiers)) {
                            continue elementLoop;
                        }
                    }
                }

                if (hasNamePredicates) {
                    for (Predicate namePredicate : namePredicates) {
                        if (!namePredicate.test(elementName)) {
                            continue elementLoop;
                        }
                    }
                }

                final AnnotationMetadata metadata = visitorContext.getAnnotationUtils().getAnnotationMetadata(enclosedElement);
                if (hasAnnotationPredicates) {
                    for (Predicate annotationPredicate : annotationPredicates) {
                        if (!annotationPredicate.test(metadata)) {
                            continue elementLoop;
                        }
                    }
                }

                T element;

                switch (enclosedElementKind) {
                    case METHOD:

                        final ExecutableElement executableElement = (ExecutableElement) enclosedElement;
                        //noinspection unchecked
                        element = (T) elementFactory.newMethodElement(
                                this,
                                executableElement,
                                metadata,
                                genericTypeInfo
                        );
                    break;
                    case FIELD:
                    case ENUM_CONSTANT:
                        //noinspection unchecked
                        element = (T) elementFactory.newFieldElement(
                                this,
                                (VariableElement) enclosedElement,
                                metadata
                        );
                    break;
                    case CONSTRUCTOR:
                        //noinspection unchecked
                        element = (T) elementFactory.newConstructorElement(
                                this,
                                (ExecutableElement) enclosedElement,
                                metadata
                        );
                    break;
                    case CLASS:
                    case ENUM:
                        //noinspection unchecked
                        element = (T) elementFactory.newClassElement(
                                (TypeElement) enclosedElement,
                                metadata
                        );
                    break;
                    default:
                        element = null;
                }

                if (element != null) {
                    if (hasTypePredicates) {
                        for (Predicate typePredicate : typePredicates) {
                            ClassElement classElement;
                            if (element instanceof ConstructorElement) {
                                classElement = this;
                            } else if (element instanceof MethodElement) {
                                classElement = ((MethodElement) element).getGenericReturnType();
                            } else if (element instanceof ClassElement) {
                                classElement = (ClassElement) element;
                            } else {
                                classElement = ((FieldElement) element).getGenericField();
                            }
                            if (!typePredicate.test(classElement)) {
                                continue elementLoop;
                            }
                        }
                    }
                    List> elementPredicates = result.getElementPredicates();
                    if (!elementPredicates.isEmpty()) {
                        for (Predicate elementPredicate : elementPredicates) {
                            if (!elementPredicate.test(element)) {
                                continue elementLoop;
                            }
                        }
                    }
                    resultingElements.add(element);
                }
            }
        }
        return Collections.unmodifiableList(resultingElements);
    }

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

    private  ElementKind getElementKind(Class elementType) {
        if (elementType == MethodElement.class) {
            return ElementKind.METHOD;
        } else if (elementType == FieldElement.class) {
            return ElementKind.FIELD;
        } else if (elementType == ConstructorElement.class) {
            return ElementKind.CONSTRUCTOR;
        } else if (elementType == ClassElement.class) {
            return ElementKind.CLASS;
        }
        throw new IllegalArgumentException("Unsupported element type for query: " + elementType);
    }

    @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(classElement, getAnnotationMetadata(), visitorContext, typeArguments, getGenericTypeInfo(), 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) {
            return new JavaPackageElement(
                    ((javax.lang.model.element.PackageElement) enclosingElement),
                    visitorContext.getAnnotationUtils().getAnnotationMetadata(enclosingElement),
                    visitorContext
            );
        } else {
            return PackageElement.DEFAULT_PACKAGE;
        }

    }

    @Override
    public boolean isAssignable(String type) {
        TypeElement otherElement = visitorContext.getElements().getTypeElement(type);
        if (otherElement != null) {
            Types types = visitorContext.getTypes();
            TypeMirror thisType = types.erasure(classElement.asType());
            TypeMirror thatType = types.erasure(otherElement.asType());
            return types.isAssignable(thisType, thatType);
        }
        return false;
    }

    @Override
    public boolean isAssignable(ClassElement type) {
        if (type.isPrimitive()) {
            return isAssignable(type.getName());
        } else {
            Object nativeType = type.getNativeType();
            if (nativeType instanceof TypeElement) {
                Types types = visitorContext.getTypes();
                TypeMirror thisType = types.erasure(classElement.asType());
                TypeMirror thatType = types.erasure(((TypeElement) nativeType).asType());
                return types.isAssignable(thisType, thatType);
            }
        }
        return false;
    }

    @NonNull
    @Override
    public Optional getPrimaryConstructor() {
        final AnnotationUtils annotationUtils = visitorContext.getAnnotationUtils();
        final ModelUtils modelUtils = visitorContext.getModelUtils();
        ExecutableElement method = modelUtils.staticCreatorFor(classElement, annotationUtils);
        if (method == null) {
            if (isInner() && !isStatic()) {
                // only static inner classes can be constructed
                return Optional.empty();
            }
            method = modelUtils.concreteConstructorFor(classElement, annotationUtils);
        }

        return createMethodElement(annotationUtils, method);
    }

    @Override
    public Optional getDefaultConstructor() {
        final AnnotationUtils annotationUtils = visitorContext.getAnnotationUtils();
        final ModelUtils modelUtils = visitorContext.getModelUtils();
        ExecutableElement method = modelUtils.defaultStaticCreatorFor(classElement, annotationUtils);
        if (method == null) {
            if (isInner() && !isStatic()) {
                // only static inner classes can be constructed
                return Optional.empty();
            }
            method = modelUtils.defaultConstructorFor(classElement);
        }
        return createMethodElement(annotationUtils, method);
    }

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

    private Optional createMethodElement(AnnotationUtils annotationUtils, ExecutableElement method) {
        return Optional.ofNullable(method).map(executableElement -> {
            final AnnotationMetadata annotationMetadata = annotationUtils.getAnnotationMetadata(executableElement);
            if (executableElement.getKind() == ElementKind.CONSTRUCTOR) {
                return new JavaConstructorElement(this, executableElement, annotationMetadata, visitorContext);
            } else {
                return new JavaMethodElement(this, executableElement, annotationMetadata, visitorContext);
            }
        });
    }

    @NonNull
    @Override
    public List getBoundGenericTypes() {
        return typeArguments.stream()
                //return getGenericTypeInfo().getOrDefault(classElement.getQualifiedName().toString(), Collections.emptyMap()).values().stream()
                .map(tm -> mirrorToClassElement(tm, visitorContext, getGenericTypeInfo()))
                .collect(Collectors.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) mirrorToClassElement(tpe.asType(), visitorContext))
                .collect(Collectors.toList());
    }

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

    private static TypeMirror toTypeMirror(JavaVisitorContext visitorContext, ClassElement element) {
        if (element.isArray()) {
            return visitorContext.getTypes().getArrayType(toTypeMirror(visitorContext, element.fromArray()));
        } else if (element.isWildcard()) {
            WildcardElement wildcardElement = (WildcardElement) element;
            List upperBounds = wildcardElement.getUpperBounds();
            if (upperBounds.size() != 1) {
                throw new UnsupportedOperationException("Multiple upper bounds not supported");
            }
            TypeMirror upperBound = toTypeMirror(visitorContext, upperBounds.get(0));
            if (upperBound.toString().equals("java.lang.Object")) {
                upperBound = null;
            }
            List lowerBounds = wildcardElement.getLowerBounds();
            if (lowerBounds.size() > 1) {
                throw new UnsupportedOperationException("Multiple upper bounds not supported");
            }
            TypeMirror lowerBound = lowerBounds.isEmpty() ? null : toTypeMirror(visitorContext, lowerBounds.get(0));
            return visitorContext.getTypes().getWildcardType(upperBound, lowerBound);
        } else if (element.isGenericPlaceholder()) {
            if (!(element instanceof JavaGenericPlaceholderElement)) {
                throw new UnsupportedOperationException("Free type variable on non-java class");
            }
            return ((JavaGenericPlaceholderElement) element).realTypeVariable;
        } else {
            if (element instanceof JavaClassElement) {
                return visitorContext.getTypes().getDeclaredType(
                        ((JavaClassElement) element).classElement,
                        ((JavaClassElement) element).typeArguments.toArray(new TypeMirror[0]));
            } else {
                return visitorContext.getTypes().getDeclaredType(
                        ((JavaClassElement) visitorContext.getClassElement(element.getName()).get()).classElement,
                        element.getBoundGenericTypes().stream().map(ce -> toTypeMirror(visitorContext, ce)).toArray(TypeMirror[]::new));
            }
        }
    }

    @NonNull
    @Override
    public ClassElement withBoundGenericTypes(@NonNull List typeArguments) {
        if (typeArguments.isEmpty() && this.typeArguments.isEmpty()) {
            return this;
        }

        List typeMirrors = typeArguments.stream()
                .map(ce -> toTypeMirror(visitorContext, ce))
                .collect(Collectors.toList());
        return withBoundGenericTypeMirrors(typeMirrors);
    }

    private ClassElement withBoundGenericTypeMirrors(@NonNull List typeMirrors) {
        if (typeMirrors.equals(this.typeArguments)) {
            return this;
        }

        Map boundByName = new LinkedHashMap<>();
        Iterator tpes = classElement.getTypeParameters().iterator();
        Iterator args = typeMirrors.iterator();
        while (tpes.hasNext() && args.hasNext()) {
            boundByName.put(tpes.next().getSimpleName().toString(), args.next());
        }

        Map> genericsInfo = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, boundByName);
        return new JavaClassElement(classElement, getAnnotationMetadata(), visitorContext, typeMirrors, genericsInfo, arrayDimensions);
    }

    @Override
    public @NonNull
    Map getTypeArguments() {
        List typeParameters = classElement.getTypeParameters();
        Iterator tpi = typeParameters.iterator();

        Map map = new LinkedHashMap<>();
        while (tpi.hasNext()) {
            TypeParameterElement tpe = tpi.next();
            ClassElement classElement = mirrorToClassElement(tpe.asType(), visitorContext, this.genericTypeInfo, visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments());
            map.put(tpe.toString(), classElement);
        }

        return Collections.unmodifiableMap(map);
    }

    private Map getBoundTypeMirrors() {
        List typeParameters = classElement.getTypeParameters();
        Iterator tpi = typeParameters.iterator();

        Map map = new LinkedHashMap<>();
        while (tpi.hasNext()) {
            TypeParameterElement tpe = tpi.next();
            TypeMirror t = tpe.asType();
            map.put(tpe.toString(), t);
        }

        return Collections.unmodifiableMap(map);
    }

    @NonNull
    @Override
    public Map> getAllTypeArguments() {
        Map typeArguments = getBoundTypeMirrors();
        Map> info = visitorContext.getGenericUtils()
                .buildGenericTypeArgumentElementInfo(
                        classElement,
                        null,
                        typeArguments
                );
        Map> result = new LinkedHashMap<>(info.size());
        info.forEach((name, generics) -> {
            Map resolved = new LinkedHashMap<>(generics.size());
            generics.forEach((variable, mirror) -> {
                final Map typeInfo = this.genericTypeInfo != null ? this.genericTypeInfo.get(getName()) : null;
                TypeMirror resolvedType = mirror;
                if (mirror instanceof TypeVariable && typeInfo != null) {
                    final TypeMirror tm = typeInfo.get(mirror.toString());
                    if (tm != null) {
                        resolvedType = tm;
                    }
                }
                ClassElement classElement = mirrorToClassElement(
                        resolvedType,
                        visitorContext,
                        info,
                        visitorContext.getConfiguration().includeTypeLevelAnnotationsInGenericArguments(),
                        mirror instanceof TypeVariable
                );
                resolved.put(variable, classElement);
            });
            result.put(name, resolved);
        });

        if (!typeArguments.isEmpty()) {
            result.put(JavaModelUtils.getClassName(this.classElement), getTypeArguments());
        }
        return result;
    }

    /**
     * @return The generic type info for this class.
     */
    Map> getGenericTypeInfo() {
        if (genericTypeInfo == null) {
            genericTypeInfo = visitorContext.getGenericUtils().buildGenericTypeArgumentElementInfo(classElement, null, getBoundTypeMirrors());
        }
        return genericTypeInfo;
    }

    /**
     * Internal holder class for getters and setters.
     */
    private static class BeanPropertyData {
        ClassElement type;
        ClassElement declaringType;
        ExecutableElement getter;
        ExecutableElement setter;
        final String propertyName;

        public BeanPropertyData(String propertyName) {
            this.propertyName = propertyName;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy