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

io.micronaut.ast.groovy.utils.AstGenericUtils.groovy 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.utils

import groovy.transform.CompileStatic
import io.micronaut.core.util.ArrayUtils
import io.micronaut.inject.visitor.VisitorContext
import org.codehaus.groovy.ast.ClassHelper
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.GenericsType
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.tools.GenericsUtils
/**
 * @author Graeme Rocher
 * @since 1.0
 */
@CompileStatic
class AstGenericUtils {

    /**
     * Variation that takes into account non primary nodes
     *
     * @param classNode The class node
     * @return The generics spec
     */
    static Map createGenericsSpec(ClassNode classNode) {
        GenericsType[] existingGenericTypes = classNode.genericsTypes
        boolean hasGenericTypes = ArrayUtils.isNotEmpty(existingGenericTypes)
        if (!classNode.isPrimaryClassNode()) {
            if (hasGenericTypes) {
                GenericsType[] redirectTypes = classNode.redirect().getGenericsTypes()
                Map ret = new LinkedHashMap()
                populateGenericsSpec(redirectTypes, existingGenericTypes, ret)
                return ret
            } else {

                classNode = classNode.redirect()
                Map ret = new LinkedHashMap()
                GenericsType[] redirectTypes = classNode.getGenericsTypes()
                if (redirectTypes != null) {
                    for (GenericsType gt in redirectTypes) {
                        if (gt.isPlaceholder()) {
                            if (gt.upperBounds) {
                                ret.put(gt.name, gt.upperBounds[0])
                            } else if (gt.lowerBound) {
                                ret.put(gt.name, gt.lowerBound)
                            } else {
                                ret.put(gt.name, ClassHelper.OBJECT_TYPE)
                            }
                        } else {
                            ret.put(gt.name, gt.type)
                        }
                    }
                }
                return ret
            }
        } else {
            if (!hasGenericTypes) {
                return Collections.emptyMap()
            } else {
                return createGenericsSpec(classNode, Collections.emptyMap())
            }
        }
    }

    static Map createGenericsSpec(ClassNode current, Map oldSpec) {
        Map ret = new LinkedHashMap(oldSpec);
        // ret contains the type specs, what we now need is the type spec for the
        // current class. To get that we first apply the type parameters to the
        // current class and then use the type names of the current class to reset
        // the map. Example:
        //   class A{}
        //   class B extends A {}
        // first we have:    T->Number
        // we apply it to A -> A
        // resulting in:     V->Number,W->Long,X->String

        GenericsType[] sgts = current.getGenericsTypes();
        if (sgts != null) {
            ClassNode[] spec = new ClassNode[sgts.length];
            for (int i = 0; i < spec.length; i++) {
                spec[i] = GenericsUtils.correctToGenericsSpec(ret, sgts[i]);
            }
            GenericsType[] newGts = current.redirect().getGenericsTypes();
            if (newGts == null) return ret;
            ret.clear();
            for (int i = 0; i < spec.length; i++) {
                ret.put(newGts[i].getName(), spec[i]);
            }
        }
        return ret;
    }

    /**
     * Populates generics for a method
     *
     * @param methodNode The method node node
     * @param genericsSpec The spec to populate
     * @return The generics spec
     */
    static Map createGenericsSpec(MethodNode methodNode, Map genericsSpec) {
        GenericsType[] redirectTypes = methodNode.genericsTypes
        if (redirectTypes != null) {
            for (GenericsType gt in redirectTypes) {
                if (gt.isPlaceholder()) {
                    if (gt.upperBounds) {
                        genericsSpec.put(gt.name, gt.upperBounds[0])
                    } else if (gt.lowerBound) {
                        genericsSpec.put(gt.name, gt.lowerBound)
                    } else {
                        genericsSpec.put(gt.name, ClassHelper.OBJECT_TYPE)
                    }
                } else {
                    genericsSpec.put(gt.name, gt.type)
                }
            }
        }
        return genericsSpec
    }

    private static void populateGenericsSpec(GenericsType[] redirectTypes, GenericsType[] genericTypes, HashMap boundTypes) {
        if (redirectTypes && redirectTypes.length == genericTypes.length) {
            int i = 0
            for (GenericsType redirect in redirectTypes) {
                GenericsType specifiedType = genericTypes[i]
                ClassNode specifiedClassNode = specifiedType.type
                if (redirect.isPlaceholder() && specifiedClassNode) {
                    if (specifiedType.isPlaceholder()) {
                        if (specifiedType.upperBounds) {
                            boundTypes.put(redirect.name, specifiedType.upperBounds[0])
                        } else if (specifiedType.lowerBound) {
                            boundTypes.put(redirect.name, specifiedType.lowerBound)
                        } else {
                            boundTypes.put(redirect.name, specifiedClassNode.redirect().plainNodeReference)
                        }
                    } else {
                        def specifiedGenerics = specifiedType.getType().getGenericsTypes()
                        if (specifiedGenerics) {
                            boundTypes.put(redirect.name, specifiedType.type)
                        } else {
                            boundTypes.put(redirect.name, specifiedClassNode.redirect())
                        }
                    }
                }

                i++
            }
        }
    }

    /**
     * Finds the generic type for the given interface for the given class node
     *
     * @param classNode The class node
     * @param itfe The interface
     * @return The generic type or null
     */
    static ClassNode resolveInterfaceGenericType(ClassNode classNode, Class itfe) {
        ClassNode foundInterface = classNode.allInterfaces.find() { it.name == itfe.name }
        if (foundInterface != null) {
            if (foundInterface.genericsTypes) {
                return foundInterface.genericsTypes[0]?.type
            }
        }
        return null
    }

    /**
     * Resolve a type reference for the given node.
     * @param classNode The class node
     * @return A type reference. Either a java.lang.Class or a string representing the name of the class
     */
    static ClassNode resolveTypeReference(ClassNode classNode, Map boundTypes = Collections.emptyMap()) {
        if (classNode == null) {
            return null
        } else {
            if (classNode.isGenericsPlaceHolder()) {
                if (classNode.genericsTypes) {

                    String typeVar = classNode.genericsTypes[0].name
                    if (boundTypes.containsKey(typeVar)) {
                        def resolved = boundTypes.get(typeVar)
                        if (resolved.isGenericsPlaceHolder()) {
                            return resolveTypeReference(resolved, boundTypes)
                        } else {
                            return resolved
                        }
                    }
                } else {
                    if (classNode.isResolved() || ClassHelper.isPrimitiveType(classNode)) {
                        try {
                            return classNode
                        } catch (ClassNotFoundException ignored) {
                            return classNode
                        }
                    } else {
                        def redirectNode = classNode.redirect()
                        String redirectName = redirectNode.name
                        if (redirectName != classNode.unresolvedName) {
                            return redirectNode
                        } else {
                            return ClassHelper.OBJECT_TYPE
                        }
                    }
                }
            } else if (classNode.isArray() && classNode.componentType.isGenericsPlaceHolder()) {
                GenericsType[] componentGenericTypes = classNode.componentType.genericsTypes
                if (componentGenericTypes) {
                    String typeVar = componentGenericTypes[0].name
                    if (boundTypes.containsKey(typeVar)) {
                        ClassNode arrayNode = boundTypes.get(typeVar).makeArray()
                        return arrayNode
                    }
                }
            }

            try {
                return classNode
            } catch (ClassNotFoundException ignored) {
                return classNode
            }
        }
    }

    /**
     * Builds all the generic information for the given type
     * @param classNode
     * @return
     */
    static Map> buildAllGenericElementInfo(ClassNode classNode, VisitorContext visitorContext) {
        Map> typeArguments = new LinkedHashMap<>()

        populateTypeArguments(classNode, typeArguments)

        Map> elements = new LinkedHashMap<>(typeArguments.size())
        for (Map.Entry> entry : typeArguments.entrySet()) {
            Map value = entry.getValue()
            HashMap submap = new LinkedHashMap<>(value.size())
            for (Map.Entry genericEntry : value.entrySet()) {
                def v = genericEntry.getValue()
                ClassNode te = null
                if (v instanceof ClassNode) {
                  te = v
                } else if (v instanceof Class) {
                    te = ClassHelper.makeCached( (Class)v )
                } else if(v instanceof String) {
                    String className = v.toString()
                    te = findGenericClassInNode(classNode, className)
                    if (te == null) {
                        def ce = visitorContext.getClassElement(className).orElse(null)
                        def nativeType = ce?.nativeType
                        if (nativeType instanceof ClassNode) {
                            te = (ClassNode) nativeType
                        }
                    }
                }
                if (te != null) {
                    submap.put(genericEntry.getKey(), te)
                }
            }
            elements.put(entry.getKey(), submap)
        }
        return elements
    }

    static ClassNode findGenericClassInNode(ClassNode classNode, String className) {
        GenericsType[] genericsTypes = classNode.getGenericsTypes()

        if (ArrayUtils.isNotEmpty(genericsTypes)) {
            for (gt in genericsTypes) {
                if (gt.type?.name == className) {
                    return gt.type
                }
            }
        }

        def interfaces = classNode.getInterfaces()
        for (i in interfaces) {
            if (i.name == classNode.name) {
                continue
            }
            def node = findGenericClassInNode(i, className)
            if (node != null) {
                return node
            }
        }

        if (!classNode.isInterface()) {
            def superClass = classNode.getSuperClass()

            while (superClass != null && superClass.name != ClassHelper.OBJECT) {
                def node = findGenericClassInNode(superClass, className)
                if (node != null) {
                    return node
                }
                superClass = superClass.getSuperClass()
            }
        }

        return null
    }

    static void populateTypeArguments(ClassNode typeElement, Map> typeArguments) {
        ClassNode current = typeElement
        ClassNode last = null
        while (current != null) {

            if (current != ClassHelper.OBJECT_TYPE) {
                GenericsType[] superArguments = current.redirect().getGenericsTypes()
                if (ArrayUtils.isNotEmpty(superArguments)) {
                    Map genericSpec = createGenericsSpec(current)
                    Map arguments = new LinkedHashMap<>(3)
                    if (genericSpec) {
                        for (gt in superArguments) {
                            ClassNode cn = genericSpec.get(gt.name)
                            if (cn != null) {
                                arguments.put(gt.name, resolveTypeReference(cn, genericSpec))
                            }
                        }
                    }
                    if (last != null) {
                        carryForwardTypeArguments(last, typeArguments, arguments)
                    }
                    typeArguments.put(current.name, arguments)
                }
            }

            populateTypeArgumentsForInterfaces(typeArguments, current)

            last = current
            current = current.getUnresolvedSuperClass()
        }
    }

    private static void populateTypeArgumentsForInterfaces(Map> typeArguments, ClassNode current) {
        for (ClassNode anInterface : current.getInterfaces()) {
            String name = anInterface.name
            if (!typeArguments.containsKey(name)) {

                Map genericSpec = createGenericsSpec(anInterface)

                if (genericSpec) {
                    Map types = [:]
                    for (entry in genericSpec) {
                        types[entry.key] = resolveTypeReference(entry.value, genericSpec)
                    }
                    carryForwardTypeArguments(current, typeArguments, types)
                    typeArguments.put(name, types)
                }

                populateTypeArgumentsForInterfaces(typeArguments, anInterface)
            }
        }
    }

    private static void carryForwardTypeArguments(ClassNode child, Map> typeArguments, Map types) {
        String childName = child.name
        if (typeArguments.containsKey(childName)) {
            typeArguments.get(childName).forEach({ arg, type ->
                if (types.containsKey(arg)) {
                    types.put(arg, type)
                }
            })
        }
    }

    /**
     * For a given classnode, fills in the supplied map with the parameterized
     * types it defines.
     * @param node
     * @param map
     */
    static void extractPlaceholders(ClassNode node, Map map, Map boundTypes) {
        if (node == null) return

        if (node.isArray()) {
            extractPlaceholders(node.getComponentType(), map, boundTypes)
            return
        }

        if (!node.isUsingGenerics() || !node.isRedirectNode()) return
        GenericsType[] parameterized = node.getGenericsTypes()
        if (parameterized == null || parameterized.length == 0) return
        GenericsType[] redirectGenericsTypes = node.redirect().getGenericsTypes()
        if (redirectGenericsTypes == null) redirectGenericsTypes = parameterized
        for (int i = 0; i < redirectGenericsTypes.length; i++) {
            GenericsType redirectType = redirectGenericsTypes[i]
            if (redirectType.isPlaceholder()) {
                String name = redirectType.getName()
                if (!map.containsKey(name)) {
                    if (i >= parameterized.length) {
                        continue
                    }
                    GenericsType value = parameterized[i]
                    ClassNode cn = value.type
                    Object typeRef = resolveTypeReference(cn)

                    if (value.isWildcard()) {
                        ClassNode lowerBound = value.getLowerBound()
                        if (lowerBound != null) {
                            def newMap = new LinkedHashMap()
                            map.put(name, Collections.singletonMap(cn.name, newMap))
                            extractPlaceholders(lowerBound, newMap, boundTypes)
                        }
                        ClassNode[] upperBounds = value.getUpperBounds()
                        if (upperBounds != null) {
                            for (ClassNode upperBound : upperBounds) {
                                if (upperBound.isGenericsPlaceHolder()) {
                                    map.put(name, resolveTypeReference(upperBound, boundTypes))
                                } else {
                                    def newMap = new LinkedHashMap()
                                    map.put(name, Collections.singletonMap(upperBound.name, newMap))
                                    if (cn.isUsingGenerics()) {
                                        extractPlaceholders(upperBound, newMap, boundTypes)
                                    }
                                }

                            }
                        }
                    } else if (!value.isPlaceholder()) {
                        if (!cn.isUsingGenerics()) {
                            map.put(name, typeRef)
                        } else {
                            def newMap = new LinkedHashMap()
                            map.put(name, Collections.singletonMap(cn.name, newMap))
                            extractPlaceholders(cn, newMap, boundTypes)
                        }
                    } else {
                        if (boundTypes.containsKey(value.name)) {
                            map.put(name, resolveTypeReference(boundTypes.get(value.name), boundTypes))
                        } else {
                            map.put(name, resolveTypeReference(value.type, boundTypes))
                        }
                    }
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy