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

io.micronaut.inject.writer.AbstractBeanDefinitionBuilder Maven / Gradle / Ivy

/*
 * Copyright 2017-2021 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.inject.writer;

import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Executable;
import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Value;
import io.micronaut.core.annotation.AnnotationClassValue;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.AnnotationValueBuilder;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.annotation.AnnotationMetadataHierarchy;
import io.micronaut.inject.annotation.MutableAnnotationMetadata;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.inject.ast.ConstructorElement;
import io.micronaut.inject.ast.Element;
import io.micronaut.inject.ast.ElementFactory;
import io.micronaut.inject.ast.ElementModifier;
import io.micronaut.inject.ast.ElementQuery;
import io.micronaut.inject.ast.FieldElement;
import io.micronaut.inject.ast.MemberElement;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.ParameterElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.ast.beans.BeanConstructorElement;
import io.micronaut.inject.ast.beans.BeanElementBuilder;
import io.micronaut.inject.ast.beans.BeanFieldElement;
import io.micronaut.inject.ast.beans.BeanMethodElement;
import io.micronaut.inject.ast.beans.BeanParameterElement;
import io.micronaut.inject.configuration.ConfigurationMetadataBuilder;
import io.micronaut.inject.visitor.VisitorContext;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * Abstract implementation of the {@link BeanElementBuilder} interface that should be implemented by downstream language specific implementations.
 *
 * @author graemerocher
 * @since 3.0.0
 */
@Internal
public abstract class AbstractBeanDefinitionBuilder implements BeanElementBuilder {
    private static final Map BEAN_COUNTER = new HashMap<>(15);
    private static final Predicate> PUBLIC_FILTER = (
            elementModifiers -> elementModifiers.contains(ElementModifier.PUBLIC));
    private static final Predicate> NON_PUBLIC_FILTER = (
            elementModifiers -> !elementModifiers.contains(ElementModifier.PUBLIC));
    private static final Comparator SORTER = (o1, o2) -> {
        final ClassElement d1 = o1.getDeclaringType();
        final ClassElement d2 = o2.getDeclaringType();
        final String o1Type = d1.getName();
        final String o2Type = d2.getName();
        if (o1Type.equals(o2Type)) {
            return 0;
        } else {
            if (d1.isAssignable(d2)) {
                return 1;
            } else {
                return -1;
            }
        }
    };
    protected final ConfigurationMetadataBuilder metadataBuilder;
    protected final VisitorContext visitorContext;
    private final Element originatingElement;
    private final ClassElement originatingType;
    private final ClassElement beanType;
    private final int identifier;
    private final MutableAnnotationMetadata annotationMetadata;
    private final List executableMethods = new ArrayList<>(5);
    private final List interceptedMethods = new ArrayList<>(5);
    private final List childBeans = new ArrayList<>(5);
    private final List injectedMethods = new ArrayList<>(5);
    private final List preDestroyMethods = new ArrayList<>(5);
    private final List postConstructMethods = new ArrayList<>(5);
    private final List injectedFields = new ArrayList<>(5);
    private BeanConstructorElement constructorElement;
    private Map> typeArguments;
    private ClassElement[] exposedTypes;
    private boolean intercepted;

    /**
     * Default constructor.
     * @param originatingElement The originating element
     * @param beanType The bean type
     * @param metadataBuilder the metadata builder
     * @param visitorContext the visitor context
     */
    protected AbstractBeanDefinitionBuilder(
            Element originatingElement,
            ClassElement beanType,
            ConfigurationMetadataBuilder metadataBuilder,
            VisitorContext visitorContext) {
        this.originatingElement = originatingElement;
        if (originatingElement instanceof MethodElement) {
            this.originatingType = ((MethodElement) originatingElement).getDeclaringType();
        } else if (originatingElement instanceof ClassElement) {
            this.originatingType = (ClassElement) originatingElement;
        } else {
            throw new IllegalArgumentException("Invalid originating element: " + originatingElement);
        }
        this.beanType = beanType;
        this.metadataBuilder = metadataBuilder;
        this.visitorContext = visitorContext;
        this.identifier = BEAN_COUNTER.computeIfAbsent(beanType.getName(), (s) -> new AtomicInteger(0))
                                      .getAndIncrement();
        final AnnotationMetadata annotationMetadata = beanType.getAnnotationMetadata();
        if (annotationMetadata instanceof MutableAnnotationMetadata) {
            this.annotationMetadata = ((MutableAnnotationMetadata) annotationMetadata).clone();
        } else {
            this.annotationMetadata = new MutableAnnotationMetadata();
        }
        this.annotationMetadata.addDeclaredAnnotation(Bean.class.getName(), Collections.emptyMap());
        this.constructorElement = initConstructor(beanType);
    }

    @Override
    public BeanElementBuilder intercept(AnnotationValue... annotationValue) {
        for (AnnotationValue value : annotationValue) {
            annotate(value);
        }
        this.intercepted = true;
        return this;
    }

    @Internal
    public static void writeBeanDefinitionBuilders(ClassWriterOutputVisitor classWriterOutputVisitor,
                                                   List beanDefinitionBuilders)
            throws IOException {
        for (AbstractBeanDefinitionBuilder beanDefinitionBuilder : beanDefinitionBuilders) {
            writeBeanDefinition(classWriterOutputVisitor, beanDefinitionBuilder);
            final List childBeans = beanDefinitionBuilder.getChildBeans();
            for (AbstractBeanDefinitionBuilder childBean : childBeans) {
                writeBeanDefinition(classWriterOutputVisitor, childBean);
            }
        }
    }

    private static void writeBeanDefinition(ClassWriterOutputVisitor classWriterOutputVisitor, AbstractBeanDefinitionBuilder beanDefinitionBuilder)
            throws IOException {
        final ClassOutputWriter beanDefinitionWriter = beanDefinitionBuilder.build();
        if (beanDefinitionWriter != null) {
            beanDefinitionWriter.accept(classWriterOutputVisitor);
        }
    }

    private InternalBeanConstructorElement initConstructor(ClassElement beanType) {
        return beanType.getPrimaryConstructor().map(m -> new InternalBeanConstructorElement(
                m,
                !m.isPublic(),
                initBeanParameters(m.getParameters())
        )).orElse(null);
    }

    /**
     * Is the bean to be built intercepted?
     * @return True if it is
     */
    protected boolean isIntercepted() {
        return this.intercepted || !this.interceptedMethods.isEmpty();
    }

    @Override
    public BeanElementBuilder inject() {
        processInjectedMethods();
        processInjectedFields();
        return this;
    }

    /**
     * Any child bean definitions.
     * @return The child beans
     */
    public List getChildBeans() {
        return childBeans;
    }

    private void processInjectedFields() {
        final ElementQuery baseQuery = ElementQuery.ALL_FIELDS
                .onlyInstance()
                .onlyInjected();
        Set accessibleFields = new HashSet<>();
        this.beanType.getEnclosedElements(baseQuery.modifiers(PUBLIC_FILTER))
                .forEach(fieldElement -> {
                    accessibleFields.add(fieldElement);
                    new InternalBeanElementField(fieldElement, false).inject();
                });
        this.beanType.getEnclosedElements(baseQuery.modifiers(NON_PUBLIC_FILTER))
                .forEach(fieldElement -> {
                    if (!accessibleFields.contains(fieldElement)) {
                        new InternalBeanElementField(fieldElement, true).inject();
                    }
                });
    }

    private void processInjectedMethods() {
        final ElementQuery baseQuery = ElementQuery.ALL_METHODS
                .onlyInstance()
                .onlyConcrete()
                .onlyInjected();
        Set accessibleMethods = new HashSet<>();
        this.beanType.getEnclosedElements(baseQuery.modifiers(PUBLIC_FILTER))
                .forEach(methodElement -> {
                     accessibleMethods.add(methodElement);
                    handleMethod(methodElement, false);
                });
        this.beanType.getEnclosedElements(baseQuery.modifiers(NON_PUBLIC_FILTER))
                .forEach(methodElement -> {
                    if (!accessibleMethods.contains(methodElement)) {
                        handleMethod(methodElement, true);
                    }
                });
    }

    private void handleMethod(MethodElement methodElement, boolean requiresReflection) {
        boolean lifecycleMethod = false;
        if (methodElement.getAnnotationMetadata().hasDeclaredAnnotation(AnnotationUtil.PRE_DESTROY)) {
            new InternalBeanElementMethod(methodElement, requiresReflection)
                    .preDestroy();
            lifecycleMethod = true;
        }
        if (methodElement.getAnnotationMetadata().hasDeclaredAnnotation(AnnotationUtil.POST_CONSTRUCT)) {
            new InternalBeanElementMethod(methodElement, requiresReflection)
                    .postConstruct();
            lifecycleMethod = true;
        }
        if (!lifecycleMethod) {
            new InternalBeanElementMethod(methodElement, requiresReflection)
                    .inject();
        }
    }

    @NonNull
    @Override
    public Element getOriginatingElement() {
        return originatingElement;
    }

    @NonNull
    @Override
    public ClassElement getBeanType() {
        return beanType;
    }

    /**
     * Initialize the bean parameters.
     * @param constructorParameters The parameters to use.
     * @return The initialized parameters
     */
    protected final BeanParameterElement[] initBeanParameters(@NonNull ParameterElement[] constructorParameters) {
        if (ArrayUtils.isNotEmpty(constructorParameters)) {
            return Arrays.stream(constructorParameters)
                    .map(InternalBeanParameter::new)
                    .toArray(BeanParameterElement[]::new);
        } else {
            return new BeanParameterElement[0];
        }
    }

    @NonNull
    @Override
    public AnnotationMetadata getAnnotationMetadata() {
        return this.annotationMetadata;
    }

    @NonNull
    @Override
    public BeanElementBuilder createWith(@NonNull MethodElement element) {
        // TODO: handle factories, static methods etc.
        //noinspection ConstantConditions
        if (element != null) {
            constructorElement = new InternalBeanConstructorElement(
                    element,
                    !element.isPublic(),
                    initBeanParameters(element.getParameters())
            );
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder typed(ClassElement... types) {
        if (ArrayUtils.isNotEmpty(types)) {
            this.exposedTypes = types;
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder typeArguments(@NonNull ClassElement... types) {
        final Map typeArguments = this.beanType.getTypeArguments();
        Map resolvedTypes = resolveTypeArguments(typeArguments, types);
        if (resolvedTypes != null) {
            if (this.typeArguments == null) {
                this.typeArguments = new LinkedHashMap<>();
            }
            this.typeArguments.put(beanType.getName(), typeArguments);
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder typeArgumentsForType(ClassElement type, @NonNull ClassElement... types) {
        if (type != null) {
            final Map typeArguments = type.getTypeArguments();
            Map resolvedTypes = resolveTypeArguments(typeArguments, types);
            if (resolvedTypes != null) {
                if (this.typeArguments == null) {
                    this.typeArguments = new LinkedHashMap<>();
                }
                this.typeArguments.put(type.getName(), resolvedTypes);
            }
        }
        return this;
    }

    @Nullable
    private Map resolveTypeArguments(Map typeArguments, ClassElement... types) {
        Map resolvedTypes = null;
        if (typeArguments.size() == types.length) {
            resolvedTypes = new LinkedHashMap<>(typeArguments.size());
            final Iterator i = typeArguments.keySet().iterator();
            for (ClassElement type : types) {
                final String variable = i.next();
                resolvedTypes.put(variable, type);
            }
        }
        return resolvedTypes;
    }

    @Override
    public BeanElementBuilder withConstructor(Consumer constructorElement) {
        if (constructorElement != null && this.constructorElement != null) {
            constructorElement.accept(this.constructorElement);
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder withMethods(
            @NonNull ElementQuery methods,
            @NonNull Consumer beanMethods) {
        //noinspection ConstantConditions
        if (methods != null && beanMethods != null) {
            final ElementQuery baseQuery = methods.onlyInstance();
            this.beanType.getEnclosedElements(baseQuery.modifiers(m -> m.contains(ElementModifier.PUBLIC)))
                    .forEach(methodElement ->
                        beanMethods.accept(new InternalBeanElementMethod(methodElement, false))
                    );
            this.beanType.getEnclosedElements(baseQuery.modifiers(m -> !m.contains(ElementModifier.PUBLIC)))
                    .forEach(methodElement ->
                         beanMethods.accept(new InternalBeanElementMethod(methodElement, true))
                    );
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder withFields(@NonNull ElementQuery fields, @NonNull Consumer beanFields) {
        //noinspection ConstantConditions
        if (fields != null && beanFields != null) {
            this.beanType.getEnclosedElements(fields.onlyInstance().onlyAccessible(originatingType))
                    .forEach((fieldElement) ->
                            beanFields.accept(new InternalBeanElementField(fieldElement, false))
                    );
        }
        return this;
    }

    @NonNull
    @Override
    public BeanElementBuilder withParameters(Consumer parameters) {
        if (parameters != null && this.constructorElement != null) {
            parameters.accept(getParameters());
        }
        return this;
    }

    /**
     * @return The bean creation parameters.
     */
    @NonNull
    protected BeanParameterElement[] getParameters() {
        return constructorElement.getParameters();
    }

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

    @Override
    public boolean isProtected() {
        return beanType.isProtected();
    }

    @Override
    public boolean isPublic() {
        return beanType.isPublic();
    }

    @NonNull
    @Override
    public Object getNativeType() {
        return beanType;
    }

    @NonNull
    @Override
    public  BeanElementBuilder annotate(@NonNull String annotationType, @NonNull Consumer> consumer) {
        annotate(this.annotationMetadata, annotationType, consumer);
        return this;
    }

    @Override
    public  Element annotate(AnnotationValue annotationValue) {
        annotate(this.annotationMetadata, annotationValue);
        return this;
    }

    @Override
    public BeanElementBuilder removeAnnotation(@NonNull String annotationType) {
        removeAnnotation(this.annotationMetadata, annotationType);
        return this;
    }

    @Override
    public  BeanElementBuilder removeAnnotationIf(@NonNull Predicate> predicate) {
        removeAnnotationIf(this.annotationMetadata, predicate);
        return this;
    }

    @Override
    public BeanElementBuilder removeStereotype(@NonNull String annotationType) {
        removeStereotype(this.annotationMetadata, annotationType);
        return this;
    }

    private BeanElementBuilder addChildBean(@NonNull MethodElement producerMethod, Consumer childBeanBuilder) {
        final AbstractBeanDefinitionBuilder childBuilder = createChildBean(producerMethod);
        this.childBeans.add(childBuilder);
        if (childBeanBuilder != null) {
            childBeanBuilder.accept(childBuilder);
        }
        return this;
    }

    private BeanElementBuilder addChildBean(@NonNull FieldElement producerMethod, Consumer childBeanBuilder) {
        final AbstractBeanDefinitionBuilder childBuilder = createChildBean(producerMethod);
        this.childBeans.add(childBuilder);
        if (childBeanBuilder != null) {
            childBeanBuilder.accept(childBuilder);
        }
        return this;
    }

    @Override
    public  BeanElementBuilder produceBeans(ElementQuery methodsOrFields,
                                                                     Consumer childBeanBuilder) {
        methodsOrFields = methodsOrFields
                .onlyConcrete()
                .modifiers(modifiers -> modifiers.contains(ElementModifier.PUBLIC));
        final List enclosedElements = this.beanType.getEnclosedElements(methodsOrFields);
        for (E enclosedElement : enclosedElements) {
            if (enclosedElement instanceof FieldElement) {
                FieldElement fe = (FieldElement) enclosedElement;
                final ClassElement type = fe.getGenericField().getType();
                if (type.isPublic() && !type.isPrimitive()) {
                    addChildBean(fe, childBeanBuilder);
                }
            }

            if (enclosedElement instanceof MethodElement && !(enclosedElement instanceof ConstructorElement)) {
                MethodElement me = (MethodElement) enclosedElement;
                final ClassElement type = me.getGenericReturnType().getType();
                if (type.isPublic() && !type.isPrimitive()) {
                    addChildBean(me, childBeanBuilder);
                }
            }
        }
        return this;
    }

    /**
     * Creates a child bean for the given producer field.
     * @param producerField The producer field
     * @return The child bean builder
     */
    protected abstract @NonNull AbstractBeanDefinitionBuilder createChildBean(FieldElement producerField);

    /**
     * Visit the intercepted methods of this type.
     * @param consumer A consumer to handle the method
     */
    protected void visitInterceptedMethods(BiConsumer consumer) {
        if (consumer != null) {

            ClassElement beanClass = getBeanType();
            if (CollectionUtils.isNotEmpty(interceptedMethods)) {
                for (BeanMethodElement interceptedMethod : interceptedMethods) {
                    handleMethod(beanClass, interceptedMethod, consumer);
                }
            }

            if (this.intercepted) {
                beanClass.getEnclosedElements(
                        ElementQuery.ALL_METHODS
                                .onlyInstance()
                                .modifiers(mods -> !mods.contains(ElementModifier.FINAL) && mods.contains(ElementModifier.PUBLIC))
                ).forEach(method -> {
                    InternalBeanElementMethod ibem = new InternalBeanElementMethod(
                            method,
                            true
                    );
                    if (!interceptedMethods.contains(ibem)) {
                        handleMethod(beanClass, ibem, consumer);
                    }
                });
            }
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    private void handleMethod(ClassElement beanClass, MethodElement method, BiConsumer consumer) {
        ElementFactory elementFactory = visitorContext.getElementFactory();
        AnnotationMetadataHierarchy fusedMetadata = new AnnotationMetadataHierarchy(getAnnotationMetadata(), method.getAnnotationMetadata());
        MethodElement finalMethod = elementFactory.newMethodElement(
                beanClass,
                method.getNativeType(),
                fusedMetadata
        );
        consumer.accept(beanClass, finalMethod);
    }

    /**
     * Creates a child bean for the given producer method.
     * @param producerMethod The producer method
     * @return The child bean builder
     */
    protected abstract @NonNull AbstractBeanDefinitionBuilder createChildBean(MethodElement producerMethod);

    /**
     * Build the bean definition writer.
     * @return The writer, possibly null if it wasn't possible to build it
     */
    @SuppressWarnings({"ConstantConditions", "java:S2583"})
    @Nullable
    public BeanClassWriter build() {
        BeanClassWriter beanWriter = buildBeanClassWriter();
        if (beanWriter == null) {
            return null;
        } else {
            BeanDefinitionVisitor parentVisitor = beanWriter.getBeanDefinitionVisitor();
            AnnotationMetadata thisAnnotationMetadata = getAnnotationMetadata();
            if (isIntercepted() && parentVisitor instanceof BeanDefinitionWriter) {
                return new BeanClassWriter() {
                    @Override
                    public BeanDefinitionVisitor getBeanDefinitionVisitor() {
                        return parentVisitor;
                    }

                    @Override
                    public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {
                        BeanDefinitionWriter beanDefinitionWriter = (BeanDefinitionWriter) parentVisitor;
                        BeanDefinitionVisitor aopProxyWriter = AbstractBeanDefinitionBuilder.this.createAopWriter(beanDefinitionWriter, thisAnnotationMetadata);


                        if (configureBeanVisitor(aopProxyWriter)) {
                            return;
                        }

                        configureInjectionPoints(aopProxyWriter);

                        visitInterceptedMethods(
                                createAroundMethodVisitor(aopProxyWriter)
                        );

                        finalizeAndWriteBean(classWriterOutputVisitor, aopProxyWriter);
                        beanWriter.accept(classWriterOutputVisitor);
                    }
                };
            } else {
                return beanWriter;
            }
        }
    }

    /**
     * Creates the around method visitor.
     * @param aopProxyWriter The AOP writer
     * @return The visitor
     */
    @NonNull
    protected abstract BiConsumer createAroundMethodVisitor(BeanDefinitionVisitor aopProxyWriter);

    /**
     * Creates the AOP writer.
     * @param beanDefinitionWriter The bean definition writer
     * @param annotationMetadata The annotation metadata
     * @return The AOP writer
     */
    @NonNull
    protected abstract BeanDefinitionVisitor createAopWriter(BeanDefinitionWriter beanDefinitionWriter, AnnotationMetadata annotationMetadata);

    @NonNull
    private BeanClassWriter buildBeanClassWriter() {
        final BeanDefinitionVisitor beanDefinitionWriter = createBeanDefinitionWriter();
        return new BeanClassWriter() {
            @Override
            public BeanDefinitionVisitor getBeanDefinitionVisitor() {
                return beanDefinitionWriter;
            }

            @Override
            public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {
                if (configureBeanVisitor(beanDefinitionWriter)) {
                    return;
                }

                configureInjectionPoints(beanDefinitionWriter);

                for (BeanMethodElement postConstructMethod : postConstructMethods) {
                    if (postConstructMethod.getDeclaringType().equals(beanType)) {
                        beanDefinitionWriter.visitPostConstructMethod(
                                beanType,
                                postConstructMethod,
                                postConstructMethod.isReflectionRequired(),
                                visitorContext
                        );
                    }
                }

                for (BeanMethodElement preDestroyMethod : preDestroyMethods) {
                    if (preDestroyMethod.getDeclaringType().equals(beanType)) {
                        beanDefinitionWriter.visitPreDestroyMethod(
                                beanType,
                                preDestroyMethod,
                                preDestroyMethod.isReflectionRequired(),
                                visitorContext
                        );
                    }
                }

                finalizeAndWriteBean(classWriterOutputVisitor, beanDefinitionWriter);
            }
        };
    }

    private void configureInjectionPoints(BeanDefinitionVisitor beanDefinitionWriter) {
        Map> sortedInjections = new LinkedHashMap<>();
        List allInjected = new ArrayList<>();
        allInjected.addAll(injectedFields);
        allInjected.addAll(injectedMethods);
        allInjected.sort(SORTER);
        for (MemberElement memberElement : allInjected) {
            final List list = sortedInjections
                    .computeIfAbsent(memberElement.getDeclaringType(),
                            classElement -> new ArrayList<>()
                    );
            list.add(memberElement);
        }
        for (List members : sortedInjections.values()) {
            members.sort((o1, o2) -> {
                if (o1 instanceof FieldElement && o2 instanceof MethodElement) {
                    return 1;
                } else if (o1 instanceof MethodElement && o1 instanceof FieldElement) {
                    return -1;
                }
                return 0;
            });
        }

        for (List list : sortedInjections.values()) {
            for (MemberElement memberElement : list) {
                if (memberElement instanceof FieldElement) {
                    InternalBeanElementField ibf = (InternalBeanElementField) memberElement;
                    ibf.with(element ->
                            visitField(beanDefinitionWriter, element, element)
                    );

                } else {
                    InternalBeanElementMethod ibm = (InternalBeanElementMethod) memberElement;
                    ibm.with(element ->
                            beanDefinitionWriter.visitMethodInjectionPoint(
                                    ibm.getDeclaringType(),
                                    ibm,
                                    ibm.isReflectionRequired(),
                                    visitorContext
                            )
                    );

                }
            }
        }


        for (BeanMethodElement executableMethod : executableMethods) {
            beanDefinitionWriter.visitExecutableMethod(
                    beanType,
                    executableMethod,
                    visitorContext
            );
            if (executableMethod.getAnnotationMetadata().isTrue(Executable.class, "processOnStartup")) {
                beanDefinitionWriter.setRequiresMethodProcessing(true);
            }
        }
    }

    /**
     * Finish the given bean and write it to the output.
     * @param classWriterOutputVisitor The output
     * @param beanDefinitionWriter The writer
     * @throws IOException If an error occurred
     */
    protected void finalizeAndWriteBean(
            ClassWriterOutputVisitor classWriterOutputVisitor,
            BeanDefinitionVisitor beanDefinitionWriter) throws IOException {
        beanDefinitionWriter.visitBeanDefinitionEnd();
        BeanDefinitionReferenceWriter beanDefinitionReferenceWriter =
                new BeanDefinitionReferenceWriter(beanDefinitionWriter);
        beanDefinitionReferenceWriter
                .setRequiresMethodProcessing(beanDefinitionWriter.requiresMethodProcessing());
        beanDefinitionReferenceWriter.accept(classWriterOutputVisitor);
        beanDefinitionWriter.accept(classWriterOutputVisitor);
    }

    /**
     * Configure the bean visitor for this builder.
     * @param beanDefinitionWriter The bean visitor
     * @return True if an error occurred
     */
    protected boolean configureBeanVisitor(BeanDefinitionVisitor beanDefinitionWriter) {
        if (exposedTypes != null) {
            final AnnotationClassValue[] annotationClassValues =
                    Arrays.stream(exposedTypes).map(ce -> new AnnotationClassValue<>(ce.getName())).toArray(AnnotationClassValue[]::new);
            annotate(Bean.class, builder -> builder.member("typed", annotationClassValues));
        }
        if (typeArguments != null) {
            beanDefinitionWriter.visitTypeArguments(AbstractBeanDefinitionBuilder.this.typeArguments);
        }

        Element producingElement = getProducingElement();
        if (producingElement instanceof ClassElement) {

            if (constructorElement == null) {
                constructorElement = initConstructor(beanType);
            }

            if (constructorElement == null) {
                visitorContext.fail("Cannot create associated bean with no accessible primary constructor. Consider supply the constructor with createWith(..)", originatingElement);
                return true;
            } else {
                beanDefinitionWriter.visitBeanDefinitionConstructor(
                    constructorElement,
                    !constructorElement.isPublic(),
                    visitorContext
                );
            }
        }
        return false;
    }

    /**
     * @return Creates the bean definition writer.
     */
    protected BeanDefinitionVisitor createBeanDefinitionWriter() {
        return new BeanDefinitionWriter(
                this,
                OriginatingElements.of(originatingElement),
                metadataBuilder,
                visitorContext,
                identifier
        );
    }

    private void visitField(BeanDefinitionVisitor beanDefinitionWriter,
                           BeanFieldElement injectedField,
                           InternalBeanElementField ibf) {
        if (injectedField.hasAnnotation(Value.class) || injectedField.hasAnnotation(Property.class)) {
            beanDefinitionWriter.visitFieldValue(
                    injectedField.getDeclaringType(),
                    injectedField,
                    ibf.isReflectionRequired(),
                    ibf.isDeclaredNullable()
            );
        } else {
            beanDefinitionWriter.visitFieldInjectionPoint(
                    injectedField.getDeclaringType(),
                    ibf,
                    ibf.isReflectionRequired()
            );
        }
    }

    /**
     * Add an annotation to the given metadata.
     * @param annotationMetadata The annotation metadata
     * @param annotationType the annotation type
     * @param consumer The builder
     * @param  The annotation generic type
     */
    protected abstract  void annotate(AnnotationMetadata annotationMetadata, String annotationType, Consumer> consumer);

    /**
     * Add an annotation to the given metadata.
     * @param annotationMetadata The annotation metadata
     * @param annotationValue The value
     * @param  The annotation generic type
     * @since 3.3.0
     */
    protected abstract  void annotate(@NonNull AnnotationMetadata annotationMetadata, @NonNull AnnotationValue annotationValue);

    /**
     * Remove a stereotype from the given metadata.
     * @param annotationMetadata The metadata
     * @param annotationType The stereotype
     */
    protected abstract void removeStereotype(AnnotationMetadata annotationMetadata, String annotationType);

    /**
     * Remove an annotation if it matches the given condition.
     * @param annotationMetadata The metadata
     * @param predicate The predicate
     * @param  The annotation type
     */
    protected abstract  void removeAnnotationIf(AnnotationMetadata annotationMetadata, Predicate> predicate);

    /**
     * Remove an annotation for the given name.
     * @param annotationMetadata The metadata
     * @param annotationType The type
     */
    protected abstract void removeAnnotation(AnnotationMetadata annotationMetadata, String annotationType);

    /**
     * Super class for all bean elements.
     * @param  The element type
     */
    private abstract class InternalBeanElement implements Element {
        private final E element;
        private final MutableAnnotationMetadata elementMetadata;
        private AnnotationMetadata currentMetadata;

        private InternalBeanElement(E element) {
            this.element = element;
            final AnnotationMetadata annotationMetadata = element.getAnnotationMetadata();
            if (annotationMetadata instanceof MutableAnnotationMetadata) {
                this.elementMetadata = ((MutableAnnotationMetadata) annotationMetadata).clone();
            } else {
                this.elementMetadata = new MutableAnnotationMetadata();
            }
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            InternalBeanElement that = (InternalBeanElement) o;
            return element.equals(that.element);
        }

        @Override
        public int hashCode() {
            return Objects.hash(element);
        }

        @NonNull
        @Override
        public AnnotationMetadata getAnnotationMetadata() {
            if (currentMetadata != null) {
                return currentMetadata;
            }
            return elementMetadata;
        }

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

        @Override
        public boolean isProtected() {
            return element.isProtected();
        }

        @Override
        public boolean isPublic() {
            return element.isPublic();
        }

        @NonNull
        @Override
        public Object getNativeType() {
            return element.getNativeType();
        }

        @NonNull
        @Override
        public  Element annotate(@NonNull String annotationType, @NonNull Consumer> consumer) {
            AbstractBeanDefinitionBuilder.this.annotate(elementMetadata, annotationType, consumer);
            return this;
        }

        @Override
        public  Element annotate(AnnotationValue annotationValue) {
            AbstractBeanDefinitionBuilder.this.annotate(elementMetadata, annotationValue);
            return this;
        }

        @Override
        public Element removeAnnotation(@NonNull String annotationType) {
            AbstractBeanDefinitionBuilder.this.removeAnnotation(elementMetadata, annotationType);
            return this;
        }

        @Override
        public  Element removeAnnotationIf(@NonNull Predicate> predicate) {
            AbstractBeanDefinitionBuilder.this.removeAnnotationIf(elementMetadata, predicate);
            return this;
        }

        @Override
        public Element removeStereotype(@NonNull String annotationType) {
            AbstractBeanDefinitionBuilder.this.removeStereotype(elementMetadata, annotationType);
            return this;
        }

        public > void with(Consumer consumer) {
            try {
                this.currentMetadata = elementMetadata.isEmpty() ? AnnotationMetadata.EMPTY_METADATA : elementMetadata;
                //noinspection unchecked
                consumer.accept((T) this);
            } finally {
                currentMetadata = null;
            }
        }
    }

    /**
     * Models a {@link BeanMethodElement}.
     */
    private final class InternalBeanElementMethod extends InternalBeanElement implements BeanMethodElement {

        private final MethodElement methodElement;
        private final boolean requiresReflection;
        private BeanParameterElement[] beanParameters;

        private InternalBeanElementMethod(MethodElement methodElement, boolean requiresReflection) {
            this(methodElement, requiresReflection, initBeanParameters(methodElement.getParameters()));
        }

        private InternalBeanElementMethod(MethodElement methodElement,
                                          boolean requiresReflection,
                                          BeanParameterElement[] beanParameters) {
            super(methodElement);
            this.methodElement = methodElement;
            this.requiresReflection = requiresReflection;
            this.beanParameters = beanParameters;
        }

        @Override
        public boolean isReflectionRequired() {
            return requiresReflection;
        }

        @Override
        public boolean isPackagePrivate() {
            return methodElement.isPackagePrivate();
        }

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

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

        @Override
        public boolean isPrivate() {
            return methodElement.isPrivate();
        }

        @Override
        public boolean isFinal() {
            return methodElement.isFinal();
        }

        @Override
        public boolean isSuspend() {
            return methodElement.isSuspend();
        }

        @Override
        public boolean isDefault() {
            return methodElement.isDefault();
        }

        @Override
        public boolean isProtected() {
            return methodElement.isProtected();
        }

        @Override
        public boolean isPublic() {
            return methodElement.isPublic();
        }

        @NonNull
        @Override
        public BeanMethodElement executable() {
            if (!AbstractBeanDefinitionBuilder.this.executableMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.executableMethods.add(this);
            }
            return BeanMethodElement.super.executable();
        }

        @Override
        public BeanMethodElement intercept(AnnotationValue... annotationValue) {
            if (!AbstractBeanDefinitionBuilder.this.interceptedMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.interceptedMethods.add(this);
            }
            return BeanMethodElement.super.intercept(annotationValue);
        }

        @Override
        public BeanMethodElement executable(boolean processOnStartup) {
            if (!AbstractBeanDefinitionBuilder.this.executableMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.executableMethods.add(this);
            }
            return BeanMethodElement.super.executable(processOnStartup);
        }

        @NonNull
        @Override
        public BeanMethodElement inject() {
            if (!AbstractBeanDefinitionBuilder.this.injectedMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.injectedMethods.add(this);
            }
            return BeanMethodElement.super.inject();
        }

        @NonNull
        @Override
        public BeanMethodElement preDestroy() {
            if (!AbstractBeanDefinitionBuilder.this.preDestroyMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.preDestroyMethods.add(this);
            }
            return BeanMethodElement.super.preDestroy();
        }

        @NonNull
        @Override
        public BeanMethodElement postConstruct() {
            if (!AbstractBeanDefinitionBuilder.this.postConstructMethods.contains(this)) {
                AbstractBeanDefinitionBuilder.this.postConstructMethods.add(this);
            }
            return BeanMethodElement.super.postConstruct();
        }

        @NonNull
        @Override
        public BeanParameterElement[] getParameters() {
            return this.beanParameters;
        }

        @NonNull
        @Override
        public ClassElement getReturnType() {
            return methodElement.getReturnType();
        }

        @NonNull
        @Override
        public ClassElement getGenericReturnType() {
            return methodElement.getGenericReturnType();
        }

        @NonNull
        @Override
        public MethodElement withNewParameters(@NonNull ParameterElement... newParameters) {
            this.beanParameters = initBeanParameters(ArrayUtils.concat(beanParameters, newParameters));
            return this;
        }

        @Override
        public ClassElement getDeclaringType() {
            return methodElement.getDeclaringType();
        }

        @Override
        public ClassElement getOwningType() {
            return AbstractBeanDefinitionBuilder.this.beanType;
        }
    }

    /**
     * Models a {@link io.micronaut.inject.ast.beans.BeanConstructorElement}.
     */
    private final class InternalBeanConstructorElement extends InternalBeanElement implements
                                                                                                       BeanConstructorElement {

        private final MethodElement methodElement;
        private final boolean requiresReflection;
        private BeanParameterElement[] beanParameters;

        private InternalBeanConstructorElement(MethodElement methodElement,
                                          boolean requiresReflection,
                                          BeanParameterElement[] beanParameters) {
            super(methodElement);
            this.methodElement = methodElement;
            this.requiresReflection = requiresReflection;
            this.beanParameters = beanParameters;
        }

        public boolean isRequiresReflection() {
            return requiresReflection;
        }

        @Override
        public boolean isPackagePrivate() {
            return methodElement.isPackagePrivate();
        }

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

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

        @Override
        public boolean isPrivate() {
            return methodElement.isPrivate();
        }

        @Override
        public boolean isFinal() {
            return methodElement.isFinal();
        }

        @Override
        public boolean isSuspend() {
            return methodElement.isSuspend();
        }

        @Override
        public boolean isDefault() {
            return methodElement.isDefault();
        }

        @Override
        public boolean isProtected() {
            return methodElement.isProtected();
        }

        @Override
        public boolean isPublic() {
            return methodElement.isPublic();
        }

        @NonNull
        @Override
        public BeanParameterElement[] getParameters() {
            return this.beanParameters;
        }

        @NonNull
        @Override
        public ClassElement getReturnType() {
            return methodElement.getReturnType();
        }

        @NonNull
        @Override
        public ClassElement getGenericReturnType() {
            return methodElement.getGenericReturnType();
        }

        @NonNull
        @Override
        public MethodElement withNewParameters(@NonNull ParameterElement... newParameters) {
            this.beanParameters = initBeanParameters(ArrayUtils.concat(beanParameters, newParameters));
            return this;
        }

        @Override
        public ClassElement getDeclaringType() {
            return methodElement.getDeclaringType();
        }

        @Override
        public ClassElement getOwningType() {
            return AbstractBeanDefinitionBuilder.this.beanType;
        }
    }

    /**
     * Models a {@link BeanFieldElement}.
     */
    private final class InternalBeanElementField extends InternalBeanElement implements BeanFieldElement {
        private final FieldElement fieldElement;
        private final boolean requiresReflection;
        private ClassElement genericType;

        private InternalBeanElementField(FieldElement element, boolean requiresReflection) {
            super(element);
            this.fieldElement = element;
            this.requiresReflection = requiresReflection;
        }

        public boolean isRequiresReflection() {
            return requiresReflection;
        }

        @Override
        public BeanFieldElement inject() {
            if (!AbstractBeanDefinitionBuilder.this.injectedFields.contains(this)) {
                AbstractBeanDefinitionBuilder.this.injectedFields.add(this);
            }
            return BeanFieldElement.super.inject();
        }

        @Override
        public BeanFieldElement injectValue(String expression) {
            if (!AbstractBeanDefinitionBuilder.this.injectedFields.contains(this)) {
                AbstractBeanDefinitionBuilder.this.injectedFields.add(this);
            }
            return BeanFieldElement.super.injectValue(expression);
        }

        @Override
        public ClassElement getDeclaringType() {
            return fieldElement.getDeclaringType();
        }

        @Override
        public ClassElement getOwningType() {
            return AbstractBeanDefinitionBuilder.this.beanType;
        }

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

        @Override
        public ClassElement getGenericField() {
            if (genericType != null) {
                return genericType;
            } else {
                return fieldElement.getGenericField();
            }
        }

        @NonNull
        @Override
        public BeanFieldElement typeArguments(@NonNull ClassElement... types) {
            final ClassElement genericType = fieldElement.getGenericField();
            final Map typeArguments = genericType.getTypeArguments();
            final Map resolved = resolveTypeArguments(typeArguments, types);
            if (resolved != null) {
                final String typeName = genericType.getName();
                this.genericType = ClassElement.of(
                        typeName,
                        genericType.isInterface(),
                        getAnnotationMetadata(),
                        resolved
                );
            }
            return this;
        }
    }

    /**
     * Models a {@link BeanParameterElement}.
     */
    private final class InternalBeanParameter extends InternalBeanElement implements BeanParameterElement {

        private final ParameterElement parameterElement;
        private ClassElement genericType;

        private InternalBeanParameter(ParameterElement element) {
            super(element);
            parameterElement = element;
        }

        @NonNull
        @Override
        public ClassElement getGenericType() {
            if (genericType != null) {
                return genericType;
            } else {
                return parameterElement.getGenericType();
            }
        }

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

        @SuppressWarnings("rawtypes")
        @NonNull
        @Override
        public BeanParameterElement typeArguments(@NonNull ClassElement... types) {
            final ClassElement genericType = parameterElement.getGenericType();
            final Map typeArguments = genericType.getTypeArguments();
            final Map resolved = resolveTypeArguments(typeArguments, types);
            if (resolved != null) {

                final ElementFactory elementFactory = visitorContext.getElementFactory();
                this.genericType = elementFactory.newClassElement(
                        genericType.getNativeType(),
                        getAnnotationMetadata(),
                        resolved
                );
            }
            return this;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy