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

io.micronaut.annotation.processing.JavaAnnotationMetadataBuilder Maven / Gradle / Ivy

/*
 * Copyright 2017-2019 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
 *
 * http://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.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.util.StringUtils;
import io.micronaut.core.value.OptionalValues;
import io.micronaut.inject.annotation.AbstractAnnotationMetadataBuilder;
import io.micronaut.inject.processing.JavaModelUtils;
import io.micronaut.inject.visitor.VisitorContext;

import javax.annotation.Nullable;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.AbstractAnnotationValueVisitor8;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Array;
import java.util.*;
import java.util.stream.Stream;

/**
 * A {@link io.micronaut.core.annotation.AnnotationMetadata} for builder for Java to be used at compile time.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
public class JavaAnnotationMetadataBuilder extends AbstractAnnotationMetadataBuilder {

    private static final Map> ANNOTATION_DEFAULTS = new HashMap<>();

    private final Elements elementUtils;
    private final Messager messager;
    private final AnnotationUtils annotationUtils;
    private final Types types;
    private final ModelUtils modelUtils;
    private final Filer filer;

    /**
     * Default constructor.
     *
     * @param elements The elementUtils
     * @param messager The messager
     * @param annotationUtils The annotation utils
     * @param types The types
     * @param modelUtils The model utils
     * @param filer The filer
     */
    public JavaAnnotationMetadataBuilder(
            Elements elements,
            Messager messager,
            AnnotationUtils annotationUtils,
            Types types,
            ModelUtils modelUtils,
            Filer filer) {
        this.elementUtils = elements;
        this.messager = messager;
        this.annotationUtils = annotationUtils;
        this.types = types;
        this.modelUtils = modelUtils;
        this.filer = filer;
    }

    @Override
    protected String getAnnotationMemberName(Element member) {
        return member.getSimpleName().toString();
    }

    @Nullable
    @Override
    protected String getRepeatableName(AnnotationMirror annotationMirror) {
        final Element typeElement = annotationMirror.getAnnotationType().asElement();
        return getRepeatableNameForType(typeElement);
    }

    @Nullable
    @Override
    protected String getRepeatableNameForType(Element annotationType) {
        List mirrors = annotationType.getAnnotationMirrors();
        for (AnnotationMirror mirror : mirrors) {
            String name = mirror.getAnnotationType().toString();
            if (Repeatable.class.getName().equals(name)) {
                Map elementValues = mirror.getElementValues();
                for (ExecutableElement executableElement : elementValues.keySet()) {
                    if (executableElement.getSimpleName().toString().equals("value")) {
                        javax.lang.model.element.AnnotationValue av = elementValues.get(executableElement);
                        Object value = av.getValue();
                        if (value instanceof DeclaredType) {
                            Element element = ((DeclaredType) value).asElement();
                            return JavaModelUtils.getClassName((TypeElement) element);
                        }
                    }
                }
            }
        }
        return null;
    }

    @Override
    protected Optional getAnnotationMirror(String annotationName) {
        return Optional.ofNullable(elementUtils.getTypeElement(annotationName));
    }

    @Override
    protected VisitorContext createVisitorContext() {
        return annotationUtils.newVisitorContext();
    }

    @Override
    protected Element getTypeForAnnotation(AnnotationMirror annotationMirror) {
        return annotationMirror.getAnnotationType().asElement();
    }

    @Override
    protected List getAnnotationsForType(Element element) {
        List annotationMirrors = new ArrayList<>(element.getAnnotationMirrors());
        annotationMirrors.removeIf(mirror -> getAnnotationTypeName(mirror).equals(AnnotationUtil.KOTLIN_METADATA));
        return annotationMirrors;
    }

    @Override
    protected List buildHierarchy(Element element, boolean inheritTypeAnnotations) {
        if (element instanceof TypeElement) {
            List hierarchy = new ArrayList<>();
            hierarchy.add(element);
            populateTypeHierarchy(element, hierarchy);
            Collections.reverse(hierarchy);
            return hierarchy;
        } else if (element instanceof ExecutableElement) {
            // we have a method
            // for methods we merge the data from any overridden interface or abstract methods
            // with type level data
            ExecutableElement executableElement = (ExecutableElement) element;
            // the starting hierarchy is the type and super types of this method
            List hierarchy;
            if (inheritTypeAnnotations) {
                hierarchy = buildHierarchy(executableElement.getEnclosingElement(), false);
            } else {
                hierarchy = new ArrayList<>();
            }
            if (hasAnnotation(executableElement, Override.class)) {
                hierarchy.addAll(findOverriddenMethods(executableElement));
            }
            hierarchy.add(element);
            return hierarchy;
        } else if (element instanceof VariableElement) {
            List hierarchy = new ArrayList<>();
            VariableElement variable = (VariableElement) element;
            Element enclosingElement = variable.getEnclosingElement();
            if (enclosingElement instanceof ExecutableElement) {
                ExecutableElement executableElement = (ExecutableElement) enclosingElement;
                if (hasAnnotation(executableElement, Override.class)) {
                    int variableIdx = executableElement.getParameters().indexOf(variable);
                    for (ExecutableElement overridden: findOverriddenMethods(executableElement)) {
                        hierarchy.add(overridden.getParameters().get(variableIdx));
                    }
                }
            }
            hierarchy.add(variable);
            return hierarchy;
        } else {
            ArrayList single = new ArrayList<>();
            single.add(element);
            return single;
        }
    }

    @Override
    protected Map readAnnotationRawValues(AnnotationMirror annotationMirror) {
        return annotationMirror.getElementValues();
    }

    @Override
    protected OptionalValues getAnnotationValues(Element member, Class annotationType) {
        List annotationMirrors = member.getAnnotationMirrors();
        String annotationName = annotationType.getName();
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            if (annotationMirror.getAnnotationType().toString().endsWith(annotationName)) {
                Map values = readAnnotationRawValues(annotationMirror);
                Map converted = new LinkedHashMap<>();
                for (Map.Entry entry : values.entrySet()) {
                    Element key = entry.getKey();
                    Object value = entry.getValue();
                    readAnnotationRawValues(key.getSimpleName().toString(), value, converted);
                }
                return OptionalValues.of(Object.class, converted);
            }
        }
        return OptionalValues.empty();
    }

    @Override
    protected void readAnnotationRawValues(String memberName, Object annotationValue, Map annotationValues) {
        if (memberName != null && annotationValue instanceof javax.lang.model.element.AnnotationValue) {
            if (!annotationValues.containsKey(memberName)) {
                ((javax.lang.model.element.AnnotationValue) annotationValue).accept(new MetadataAnnotationValueVisitor(annotationValues, memberName), this);

            }
        }
    }

    @Override
    protected Object readAnnotationValue(String memberName, Object annotationValue) {
        if (memberName != null && annotationValue instanceof javax.lang.model.element.AnnotationValue) {
            HashMap result = new HashMap<>(1);
            ((javax.lang.model.element.AnnotationValue) annotationValue).accept(new MetadataAnnotationValueVisitor(result, memberName), this);
            return result.get(memberName);
        }
        return null;
    }

    @Override
    protected Map readAnnotationDefaultValues(AnnotationMirror annotationMirror) {

        final String annotationTypeName = getAnnotationTypeName(annotationMirror);
        Element element = annotationMirror.getAnnotationType().asElement();
        return readAnnotationDefaultValues(annotationTypeName, element);
    }

    @Override
    protected Map readAnnotationDefaultValues(String annotationTypeName, Element element) {
        Map> defaults = JavaAnnotationMetadataBuilder.ANNOTATION_DEFAULTS;
        if (element instanceof TypeElement) {
            TypeElement annotationElement = (TypeElement) element;
            String annotationName = annotationElement.getQualifiedName().toString();
            if (!defaults.containsKey(annotationName)) {

                Map defaultValues = new HashMap<>();
                final List allMembers = elementUtils.getAllMembers(annotationElement);

                allMembers
                        .stream()
                        .filter(member -> member.getEnclosingElement().equals(annotationElement))
                        .filter(ExecutableElement.class::isInstance)
                        .map(ExecutableElement.class::cast)
                        .filter(this::isValidDefaultValue)
                        .forEach(executableElement -> {
                                    final AnnotationValue defaultValue = executableElement.getDefaultValue();
                                    defaultValues.put(executableElement, defaultValue);
                                }
                        );

                defaults.put(annotationName, defaultValues);
            }
        }
        return ANNOTATION_DEFAULTS.get(annotationTypeName);
    }

    private boolean isValidDefaultValue(ExecutableElement executableElement) {
        AnnotationValue defaultValue = executableElement.getDefaultValue();
        if (defaultValue != null) {
            Object v = defaultValue.getValue();
            if (v != null) {
                if (v instanceof String) {
                    return StringUtils.isNotEmpty((CharSequence) v);
                } else {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    protected String getAnnotationTypeName(AnnotationMirror annotationMirror) {
        return JavaModelUtils.getClassName((TypeElement) annotationMirror.getAnnotationType().asElement());
    }

    private void populateTypeHierarchy(Element element, List hierarchy) {
        while (JavaModelUtils.resolveKind(element, ElementKind.CLASS).isPresent()) {

            TypeElement typeElement = (TypeElement) element;
            List interfaces = typeElement.getInterfaces();
            for (TypeMirror anInterface : interfaces) {
                if (anInterface instanceof DeclaredType) {
                    Element interfaceElement = ((DeclaredType) anInterface).asElement();
                    hierarchy.add(interfaceElement);
                    populateTypeHierarchy(interfaceElement, hierarchy);
                }
            }
            TypeMirror superMirror = typeElement.getSuperclass();
            if (superMirror instanceof DeclaredType) {
                DeclaredType type = (DeclaredType) superMirror;
                if (type.toString().equals(Object.class.getName())) {
                    break;
                } else {
                    element = type.asElement();
                    hierarchy.add(element);
                }
            } else {
                break;
            }
        }
    }

    private List findOverriddenMethods(ExecutableElement executableElement) {
        List overridden = new ArrayList<>();
        Element enclosingElement = executableElement.getEnclosingElement();
        if (enclosingElement instanceof TypeElement) {
            TypeElement supertype = (TypeElement) enclosingElement;
            while (supertype != null && !supertype.toString().equals(Object.class.getName())) {
                Optional result = findOverridden(executableElement, supertype);
                if (result.isPresent()) {
                    ExecutableElement overriddenMethod = result.get();
                    overridden.add(overriddenMethod);
                    if (!hasAnnotation(overriddenMethod, Override.class)) {
                        findOverriddenInterfaceMethod(executableElement, overridden, supertype);
                        break;
                    }
                } else {
                    findOverriddenInterfaceMethod(executableElement, overridden, supertype);

                }
                TypeMirror superclass = supertype.getSuperclass();
                if (superclass instanceof DeclaredType) {
                    supertype = (TypeElement) ((DeclaredType) superclass).asElement();
                } else {
                    break;
                }
            }
        }
        return overridden;
    }

    private void findOverriddenInterfaceMethod(ExecutableElement executableElement, List overridden, TypeElement supertype) {
        Optional result;
        List interfaces = supertype.getInterfaces();

        for (TypeMirror anInterface : interfaces) {
            if (anInterface instanceof DeclaredType) {
                DeclaredType iElement = (DeclaredType) anInterface;
                TypeElement interfaceElement = (TypeElement) iElement.asElement();
                result = findOverridden(executableElement, interfaceElement);
                if (result.isPresent()) {
                    overridden.add(result.get());
                } else {
                    findOverriddenInterfaceMethod(executableElement, overridden, interfaceElement);
                }
            }
        }
    }

    private Optional findOverridden(ExecutableElement executableElement, TypeElement supertype) {
        Stream elements = supertype.getEnclosedElements().stream();
        return elements.filter(el -> el.getKind() == ElementKind.METHOD && el.getEnclosingElement().equals(supertype))
                .map(el -> (ExecutableElement) el)
                .filter(method -> elementUtils.overrides(executableElement, method, (TypeElement) method.getEnclosingElement()))
                .findFirst();
    }

    /**
     * Checks if a method has an annotation.
     *
     * @param method The method
     * @param ann    The annotation to look for
     * @return Whether if the method has the annotation
     */
    public static boolean hasAnnotation(ExecutableElement method, Class ann) {
        List annotationMirrors = method.getAnnotationMirrors();
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            if (annotationMirror.getAnnotationType().toString().equals(ann.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Meta annotation value visitor class.
     */
    private class MetadataAnnotationValueVisitor extends AbstractAnnotationValueVisitor8 {
        private final Map annotationValues;
        private final String memberName;

        /**
         * @param annotationValues A map of annotation values
         * @param member           A member
         */
        MetadataAnnotationValueVisitor(Map annotationValues, String member) {
            this.annotationValues = annotationValues;
            this.memberName = member;
        }

        @Override
        public Object visitBoolean(boolean b, Object o) {
            annotationValues.put(memberName, b);
            return null;
        }

        @Override
        public Object visitByte(byte b, Object o) {
            annotationValues.put(memberName, b);
            return null;
        }

        @Override
        public Object visitChar(char c, Object o) {
            annotationValues.put(memberName, c);
            return null;
        }

        @Override
        public Object visitDouble(double d, Object o) {
            annotationValues.put(memberName, d);
            return null;
        }

        @Override
        public Object visitFloat(float f, Object o) {
            annotationValues.put(memberName, f);
            return null;
        }

        @Override
        public Object visitInt(int i, Object o) {
            annotationValues.put(memberName, i);
            return null;
        }

        @Override
        public Object visitLong(long i, Object o) {
            annotationValues.put(memberName, i);
            return null;
        }

        @Override
        public Object visitShort(short s, Object o) {
            annotationValues.put(memberName, s);
            return null;
        }

        @Override
        public Object visitString(String s, Object o) {
            annotationValues.put(memberName, s);
            return null;
        }

        @Override
        public Object visitType(TypeMirror t, Object o) {
            if (t instanceof DeclaredType) {
                Element typeElement = ((DeclaredType) t).asElement();
                if (typeElement instanceof TypeElement) {
                    String className = JavaModelUtils.getClassName((TypeElement) typeElement);
                    annotationValues.put(memberName, new AnnotationClassValue<>(className));
                }
            }
            return null;
        }

        @Override
        public Object visitEnumConstant(VariableElement c, Object o) {
            annotationValues.put(memberName, c.toString());
            return null;
        }

        @Override
        public Object visitAnnotation(AnnotationMirror a, Object o) {
            if (a instanceof javax.lang.model.element.AnnotationValue) {
                io.micronaut.core.annotation.AnnotationValue value = readNestedAnnotationValue(a);
                annotationValues.put(memberName, value);
            }
            return null;
        }

        @Override
        public Object visitArray(List vals, Object o) {
            ArrayValueVisitor arrayValueVisitor = new ArrayValueVisitor();
            for (javax.lang.model.element.AnnotationValue val : vals) {
                val.accept(arrayValueVisitor, o);
            }
            annotationValues.put(memberName, arrayValueVisitor.getValues());
            return null;
        }

        /**
         * Array value visitor class.
         */
        @SuppressWarnings("unchecked")
        private class ArrayValueVisitor extends AbstractAnnotationValueVisitor8 {

            private List values = new ArrayList();
            private Class arrayType;

            Object[] getValues() {
                if (arrayType != null) {
                    return values.toArray((Object[]) Array.newInstance(arrayType, values.size()));
                } else {
                    return values.toArray(new Object[0]);
                }
            }

            @Override
            public Object visitBoolean(boolean b, Object o) {
                arrayType = Boolean.class;
                values.add(b);
                return null;
            }

            @Override
            public Object visitByte(byte b, Object o) {
                arrayType = Byte.class;
                values.add(b);
                return null;
            }

            @Override
            public Object visitChar(char c, Object o) {
                arrayType = Character.class;
                values.add(c);
                return null;
            }

            @Override
            public Object visitDouble(double d, Object o) {
                arrayType = Double.class;
                values.add(d);
                return null;
            }

            @Override
            public Object visitFloat(float f, Object o) {
                arrayType = Float.class;
                values.add(f);
                return null;
            }

            @Override
            public Object visitInt(int i, Object o) {
                arrayType = Integer.class;
                values.add(i);
                return null;
            }

            @Override
            public Object visitLong(long i, Object o) {
                arrayType = Long.class;
                values.add(i);
                return null;
            }

            @Override
            public Object visitShort(short s, Object o) {
                arrayType = Short.class;
                values.add(s);
                return null;
            }

            @Override
            public Object visitString(String s, Object o) {
                arrayType = String.class;
                values.add(s);
                return null;
            }

            @Override
            public Object visitType(TypeMirror t, Object o) {
                arrayType = AnnotationClassValue.class;
                if (t instanceof DeclaredType) {
                    Element typeElement = ((DeclaredType) t).asElement();
                    if (typeElement instanceof TypeElement) {
                        final String className = JavaModelUtils.getClassName((TypeElement) typeElement);
                        values.add(new AnnotationClassValue<>(className));
                    }
                }
                return null;
            }

            @Override
            public Object visitEnumConstant(VariableElement c, Object o) {
                arrayType = String.class;
                values.add(c.getSimpleName().toString());
                return null;
            }

            @Override
            public Object visitAnnotation(AnnotationMirror a, Object o) {
                arrayType = io.micronaut.core.annotation.AnnotationValue.class;
                io.micronaut.core.annotation.AnnotationValue annotationValue = readNestedAnnotationValue(a);
                values.add(annotationValue);
                return null;
            }

            @Override
            public Object visitArray(List vals, Object o) {
                return null;
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy