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

io.micronaut.ast.groovy.visitor.GroovyClassElement Maven / Gradle / Ivy

There is a newer version: 4.7.9
Show newest version
/*
 * Copyright 2017-2020 original authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micronaut.ast.groovy.visitor;

import io.micronaut.ast.groovy.annotation.GroovyAnnotationMetadataBuilder;
import io.micronaut.ast.groovy.utils.AstAnnotationUtils;
import io.micronaut.ast.groovy.utils.AstClassUtils;
import io.micronaut.ast.groovy.utils.AstGenericUtils;
import io.micronaut.ast.groovy.utils.PublicMethodVisitor;
import io.micronaut.core.annotation.*;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.reflect.ClassUtils;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.ast.*;
import org.apache.groovy.ast.tools.ClassNodeUtils;
import org.apache.groovy.util.concurrent.LazyInitializable;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static groovyjarjarasm.asm.Opcodes.*;
import static org.codehaus.groovy.ast.ClassHelper.makeCached;

/**
 * A class element returning data from a {@link ClassNode}.
 *
 * @author James Kleeh
 * @since 1.0
 */
@Internal
public class GroovyClassElement extends AbstractGroovyElement implements ArrayableClassElement {

    private static final Predicate JUNK_METHOD_FILTER = m -> {
        String methodName = m.getName();

        return m.isStaticConstructor() ||
                methodName.startsWith("$") ||
                methodName.contains("trait$") ||
                methodName.startsWith("super$") ||
                methodName.equals("setMetaClass") ||
                m.getReturnType().getNameWithoutPackage().equals("MetaClass") ||
                m.getDeclaringClass().equals(ClassHelper.GROOVY_OBJECT_TYPE) ||
                m.getDeclaringClass().equals(ClassHelper.OBJECT_TYPE);
    };
    private static final Predicate JUNK_FIELD_FILTER = m -> {
        String fieldName = m.getName();

        return  fieldName.startsWith("$") ||
                fieldName.startsWith("__$") ||
                fieldName.contains("trait$") ||
                fieldName.equals("metaClass") ||
                m.getDeclaringClass().equals(ClassHelper.GROOVY_OBJECT_TYPE) ||
                m.getDeclaringClass().equals(ClassHelper.OBJECT_TYPE);
    };
    protected final ClassNode classNode;
    private final int arrayDimensions;
    private final boolean isTypeVar;
    private List overrideBoundGenericTypes;
    private Map> genericInfo;

    /**
     * @param visitorContext     The visitor context
     * @param classNode          The {@link ClassNode}
     * @param annotationMetadata The annotation metadata
     */
    public GroovyClassElement(GroovyVisitorContext visitorContext, ClassNode classNode, AnnotationMetadata annotationMetadata) {
        this(visitorContext, classNode, annotationMetadata, null, 0);
    }

    /**
     * @param visitorContext     The visitor context
     * @param classNode          The {@link ClassNode}
     * @param annotationMetadata The annotation metadata
     * @param genericInfo        The generic info
     * @param arrayDimensions    The number of array dimensions
     */
    GroovyClassElement(
            GroovyVisitorContext visitorContext,
            ClassNode classNode,
            AnnotationMetadata annotationMetadata,
            Map> genericInfo,
            int arrayDimensions) {
        this(visitorContext, classNode, annotationMetadata, genericInfo, arrayDimensions, false);
    }

    /**
     * @param visitorContext     The visitor context
     * @param classNode          The {@link ClassNode}
     * @param annotationMetadata The annotation metadata
     * @param genericInfo        The generic info
     * @param arrayDimensions    The number of array dimensions
     * @param isTypeVar          Is the element a type variable
     */
    GroovyClassElement(
            GroovyVisitorContext visitorContext,
            ClassNode classNode,
            AnnotationMetadata annotationMetadata,
            Map> genericInfo,
            int arrayDimensions,
            boolean isTypeVar) {
        super(visitorContext, classNode, annotationMetadata);
        this.classNode = classNode;
        this.genericInfo = genericInfo;
        this.arrayDimensions = arrayDimensions;
        if (classNode.isArray()) {
            classNode.setName(classNode.getComponentType().getName());
        }
        this.isTypeVar = isTypeVar;
    }

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

    @Override
    public  List getEnclosedElements(@NonNull ElementQuery query) {
        Objects.requireNonNull(query, "Query cannot be null");
        ElementQuery.Result result = query.result();
        boolean onlyDeclared = result.isOnlyDeclared();
        boolean onlyAccessible = result.isOnlyAccessible();
        boolean onlyAbstract = result.isOnlyAbstract();
        boolean onlyConcrete = result.isOnlyConcrete();
        boolean onlyInstance = result.isOnlyInstance();
        List> namePredicates = result.getNamePredicates();
        List> typePredicates = result.getTypePredicates();
        List> annotationPredicates = result.getAnnotationPredicates();
        List> elementPredicates = result.getElementPredicates();
        List>> modifierPredicates = result.getModifierPredicates();
        List elements;
        Class elementType = result.getElementType();
        if (elementType == MethodElement.class) {

            List methods;
            Map declaredMethodsMap = classNode.getDeclaredMethodsMap();
            ClassNodeUtils.addDeclaredMethodsFromInterfaces(classNode, declaredMethodsMap);
            if (onlyDeclared) {
                methods = new ArrayList<>(classNode.getMethods());
            } else {
                methods = new ArrayList<>(classNode.getAllDeclaredMethods());
            }

            Iterator i = methods.iterator();
            while (i.hasNext()) {
                MethodNode methodNode = i.next();
                if (JUNK_METHOD_FILTER.test(methodNode)) {
                    i.remove();
                    continue;
                }
                if (onlyAbstract && !methodNode.isAbstract()) {
                    i.remove();
                    continue;
                }
                if (onlyConcrete && methodNode.isAbstract()) {
                    i.remove();
                    continue;
                }
                if (onlyInstance && methodNode.isStatic()) {
                    i.remove();
                    continue;
                }
                if (onlyAccessible) {
                    final ClassElement accessibleFromType = result.getOnlyAccessibleFromType().orElse(this);
                    if (methodNode.isPrivate()) {
                        i.remove();
                        continue;
                    } else if (!methodNode.getDeclaringClass().getName().equals(accessibleFromType.getName())) {
                        // inaccessible through package scope
                        if (methodNode.isPackageScope() && !methodNode.getDeclaringClass().getPackageName().equals(accessibleFromType.getPackageName())) {
                            i.remove();
                            continue;
                        }
                    }
                }
                if (!modifierPredicates.isEmpty()) {
                    Set elementModifiers = resolveModifiers(methodNode);
                    if (!modifierPredicates.stream().allMatch(p -> p.test(elementModifiers))) {
                        i.remove();
                        continue;
                    }
                }

                if (!namePredicates.isEmpty()) {
                    if (!namePredicates.stream().allMatch(p -> p.test(methodNode.getName()))) {
                        i.remove();
                    }
                }
            }

            //noinspection unchecked
            elements = methods.stream().map(methodNode -> (T) new GroovyMethodElement(
                    this,
                    visitorContext,
                    methodNode,
                    AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, methodNode)
            )).collect(Collectors.toList());

            if (!typePredicates.isEmpty()) {
                elements.removeIf(e -> !typePredicates.stream().allMatch(p -> p.test(((MethodElement) e).getGenericReturnType())));
            }
        } else if (elementType == ConstructorElement.class) {
            List constructors = new ArrayList<>(classNode.getDeclaredConstructors());
            if (!onlyDeclared) {
                ClassNode superClass = classNode.getSuperClass();
                while (superClass != null) {
                    // don't include constructors on enum, record... – matches behavior of JavaClassElement
                    if (superClass.getPackageName().equals("java.lang")) {
                        break;
                    }
                    constructors.addAll(superClass.getDeclaredConstructors());
                    superClass = superClass.getSuperClass();
                }
            }
            for (Iterator i = constructors.iterator(); i.hasNext(); ) {
                ConstructorNode constructor = i.next();
                // we don't listen to the user here, we never return static initializers. This matches behavior of JavaClassElement
                if (constructor.isStatic()) {
                    i.remove();
                    continue;
                }
                if (onlyAccessible) {
                    final ClassElement accessibleFromType = result.getOnlyAccessibleFromType().orElse(this);
                    if (constructor.isPrivate()) {
                        i.remove();
                        continue;
                    } else if (!constructor.getDeclaringClass().getName().equals(accessibleFromType.getName())) {
                        // inaccessible through package scope
                        if (constructor.isPackageScope() && !constructor.getDeclaringClass().getPackageName().equals(accessibleFromType.getPackageName())) {
                            i.remove();
                            continue;
                        }
                    }
                }
                if (!modifierPredicates.isEmpty()) {
                    Set elementModifiers = resolveModifiers(constructor);
                    if (!modifierPredicates.stream().allMatch(p -> p.test(elementModifiers))) {
                        i.remove();
                    }
                }
            }

            //noinspection unchecked
            elements = constructors.stream().map(constructorNode -> (T) new GroovyConstructorElement(
                    this,
                    visitorContext,
                    constructorNode,
                    AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, constructorNode)
            )).collect(Collectors.toList());
        } else if (elementType == FieldElement.class) {
            List fields;
            if (onlyDeclared) {
                List initialFields = classNode.getFields();
                fields = findRelevantFields(onlyAccessible, result.getOnlyAccessibleFromType().orElse(this), initialFields, namePredicates, modifierPredicates);
            } else {
                fields = new ArrayList<>(classNode.getFields());
                ClassNode superClass = classNode.getSuperClass();
                while (superClass != null && !superClass.equals(ClassHelper.OBJECT_TYPE)) {
                    fields.addAll(superClass.getFields());
                    superClass = superClass.getSuperClass();
                }
                fields = findRelevantFields(onlyAccessible, result.getOnlyAccessibleFromType().orElse(this), fields, namePredicates, modifierPredicates);
            }
            //noinspection unchecked
            Stream fieldStream = fields.stream();
            if (onlyInstance) {
                fieldStream = fieldStream.filter((fn) -> !fn.isStatic());
            }
            elements = fieldStream.map(fieldNode -> (T) new GroovyFieldElement(
                    visitorContext,
                    fieldNode,
                    fieldNode,
                    AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, fieldNode)
            )).collect(Collectors.toList());

            if (!typePredicates.isEmpty()) {
                elements.removeIf(e -> !typePredicates.stream().allMatch(p -> p.test(((FieldElement) e).getGenericField())));
            }
        } else if (elementType == ClassElement.class) {
            Iterator i = classNode.getInnerClasses();
            List innerClasses = new ArrayList<>();
            while (i.hasNext()) {
                InnerClassNode innerClassNode = i.next();
                if (onlyAbstract && !innerClassNode.isAbstract()) {
                    continue;
                }
                if (onlyConcrete && innerClassNode.isAbstract()) {
                    continue;
                }
                if (onlyAccessible) {
                    if (Modifier.isPrivate(innerClassNode.getModifiers())) {
                        continue;
                    }
                }
                if (!modifierPredicates.isEmpty()) {
                    Set elementModifiers = resolveModifiers(innerClassNode);
                    if (!modifierPredicates.stream().allMatch(p -> p.test(elementModifiers))) {
                        continue;
                    }
                }

                if (!namePredicates.isEmpty()) {
                    if (!namePredicates.stream().allMatch(p -> p.test(innerClassNode.getName()))) {
                        continue;
                    }
                }
                ClassElement classElement = visitorContext.getElementFactory()
                        .newClassElement(innerClassNode, AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, innerClassNode));

                if (!typePredicates.isEmpty()) {
                    if (!typePredicates.stream().allMatch(p -> p.test(classElement))) {
                        continue;
                    }
                }

                innerClasses.add((T) classElement);
            }
            elements = innerClasses;
        } else {
            elements = Collections.emptyList();
        }
        if (!elements.isEmpty()) {
            if (!annotationPredicates.isEmpty()) {
                elements.removeIf(e -> !annotationPredicates.stream().allMatch(p -> p.test(e.getAnnotationMetadata())));
            }

            if (!elements.isEmpty() && !elementPredicates.isEmpty()) {
                elements.removeIf(e -> !elementPredicates.stream().allMatch(p -> p.test(e)));
            }
        }
        return elements;
    }

    private List findRelevantFields(
            boolean onlyAccessible,
            ClassElement onlyAccessibleType,
            List initialFields,
            List> namePredicates,
            List>> modifierPredicates) {
        List filteredFields = new ArrayList<>(initialFields.size());

        elementLoop:
        for (FieldNode fn: initialFields) {
            if (JUNK_FIELD_FILTER.test(fn)) {
                continue;
            }
            if (onlyAccessible && fn.isPrivate()) {
                continue;
            } else if (onlyAccessible && isPackageScope(fn)) {
                if (!fn.getDeclaringClass().getPackageName().equals(onlyAccessibleType.getPackageName())) {
                    continue;
                }
            }
            if (!modifierPredicates.isEmpty()) {
                final Set elementModifiers = resolveModifiers(fn);
                for (Predicate> modifierPredicate : modifierPredicates) {
                    if (!modifierPredicate.test(elementModifiers)) {
                        continue elementLoop;
                    }
                }
            }

            if (!namePredicates.isEmpty()) {
                String name = fn.getName();
                for (Predicate namePredicate : namePredicates) {
                    if (!namePredicate.test(name)) {
                        continue elementLoop;
                    }
                }
            }

            filteredFields.add(fn);
        }
        return filteredFields;
    }

    private boolean isPackageScope(FieldNode fn) {
        return (fn.getModifiers() & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED)) == 0;
    }

    @Override
    public Set getModifiers() {
        return resolveModifiers(this.classNode);
    }

    @Override
    public boolean isInner() {
        return classNode instanceof InnerClassNode;
    }

    @Override
    public Optional getEnclosingType() {
        if (isInner()) {
            ClassNode outerClass = classNode.getOuterClass();
            if (outerClass != null) {
                return Optional.of(
                        visitorContext.getElementFactory()
                            .newClassElement(
                                    outerClass,
                                    AstAnnotationUtils.getAnnotationMetadata(
                                            sourceUnit,
                                            compilationUnit,
                                            outerClass
                                    )
                            )
                );
            }
        }
        return Optional.empty();
    }

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

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

    @Override
    public Collection getInterfaces() {
        final ClassNode[] interfaces = classNode.getInterfaces();
        if (ArrayUtils.isNotEmpty(interfaces)) {
            return Arrays.stream(interfaces).map((cn) -> visitorContext.getElementFactory().newClassElement(
                    cn,
                    AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, cn)
            )).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }

    @Override
    public Optional getSuperType() {
        final ClassNode superClass = classNode.getUnresolvedSuperClass(false);
        if (superClass != null && !superClass.equals(ClassHelper.OBJECT_TYPE)) {
            return Optional.of(
                    visitorContext.getElementFactory().newClassElement(
                            superClass,
                            AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, superClass)
                    )
            );
        }
        return Optional.empty();
    }

    @NonNull
    @Override
    public Optional getPrimaryConstructor() {
        MethodNode method = findStaticCreator();
        if (method == null) {
            method = findConcreteConstructor();
        }

        return createMethodElement(method);
    }

    @NonNull
    @Override
    public Optional getDefaultConstructor() {
        MethodNode method = findDefaultStaticCreator();
        if (method == null) {
            method = findDefaultConstructor();
        }
        return createMethodElement(method);
    }

    private Optional createMethodElement(MethodNode method) {
        return Optional.ofNullable(method).map(executableElement -> {

            final AnnotationMetadata annotationMetadata = AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, executableElement);
            if (executableElement instanceof ConstructorNode) {
                return new GroovyConstructorElement(this, visitorContext, (ConstructorNode) executableElement, annotationMetadata);
            } else {
                return new GroovyMethodElement(this, visitorContext, executableElement, annotationMetadata);
            }
        });
    }

    /**
     * Builds and returns the generic type information.
     *
     * @return The generic type info
     */
    public Map> getGenericTypeInfo() {
        if (genericInfo == null) {
            genericInfo = AstGenericUtils.buildAllGenericElementInfo(classNode, new GroovyVisitorContext(sourceUnit, compilationUnit));
        }
        return genericInfo;
    }

    @NonNull
    @Override
    public Map getTypeArguments(@NonNull String type) {
        Map> allData = getGenericTypeInfo();

        Map thisSpec = allData.get(getName());
        Map forType = allData.get(type);
        if (forType != null) {
            Map typeArgs = new LinkedHashMap<>(forType.size());
            for (Map.Entry entry : forType.entrySet()) {
                ClassNode classNode = entry.getValue();

                AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(classNode);
                ClassElement rawElement = visitorContext.getElementFactory().newClassElement(classNode, annotationMetadata);
                if (thisSpec != null) {
                    rawElement = getGenericElement(sourceUnit, classNode, rawElement, thisSpec);
                }
                typeArgs.put(entry.getKey(), rawElement);
            }
            return Collections.unmodifiableMap(typeArgs);
        }

        return Collections.emptyMap();
    }

    @NonNull
    @Override
    public Map> getAllTypeArguments() {
        Map> genericInfo =
                AstGenericUtils.buildAllGenericElementInfo(classNode, new GroovyVisitorContext(sourceUnit, compilationUnit));
        Map> results = new LinkedHashMap<>(genericInfo.size());

        genericInfo.forEach((name, generics) -> {
            Map resolved = new LinkedHashMap<>(generics.size());
            generics.forEach((variable, type) -> {
                AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(type);
                resolved.put(variable, new GroovyClassElement(visitorContext, type, annotationMetadata));
            });
            results.put(name, resolved);
        });
        results.put(getName(), getTypeArguments());
        return results;
    }

    @Override
    public @NonNull
    Map getTypeArguments() {
        Map> genericInfo = getGenericTypeInfo();
        Map info = genericInfo.get(classNode.getName());
        return resolveGenericMap(info);
    }

    @NonNull
    private Map resolveGenericMap(Map info) {
        if (info != null) {
            Map typeArgumentMap = new LinkedHashMap<>(info.size());
            GenericsType[] genericsTypes = classNode.getGenericsTypes();
            GenericsType[] redirectTypes = classNode.redirect().getGenericsTypes();
            if (genericsTypes != null && redirectTypes != null && genericsTypes.length == redirectTypes.length) {
                for (int i = 0; i < genericsTypes.length; i++) {
                    GenericsType gt = genericsTypes[i];
                    GenericsType redirectType = redirectTypes[i];
                    if (gt.isPlaceholder()) {
                        ClassNode cn = resolveTypeArgument(info, redirectType.getName());
                        if (cn != null) {
                            Map newInfo = alignNewGenericsInfo(genericsTypes, redirectTypes, info);
                            AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(cn);
                            typeArgumentMap.put(redirectType.getName(), new GroovyClassElement(
                                    visitorContext,
                                    cn,
                                    annotationMetadata,
                                    Collections.singletonMap(cn.getName(), newInfo),
                                    cn.isArray() ? computeDimensions(cn) : 0,
                                    true
                            ));
                        }
                    } else {
                        ClassNode type;
                        String unresolvedName = redirectType.getType().getUnresolvedName();
                        ClassNode cn = info.get(unresolvedName);
                        if (cn != null) {
                            type = cn;
                        } else {
                            type = gt.getType();
                        }
                        AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(type);
                        typeArgumentMap.put(redirectType.getName(), new GroovyClassElement(
                                visitorContext,
                                type,
                                annotationMetadata,
                                Collections.emptyMap(),
                                type.isArray() ? computeDimensions(type) : 0
                        ));
                    }
                }
            } else if (redirectTypes != null) {

                for (GenericsType gt : redirectTypes) {
                    String name = gt.getName();
                    ClassNode cn = resolveTypeArgument(info, name);
                    if (cn != null) {
                        Map newInfo = Collections.emptyMap();
                        if (genericsTypes != null) {
                            newInfo = alignNewGenericsInfo(genericsTypes, redirectTypes, info);
                        }
                        AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(cn);
                        typeArgumentMap.put(gt.getName(), new GroovyClassElement(
                                visitorContext,
                                cn,
                                annotationMetadata,
                                Collections.singletonMap(cn.getName(), newInfo),
                                cn.isArray() ? computeDimensions(cn) : 0
                        ));
                    }
                }
            }
            if (CollectionUtils.isNotEmpty(typeArgumentMap)) {
                return typeArgumentMap;
            }
        }
        Map spec = AstGenericUtils.createGenericsSpec(classNode);
        if (!spec.isEmpty()) {
            Map map = new LinkedHashMap<>(spec.size());
            for (Map.Entry entry : spec.entrySet()) {
                ClassNode cn = entry.getValue();
                AnnotationMetadata annotationMetadata = resolveAnnotationMetadata(cn);
                ClassElement classElement = visitorContext.getElementFactory().newClassElement(cn, annotationMetadata);
                map.put(entry.getKey(), classElement);
            }
            return Collections.unmodifiableMap(map);
        }
        return Collections.emptyMap();
    }

    @Nullable
    private ClassNode resolveTypeArgument(Map info, String name) {
        ClassNode cn = info.get(name);
        while (cn != null && cn.isGenericsPlaceHolder()) {
            name = cn.getUnresolvedName();
            ClassNode next = info.get(name);
            if (next == cn) {
                break;
            }
            cn = next;
        }
        return cn;
    }

    private int computeDimensions(ClassNode cn) {
        ClassNode componentType = cn.getComponentType();
        int i = 1;
        while (componentType != null && componentType.isArray()) {
            i++;
            componentType = componentType.getComponentType();
        }
        return i;
    }

    @Override
    public List getBeanProperties() {
        List allProperties = new ArrayList<>();
        List propertyElements = getPropertyNodes();
        allProperties.addAll(propertyElements);
        allProperties.addAll(getPropertiesFromGettersAndSetters(propertyElements));
        return Collections.unmodifiableList(allProperties);
    }

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

    @Override
    public ClassElement withArrayDimensions(int arrayDimensions) {
        return new GroovyClassElement(visitorContext, classNode, getAnnotationMetadata(), getGenericTypeInfo(), arrayDimensions);
    }

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

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

    @Override
    public String getName() {
        return classNode.getName();
    }

    @Override
    public String getSimpleName() {
        return classNode.getNameWithoutPackage();
    }

    @Override
    public String getPackageName() {
        return classNode.getPackageName();
    }

    @Override
    public PackageElement getPackage() {
        final PackageNode aPackage = classNode.getPackage();
        if (aPackage != null) {

            return new GroovyPackageElement(
                    visitorContext,
                    aPackage,
                    AstAnnotationUtils.getAnnotationMetadata(
                            sourceUnit,
                            compilationUnit,
                            aPackage
                    )
            );
        } else {
            return PackageElement.DEFAULT_PACKAGE;
        }
    }

    @Override
    public boolean isAbstract() {
        return classNode.isAbstract();
    }

    @Override
    public boolean isStatic() {
        return classNode.isStaticClass();
    }

    @Override
    public boolean isPublic() {
        return (classNode.isSyntheticPublic() || Modifier.isPublic(classNode.getModifiers())) && !isPackagePrivate();
    }

    @Override
    public boolean isPrivate() {
        return Modifier.isPrivate(classNode.getModifiers());
    }

    @Override
    public boolean isFinal() {
        return Modifier.isFinal(classNode.getModifiers());
    }

    @Override
    public boolean isProtected() {
        return Modifier.isProtected(classNode.getModifiers());
    }

    @Override
    public Object getNativeType() {
        return classNode;
    }

    @Override
    public boolean isAssignable(String type) {
        return AstClassUtils.isSubclassOfOrImplementsInterface(classNode, type);
    }

    @Override
    public boolean isAssignable(ClassElement type) {
        return AstClassUtils.isSubclassOfOrImplementsInterface(classNode, type.getName());
    }

    @NonNull
    @Override
    public List getBoundGenericTypes() {
        if (overrideBoundGenericTypes == null) {
            overrideBoundGenericTypes = getBoundGenericTypes(classNode);
        }
        return overrideBoundGenericTypes;
    }

    @NonNull
    private List getBoundGenericTypes(ClassNode classNode) {
        GenericsType[] genericsTypes = classNode.getGenericsTypes();
        if (genericsTypes == null) {
            return Collections.emptyList();
        } else {
            return Arrays.stream(genericsTypes)
                    .map(cn -> {
                        if (cn.isWildcard()) {
                            List upperBounds;
                            if (cn.getUpperBounds() != null && cn.getUpperBounds().length > 0) {
                                upperBounds = Arrays.stream(cn.getUpperBounds())
                                        .map(bound -> (GroovyClassElement) toClassElement(bound))
                                        .collect(Collectors.toList());
                            } else {
                                upperBounds = Collections.singletonList((GroovyClassElement) visitorContext.getClassElement(Object.class).get());
                            }
                            List lowerBounds;
                            if (cn.getLowerBound() == null) {
                                lowerBounds = Collections.emptyList();
                            } else {
                                lowerBounds = Collections.singletonList((GroovyClassElement) toClassElement(cn.getLowerBound()));
                            }
                            return new GroovyWildcardElement(
                                    upperBounds,
                                    lowerBounds
                            );
                        } else {
                            return toClassElement(cn.getType());
                        }
                    })
                    .collect(Collectors.toList());
        }
    }

    @NonNull
    @Override
    public List getDeclaredGenericPlaceholders() {
        //noinspection unchecked
        return (List) getBoundGenericTypes(classNode.redirect());
    }

    protected final ClassElement toClassElement(ClassNode classNode) {
        return visitorContext.getElementFactory().newClassElement(classNode, AnnotationMetadata.EMPTY_METADATA);
    }

    @NonNull
    @Override
    public ClassElement withBoundGenericTypes(@NonNull List typeArguments) {
        // we can't create a new ClassNode, so we have to go this route.
        GroovyClassElement copy = (GroovyClassElement) visitorContext.getElementFactory().newClassElement(classNode, getAnnotationMetadata());
        copy.overrideBoundGenericTypes = typeArguments;
        return copy;
    }

    private List getPropertyNodes() {
        List propertyNodes = new ArrayList<>();
        ClassNode classNode = this.classNode;
        while (classNode != null && !classNode.equals(ClassHelper.OBJECT_TYPE) && !classNode.equals(ClassHelper.Enum_Type)) {
            propertyNodes.addAll(classNode.getProperties());
            classNode = classNode.getSuperClass();
        }
        List propertyElements = new ArrayList<>();
        for (PropertyNode propertyNode : propertyNodes) {
            if (propertyNode.isPublic() && !propertyNode.isStatic()) {
                final String propertyName = propertyNode.getName();
                boolean readOnly = propertyNode.getField().isFinal();
                final AnnotationMetadata annotationMetadata =
                        AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, propertyNode.getField());
                GroovyPropertyElement groovyPropertyElement = new GroovyPropertyElement(
                        visitorContext,
                        this,
                        propertyNode.getField(),
                        annotationMetadata,
                        propertyName,
                        readOnly,
                        propertyNode
                ) {

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

                    @NonNull
                    @Override
                    public ClassElement getType() {
                        ClassNode type = propertyNode.getType();
                        return visitorContext.getElementFactory().newClassElement(type,
                                AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, type));
                    }

                    @Override
                    public Optional getWriteMethod() {
                        if (!readOnly) {
                            return Optional.of(MethodElement.of(
                                    GroovyClassElement.this,
                                    annotationMetadata,
                                    PrimitiveElement.VOID,
                                    PrimitiveElement.VOID,
                                    NameUtils.setterNameFor(propertyName, writePrefixes),
                                    ParameterElement.of(getType(), propertyName)

                            ));
                        }
                        return Optional.empty();
                    }

                    @Override
                    public Optional getReadMethod() {
                        return Optional.of(MethodElement.of(
                                GroovyClassElement.this,
                                annotationMetadata,
                                getType(),
                                getGenericType(),
                                getGetterName(propertyName, getType())
                        ));
                    }

                    private String getGetterName(String propertyName, ClassElement type) {
                        return NameUtils.getterNameFor(
                                propertyName,
                                type.equals(PrimitiveElement.BOOLEAN) || type.getName().equals(Boolean.class.getName())
                        );
                    }
                };
                propertyElements.add(groovyPropertyElement);
            }
        }
        return propertyElements;
    }

    private List getPropertiesFromGettersAndSetters(List propertyNodes) {
        Set groovyProps = propertyNodes.stream().map(PropertyElement::getName).collect(Collectors.toSet());
        Map props = new LinkedHashMap<>();
        ClassNode classNode = this.classNode;
        while (classNode != null && !classNode.equals(ClassHelper.OBJECT_TYPE) && !classNode.equals(ClassHelper.Enum_Type)) {

            classNode.visitContents(
                    new PublicMethodVisitor(null) {

                        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(MethodNode node) {
                            boolean validModifiers = node.isPublic() && !node.isStatic() && !node.isSynthetic() && !node.isAbstract();
                            if (validModifiers) {
                                String methodName = node.getName();
                                if (methodName.contains("$") || methodName.equals("getMetaClass")) {
                                    return false;
                                }

                                if (NameUtils.isReaderName(methodName, readPrefixes) && node.getParameters().length == 0) {
                                    return true;
                                } else {
                                    return NameUtils.isWriterName(methodName, writePrefixes) && node.getParameters().length == 1;
                                }
                            }
                            return validModifiers;
                        }

                        @Override
                        public void accept(ClassNode classNode, MethodNode node) {
                            String methodName = node.getName();
                            final ClassNode declaringTypeElement = node.getDeclaringClass();
                            if (NameUtils.isReaderName(methodName, readPrefixes) && node.getParameters().length == 0) {
                                String propertyName = NameUtils.getPropertyNameForGetter(methodName, readPrefixes);
                                if (groovyProps.contains(propertyName)) {
                                    return;
                                }
                                ClassNode returnTypeNode = node.getReturnType();
                                ClassElement getterReturnType = null;
                                if (returnTypeNode.isGenericsPlaceHolder()) {
                                    final String placeHolderName = returnTypeNode.getUnresolvedName();
                                    final ClassElement classElement = getTypeArguments().get(placeHolderName);
                                    if (classElement != null) {
                                        getterReturnType = classElement;
                                    }
                                }
                                if (getterReturnType == null) {
                                    getterReturnType = visitorContext.getElementFactory().newClassElement(returnTypeNode, AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, returnTypeNode));
                                }

                                GetterAndSetter getterAndSetter = props.computeIfAbsent(propertyName, GetterAndSetter::new);
                                configureDeclaringType(declaringTypeElement, getterAndSetter);
                                getterAndSetter.type = getterReturnType;
                                getterAndSetter.getter = node;
                                if (getterAndSetter.setter != null) {
                                    ClassNode typeMirror = getterAndSetter.setter.getParameters()[0].getType();
                                    ClassElement setterParameterType = visitorContext.getElementFactory().newClassElement(typeMirror, AnnotationMetadata.EMPTY_METADATA);
                                    if (!setterParameterType.getName().equals(getterReturnType.getName())) {
                                        getterAndSetter.setter = null; // not a compatible setter
                                    }
                                }
                            } else if (NameUtils.isWriterName(methodName, writePrefixes) && node.getParameters().length == 1) {
                                String propertyName = NameUtils.getPropertyNameForSetter(methodName, writePrefixes);
                                if (groovyProps.contains(propertyName)) {
                                    return;
                                }
                                ClassNode typeMirror = node.getParameters()[0].getType();
                                ClassElement setterParameterType = visitorContext.getElementFactory().newClassElement(typeMirror, AnnotationMetadata.EMPTY_METADATA);

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

                        private void configureDeclaringType(ClassNode declaringTypeElement, GetterAndSetter beanPropertyData) {
                            if (beanPropertyData.declaringType == null) {
                                if (GroovyClassElement.this.classNode.equals(declaringTypeElement)) {
                                    beanPropertyData.declaringType = GroovyClassElement.this;
                                } else {
                                    beanPropertyData.declaringType = new GroovyClassElement(
                                            visitorContext,
                                            declaringTypeElement,
                                            AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, declaringTypeElement)
                                    );
                                }
                            }
                        }
                    });
            classNode = classNode.getSuperClass();
        }
        List propertyElements = new ArrayList<>(props.size());
        if (!props.isEmpty()) {
            GroovyClassElement thisElement = this;
            for (Map.Entry entry : props.entrySet()) {
                String propertyName = entry.getKey();
                GetterAndSetter value = entry.getValue();
                if (value.getter != null) {

                    final AnnotationMetadata annotationMetadata;
                    final GroovyAnnotationMetadataBuilder groovyAnnotationMetadataBuilder = new GroovyAnnotationMetadataBuilder(sourceUnit, compilationUnit);
                    FieldNode field = value.declaringType.classNode.getField(propertyName);
                    if (field instanceof LazyInitializable) {
                        //this nonsense is to work around https://issues.apache.org/jira/browse/GROOVY-10398
                        ((LazyInitializable) field).lazyInit();
                        try {
                            Field delegate = field.getClass().getDeclaredField("delegate");
                            delegate.setAccessible(true);
                            field = (FieldNode) delegate.get(field);
                        } catch (NoSuchFieldException | IllegalAccessException e) {
                            // no op
                        }
                    }
                    final List parents = new ArrayList<>();
                    if (field != null) {
                        parents.add(field);
                    }
                    if (value.setter != null) {
                        parents.add(value.setter);
                    }
                    if (!parents.isEmpty()) {
                        annotationMetadata = AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, parents, value.getter);
                    } else {
                        annotationMetadata = groovyAnnotationMetadataBuilder.buildForMethod(value.getter);
                    }
                    GroovyPropertyElement propertyElement = new GroovyPropertyElement(
                            visitorContext,
                            value.declaringType,
                            value.getter,
                            annotationMetadata,
                            propertyName,
                            value.setter == null,
                            value.getter) {
                        @Override
                        public Optional getWriteMethod() {
                            if (value.setter != null) {
                                return Optional.of(new GroovyMethodElement(
                                        thisElement,
                                        visitorContext,
                                        value.setter,
                                        groovyAnnotationMetadataBuilder.buildForMethod(value.setter)
                                ));
                            }
                            return Optional.empty();
                        }

                        @NonNull
                        @Override
                        public ClassElement getType() {
                            return value.type;
                        }

                        @Override
                        public Optional getReadMethod() {
                            return Optional.of(new GroovyMethodElement(thisElement, visitorContext, value.getter, annotationMetadata));
                        }
                    };
                    propertyElements.add(propertyElement);
                }
            }
        }
        return propertyElements;
    }

    private MethodNode findConcreteConstructor() {
        List constructors = classNode.getDeclaredConstructors();
        if (CollectionUtils.isEmpty(constructors) && !classNode.isAbstract() && !classNode.isEnum()) {
            return new ConstructorNode(Modifier.PUBLIC, new BlockStatement()); // empty default constructor
        }

        List nonPrivateConstructors = findNonPrivateMethods(constructors);

        MethodNode methodNode;
        if (nonPrivateConstructors.size() == 1) {
            methodNode = nonPrivateConstructors.get(0);
        } else {
            methodNode = nonPrivateConstructors.stream()
                    .filter(cn -> {
                        AnnotationMetadata annotationMetadata = AstAnnotationUtils.getAnnotationMetadata(sourceUnit, compilationUnit, cn);
                        return annotationMetadata.hasAnnotation(AnnotationUtil.INJECT) ||
                                annotationMetadata.hasAnnotation(Creator.class);
                    })
                    .findFirst().orElse(null);
            if (methodNode == null) {
                methodNode = nonPrivateConstructors.stream().filter(cn -> Modifier.isPublic(cn.getModifiers())).findFirst().orElse(null);
            }
        }
        return methodNode;
    }

    private MethodNode findDefaultConstructor() {
        List constructors = classNode.getDeclaredConstructors();
        if (CollectionUtils.isEmpty(constructors) && !classNode.isEnum()) {
            return new ConstructorNode(Modifier.PUBLIC, new BlockStatement()); // empty default constructor
        }

        constructors = findNonPrivateMethods(constructors).stream()
                .filter(ctor -> ctor.getParameters().length == 0)
                .collect(Collectors.toList());

        if (constructors.isEmpty()) {
            return null;
        }

        if (constructors.size() == 1) {
            return constructors.get(0);
        }

        return constructors.stream()
                .filter(method -> Modifier.isPublic(method.getModifiers()))
                .findFirst().orElse(null);
    }

    private MethodNode findStaticCreator() {
        List creators = findNonPrivateStaticCreators();

        if (creators.isEmpty()) {
            return null;
        }
        if (creators.size() == 1) {
            return creators.get(0);
        }

        //Can be multiple static @Creator methods. Prefer one with args here. The no arg method (if present) will
        //be picked up by staticDefaultCreatorFor
        List withArgs = creators.stream()
                .filter(method -> method.getParameters().length > 0)
                .collect(Collectors.toList());

        if (withArgs.size() == 1) {
            return withArgs.get(0);
        } else {
            creators = withArgs;
        }

        return creators.stream()
                .filter(method -> Modifier.isPublic(method.getModifiers()))
                .findFirst().orElse(null);
    }

    private MethodNode findDefaultStaticCreator() {
        List creators = findNonPrivateStaticCreators().stream()
                .filter(ctor -> ctor.getParameters().length == 0)
                .collect(Collectors.toList());

        if (creators.isEmpty()) {
            return null;
        }

        if (creators.size() == 1) {
            return creators.get(0);
        }

        return creators.stream()
                .filter(method -> Modifier.isPublic(method.getModifiers()))
                .findFirst().orElse(null);
    }

    private  List findNonPrivateMethods(List methodNodes) {
        List nonPrivateMethods = new ArrayList<>(2);
        for (MethodNode node : methodNodes) {
            if (!Modifier.isPrivate(node.getModifiers())) {
                nonPrivateMethods.add((T) node);
            }
        }
        return nonPrivateMethods;
    }

    private List findNonPrivateStaticCreators() {
        List creators = classNode.getAllDeclaredMethods().stream()
                .filter(method -> Modifier.isStatic(method.getModifiers()))
                .filter(method -> !Modifier.isPrivate(method.getModifiers()))
                .filter(method -> method.getReturnType().equals(classNode))
                .filter(method -> method.getAnnotations(makeCached(Creator.class)).size() > 0)
                .collect(Collectors.toList());

        if (creators.isEmpty() && classNode.isEnum()) {
            creators = classNode.getAllDeclaredMethods().stream()
                    .filter(method -> Modifier.isStatic(method.getModifiers()))
                    .filter(method -> !Modifier.isPrivate(method.getModifiers()))
                    .filter(method -> method.getReturnType().equals(classNode))
                    .filter(method -> method.getName().equals("valueOf"))
                    .collect(Collectors.toList());
        }
        return creators;
    }

    /**
     * Internal holder class for getters and setters.
     */
    private class GetterAndSetter {
        ClassElement type;
        GroovyClassElement declaringType;
        MethodNode getter;
        MethodNode setter;
        final String propertyName;

        GetterAndSetter(String propertyName) {
            this.propertyName = propertyName;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy