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

io.micronaut.annotation.processing.GenericUtils 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.annotation.processing;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.processing.JavaModelUtils;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.util.*;

/**
 * Utility methods for dealing with generic type signatures.
 *
 * @author Graeme Rocher
 */
@Internal
public class GenericUtils {

    private final Elements elementUtils;
    private final Types typeUtils;
    private final ModelUtils modelUtils;

    /**
     * @param elementUtils The {@link Elements}
     * @param typeUtils    The {@link Types}
     * @param modelUtils   The {@link ModelUtils}
     */
    protected GenericUtils(Elements elementUtils, Types typeUtils, ModelUtils modelUtils) {
        this.elementUtils = elementUtils;
        this.typeUtils = typeUtils;
        this.modelUtils = modelUtils;
    }

    /**
     * Builds type argument information for the given type.
     *
     * @param dt The declared type
     * @return The type argument information
     */
    Map> buildGenericTypeArgumentInfo(DeclaredType dt) {
        Element element = dt.asElement();
        return buildGenericTypeArgumentInfo(element, dt, Collections.emptyMap());
    }

    /**
     * Builds type argument information for the given type.
     *
     * @param element The element
     * @return The type argument information
     */
    public Map> buildGenericTypeArgumentElementInfo(@NonNull Element element) {
        return buildGenericTypeArgumentElementInfo(element, null);
    }

    /**
     * Builds type argument information for the given type.
     *
     * @param element The element
     * @param declaredType The declared type
     * @return The type argument information
     */
    public Map> buildGenericTypeArgumentElementInfo(@NonNull Element element, @Nullable DeclaredType declaredType) {
        return buildGenericTypeArgumentInfo(element, declaredType, Collections.emptyMap());
    }

    /**
     * Builds type argument information for the given type.
     *
     * @param element The element
     * @param declaredType The declared type
     * @param boundTypes The type variables
     * @return The type argument information
     */
    public Map> buildGenericTypeArgumentElementInfo(@NonNull Element element, @Nullable DeclaredType declaredType, Map boundTypes) {
        return buildGenericTypeArgumentInfo(element, declaredType, boundTypes);
    }

    private Map> buildGenericTypeArgumentInfo(@NonNull Element element, @Nullable DeclaredType dt, Map boundTypes) {

        Map> beanTypeArguments = new LinkedHashMap<>();
        if (dt != null) {

            List typeArguments = dt.getTypeArguments();
            if (CollectionUtils.isNotEmpty(typeArguments)) {
                TypeElement typeElement = (TypeElement) element;

                Map directTypeArguments = resolveBoundTypes(dt);
                if (CollectionUtils.isNotEmpty(directTypeArguments)) {
                    beanTypeArguments.put(typeElement.getQualifiedName().toString(), directTypeArguments);
                }
            }
        }

        if (element instanceof TypeElement) {
            TypeElement typeElement = (TypeElement) element;
            if (CollectionUtils.isNotEmpty(boundTypes)) {
                beanTypeArguments.put(JavaModelUtils.getClassName(typeElement), boundTypes);
            }
            populateTypeArguments(typeElement, beanTypeArguments);
        }
        return beanTypeArguments;
    }

    /**
     * Finds the generic types for the given interface for the given class element.
     *
     * @param element       The class element
     * @param interfaceName The interface
     * @return The generic types or an empty list
     */
    public List interfaceGenericTypesFor(TypeElement element, String interfaceName) {
        for (TypeMirror tm : element.getInterfaces()) {
            DeclaredType declaredType = (DeclaredType) tm;
            Element declaredElement = declaredType.asElement();
            if (declaredElement instanceof TypeElement) {
               TypeElement te = (TypeElement) declaredElement;
                if (interfaceName.equals(te.getQualifiedName().toString())) {
                    return declaredType.getTypeArguments();
                }
            }
        }
        return Collections.emptyList();
    }

    /**
     * Return the first type argument for the given type mirror. For example for Optional<String> this will
     * return {@code String}.
     *
     * @param type The type
     * @return The first argument.
     */
    protected Optional getFirstTypeArgument(TypeMirror type) {
        TypeMirror typeMirror = null;

        if (type instanceof DeclaredType) {
            DeclaredType declaredType = (DeclaredType) type;
            List typeArguments = declaredType.getTypeArguments();
            if (CollectionUtils.isNotEmpty(typeArguments)) {
                typeMirror = typeArguments.get(0);
            }
        }
        return Optional.ofNullable(typeMirror);
    }

    /**
     * Resolve the generic type arguments for the given type mirror and bound type arguments.
     *
     * @param type        The declaring type
     * @param typeElement The type element
     * @param boundTypes  The bound types
     * @return A map of generic type arguments
     */
    private Map resolveGenericTypes(DeclaredType type, TypeElement typeElement, Map boundTypes) {
        List typeArguments = type.getTypeArguments();
        Map resolvedParameters = new LinkedHashMap<>();
        List typeParameters = typeElement.getTypeParameters();
        if (typeArguments.size() == typeParameters.size()) {
            Iterator i = typeArguments.iterator();
            for (TypeParameterElement typeParameter : typeParameters) {
                String parameterName = typeParameter.toString();
                TypeMirror mirror = i.next();

                TypeKind kind = mirror.getKind();
                switch (kind) {
                    case DECLARED:
                        resolvedParameters.put(parameterName, mirror);
                    break;
                    case TYPEVAR:
                        TypeVariable tv = (TypeVariable) mirror;
                        if (boundTypes.containsKey(tv.toString())) {
                            resolvedParameters.put(parameterName, boundTypes.get(tv.toString()));
                        } else {
                            TypeMirror upperBound = tv.getUpperBound();
                            TypeMirror lowerBound = tv.getLowerBound();
                            if (upperBound.getKind() != TypeKind.NULL) {
                                resolvedParameters.put(parameterName, resolveTypeReference(upperBound, boundTypes));
                            } else if (lowerBound.getKind() != TypeKind.NULL) {
                                resolvedParameters.put(parameterName, resolveTypeReference(lowerBound, boundTypes));
                            }
                        }
                        continue;
                    case ARRAY:
                    case BOOLEAN:
                    case BYTE:
                    case CHAR:
                    case DOUBLE:
                    case FLOAT:
                    case INT:
                    case LONG:
                    case SHORT:
                        resolveGenericTypeParameter(resolvedParameters, parameterName, mirror, boundTypes);
                    continue;
                    case WILDCARD:
                        WildcardType wcType = (WildcardType) mirror;
                        TypeMirror extendsBound = wcType.getExtendsBound();
                        TypeMirror superBound = wcType.getSuperBound();
                        if (extendsBound != null) {
                            resolveGenericTypeParameter(resolvedParameters, parameterName, extendsBound, boundTypes);
                        } else if (superBound != null) {
                            if (superBound instanceof TypeVariable) {
                                TypeVariable superTypeVar = (TypeVariable) superBound;
                                final TypeMirror upperBound = superTypeVar.getUpperBound();
                                if (upperBound != null && !type.equals(upperBound)) {
                                    resolveGenericTypeParameter(resolvedParameters, parameterName, superBound, boundTypes);
                                }
                            } else {
                                resolveGenericTypeParameter(resolvedParameters, parameterName, superBound, boundTypes);
                            }
                        } else {
                            resolvedParameters.put(parameterName, elementUtils.getTypeElement(Object.class.getName()).asType());
                        }
                        break;
                    default:
                        // no-op
                }
            }
        }
        return resolvedParameters;
    }

    /**
     * @param mirror The {@link TypeMirror}
     * @return The resolved type reference
     */
    protected TypeMirror resolveTypeReference(TypeMirror mirror) {
        return resolveTypeReference(mirror, Collections.emptyMap());
    }

    /**
     * Resolve a type reference to use for the given type mirror taking into account generic type variables.
     *
     * @param mirror     The mirror
     * @param boundTypes The already bound types for any type variable
     * @return A type reference
     */
    protected TypeMirror resolveTypeReference(TypeMirror mirror, Map boundTypes) {
        TypeKind kind = mirror.getKind();
        switch (kind) {
            case TYPEVAR:
                TypeVariable tv = (TypeVariable) mirror;
                String name = tv.toString();
                if (boundTypes.containsKey(name)) {
                    return boundTypes.get(name);
                } else {
                    return resolveTypeReference(tv.getUpperBound(), boundTypes);
                }
            case WILDCARD:
                WildcardType wcType = (WildcardType) mirror;
                TypeMirror extendsBound = wcType.getExtendsBound();
                TypeMirror superBound = wcType.getSuperBound();
                if (extendsBound == null && superBound == null) {
                    return elementUtils.getTypeElement(Object.class.getName()).asType();
                } else if (extendsBound != null) {
                    return resolveTypeReference(typeUtils.erasure(extendsBound), boundTypes);
                } else {
                    return resolveTypeReference(superBound, boundTypes);
                }
            case ARRAY:
                ArrayType arrayType = (ArrayType) mirror;
                TypeMirror reference = resolveTypeReference(arrayType.getComponentType(), boundTypes);
                return typeUtils.getArrayType(reference);
            default:
                return modelUtils.resolveTypeReference(mirror);
        }
    }

    /**
     * Resolve bound types for the given declared type.
     *
     * @param type The declaring type
     * @return The type bounds
     */
    protected Map resolveBoundTypes(DeclaredType type) {
        Map boundTypes = new LinkedHashMap<>(2);
        TypeElement element = (TypeElement) type.asElement();

        List typeParameters = element.getTypeParameters();
        List typeArguments = type.getTypeArguments();
        if (typeArguments.size() == typeParameters.size()) {
            Iterator i = typeArguments.iterator();
            for (TypeParameterElement typeParameter : typeParameters) {
                boundTypes.put(typeParameter.toString(), resolveTypeReference(i.next(), boundTypes));
            }
        }

        return boundTypes;
    }

    /**
     * Takes a type element and the bound generic information and re-aligns for the new type.
     *
     * @param typeElement The type element
     * @param typeArguments The type arguments
     * @param genericsInfo The generic info
     * @return The aligned generics
     */
    public Map> alignNewGenericsInfo(
            TypeElement typeElement,
            List typeArguments,
            Map genericsInfo) {
        String typeName = typeElement.getQualifiedName().toString();
        List typeParameters = typeElement.getTypeParameters();
        Map resolved = alignNewGenericsInfo(typeParameters, typeArguments, genericsInfo);
        if (!resolved.isEmpty()) {
            return Collections.singletonMap(
                    typeName,
                    resolved
            );
        }
        return Collections.emptyMap();
    }

    /**
     * Takes the bound generic information and re-aligns for the new type.
     *
     * @param typeParameters The type parameters
     * @param typeArguments The type arguments
     * @param genericsInfo The generic info
     * @return The aligned generics
     */
    public Map alignNewGenericsInfo(
            List typeParameters,
            List typeArguments,
            Map genericsInfo) {
        if (typeArguments.size() == typeParameters.size()) {

            Map resolved = new HashMap<>(typeArguments.size());
            Iterator i = typeArguments.iterator();
            for (TypeParameterElement typeParameter : typeParameters) {
                TypeMirror typeParameterMirror = i.next();
                String variableName = typeParameter.getSimpleName().toString();
                resolveVariableForMirror(genericsInfo, resolved, variableName, typeParameterMirror);
            }
            return resolved;
        }
        return Collections.emptyMap();
    }

    private void resolveVariableForMirror(
            Map genericsInfo,
            Map resolved,
            String variableName,
            TypeMirror mirror) {
        if (mirror instanceof TypeVariable) {
            TypeVariable tv = (TypeVariable) mirror;
            resolveTypeVariable(genericsInfo, resolved, variableName, tv);
        } else {
            if (mirror instanceof WildcardType) {
                WildcardType wt = (WildcardType) mirror;
                TypeMirror extendsBound = wt.getExtendsBound();
                if (extendsBound != null) {
                    resolveVariableForMirror(genericsInfo, resolved, variableName, extendsBound);
                } else {
                    TypeMirror superBound = wt.getSuperBound();
                    resolveVariableForMirror(genericsInfo, resolved, variableName, superBound);
                }
            } else if (mirror instanceof DeclaredType) {
                DeclaredType dt = (DeclaredType) mirror;
                List typeArguments = dt.getTypeArguments();
                if (CollectionUtils.isNotEmpty(typeArguments) && CollectionUtils.isNotEmpty(genericsInfo)) {
                    List resolvedArguments = new ArrayList<>(typeArguments.size());
                    for (TypeMirror typeArgument : typeArguments) {
                        if (typeArgument instanceof TypeVariable) {
                            TypeVariable tv = (TypeVariable) typeArgument;
                            String name = tv.toString();
                            TypeMirror bound = genericsInfo.get(name);
                            if (bound != null) {
                                resolvedArguments.add(bound);
                            } else {
                                resolvedArguments.add(typeArgument);
                            }
                        } else {
                            resolvedArguments.add(typeArgument);
                        }
                    }
                    TypeMirror[] typeMirrors = resolvedArguments.toArray(new TypeMirror[0]);
                    resolved.put(variableName, typeUtils.getDeclaredType((TypeElement) dt.asElement(), typeMirrors));
                } else {
                    resolved.put(variableName, mirror);
                }
            } else if (mirror instanceof ArrayType) {
                resolved.put(variableName, mirror);
            }
        }
    }

    private void resolveTypeVariable(
            Map genericsInfo,
            Map resolved,
            String variableName,
            TypeVariable variable) {
        String name = variable.toString();
        TypeMirror element = genericsInfo.get(name);
        if (element != null) {
            if (element instanceof DeclaredType) {
                DeclaredType dt = (DeclaredType) element;
                List typeArguments = dt.getTypeArguments();
                for (TypeMirror typeArgument : typeArguments) {
                    if (typeArgument instanceof TypeVariable) {
                        TypeVariable tv = (TypeVariable) typeArgument;
                        TypeMirror upperBound = tv.getUpperBound();
                        if (upperBound instanceof DeclaredType) {
                            resolved.put(variableName, upperBound);
                            break;
                        }

                        TypeMirror lowerBound = tv.getLowerBound();
                        if (lowerBound instanceof DeclaredType) {
                            resolved.put(variableName, lowerBound);
                            break;
                        }
                    }
                }

                if (!resolved.containsKey(variableName)) {
                    resolved.put(variableName, element);
                }
            } else {
                resolved.put(variableName, element);
            }
        } else {
            TypeMirror upperBound = variable.getUpperBound();
            if (upperBound instanceof TypeVariable) {
                resolveTypeVariable(genericsInfo, resolved, variableName, (TypeVariable) upperBound);
            } else if (upperBound instanceof DeclaredType) {
                resolved.put(
                        variableName,
                        upperBound
                );
            } else {
                TypeMirror lowerBound = variable.getLowerBound();
                if (lowerBound instanceof TypeVariable) {
                    resolveTypeVariable(genericsInfo, resolved, variableName, (TypeVariable) lowerBound);
                } else if (lowerBound instanceof DeclaredType) {
                    resolved.put(
                            variableName,
                            lowerBound
                    );
                }
            }
        }
    }

    private void resolveGenericTypeParameter(Map resolvedParameters, String parameterName, TypeMirror mirror, Map boundTypes) {
        if (mirror instanceof DeclaredType) {
            resolvedParameters.put(
                    parameterName,
                    mirror
            );
        } else if (mirror instanceof TypeVariable) {
            TypeVariable tv = (TypeVariable) mirror;
            String variableName = tv.toString();
            if (boundTypes.containsKey(variableName)) {
                resolvedParameters.put(
                        parameterName,
                        boundTypes.get(variableName)
                );
            } else {
                TypeMirror upperBound = tv.getUpperBound();
                if (upperBound instanceof DeclaredType) {
                    resolveGenericTypeParameter(
                            resolvedParameters,
                            parameterName,
                            upperBound,
                            boundTypes
                    );
                }
            }
        }
    }

    private void populateTypeArguments(TypeElement typeElement, Map> typeArguments) {
        TypeElement current = typeElement;
        while (current != null) {

            populateTypeArgumentsForInterfaces(typeArguments, current);
            TypeMirror superclass = current.getSuperclass();

            if (superclass.getKind() == TypeKind.NONE) {
                current = null;
            } else {
                if (superclass instanceof DeclaredType) {
                    DeclaredType dt = (DeclaredType) superclass;
                    List superArguments = dt.getTypeArguments();


                    Element te = dt.asElement();
                    if (te instanceof TypeElement) {
                        TypeElement child = current;
                        current = (TypeElement) te;
                        if (CollectionUtils.isNotEmpty(superArguments)) {
                            Map boundTypes = typeArguments.get(JavaModelUtils.getClassName(child));
                            if (boundTypes != null) {
                                Map types = resolveGenericTypes(dt, current, boundTypes);

                                String name = JavaModelUtils.getClassName(current);
                                typeArguments.put(name, types);
                            } else {
                                List typeParameters = current.getTypeParameters();
                                Map types = new LinkedHashMap<>(typeParameters.size());
                                if (typeParameters.size() == superArguments.size()) {
                                    Iterator i = superArguments.iterator();
                                    for (TypeParameterElement typeParameter : typeParameters) {
                                        String n = typeParameter.getSimpleName().toString();
                                        types.put(n, i.next());
                                    }
                                }
                                typeArguments.put(JavaModelUtils.getClassName(current), types);
                            }
                        }

                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
        }
    }

    private void populateTypeArgumentsForInterfaces(Map> typeArguments, TypeElement child) {
        for (TypeMirror anInterface : child.getInterfaces()) {
            if (anInterface instanceof DeclaredType) {
                DeclaredType declaredType = (DeclaredType) anInterface;
                Element element = declaredType.asElement();
                if (element instanceof TypeElement) {
                    TypeElement te = (TypeElement) element;
                    String name = JavaModelUtils.getClassName(te);
                    if (!typeArguments.containsKey(name)) {
                        Map boundTypes = typeArguments.get(JavaModelUtils.getClassName(child));
                        if (boundTypes == null) {
                            boundTypes = Collections.emptyMap();
                        }
                        Map types = resolveGenericTypes(declaredType, te, boundTypes);
                        if (!types.isEmpty()) {
                            typeArguments.put(name, types);
                        }
                    }
                    populateTypeArgumentsForInterfaces(typeArguments, te);
                }
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy