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

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

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

import io.micronaut.context.AbstractInitializableBeanDefinitionReference;
import io.micronaut.context.annotation.*;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationUtil;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.type.DefaultArgument;
import io.micronaut.core.util.StringUtils;
import io.micronaut.inject.AdvisedBeanType;
import io.micronaut.inject.BeanDefinition;
import io.micronaut.inject.BeanDefinitionReference;
import io.micronaut.inject.annotation.AnnotationMetadataReference;
import jakarta.inject.Singleton;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Writes the bean definition class file to disk.
 *
 * @author Graeme Rocher
 * @author Denis Stepanov
 * @see BeanDefinitionReference
 * @since 1.0
 */
@Internal
public class BeanDefinitionReferenceWriter extends AbstractAnnotationMetadataWriter {

    /**
     * Suffix for reference classes.
     */
    public static final String REF_SUFFIX = "$Reference";

    private static final org.objectweb.asm.commons.Method BEAN_DEFINITION_REF_CLASS_CONSTRUCTOR = new org.objectweb.asm.commons.Method(CONSTRUCTOR_NAME, getConstructorDescriptor(
            String.class, // beanTypeName
            String.class, // beanDefinitionTypeName
            AnnotationMetadata.class, // annotationMetadata
            boolean.class, // isPrimary
            boolean.class, // isContextScope
            boolean.class, // isConditional
            boolean.class, // isContainerType
            boolean.class, // isSingleton
            boolean.class, // isConfigurationProperties
            boolean.class,  // hasExposedTypes
            boolean.class  // requiresMethodProcessing
    ));

    private final String beanTypeName;
    private final String beanDefinitionName;
    private final String beanDefinitionClassInternalName;
    private final String beanDefinitionReferenceClassName;
    private final Type interceptedType;
    private final Type providedType;
    private boolean contextScope = false;
    private boolean requiresMethodProcessing;

    /**
     * Default constructor.
     *
     * @param visitor      The visitor
     */
    public BeanDefinitionReferenceWriter(BeanDefinitionVisitor visitor) {
        super(
                visitor.getBeanDefinitionName() + REF_SUFFIX,
                visitor,
                visitor.getAnnotationMetadata(),
                true);
        this.providedType = visitor.getProvidedType();
        this.beanTypeName = visitor.getBeanTypeName();
        this.beanDefinitionName = visitor.getBeanDefinitionName();
        this.beanDefinitionReferenceClassName = beanDefinitionName + REF_SUFFIX;
        this.beanDefinitionClassInternalName = getInternalName(beanDefinitionName) + REF_SUFFIX;
        this.interceptedType = visitor.getInterceptedType().orElse(null);
    }

    /**
     * Accept an {@link ClassWriterOutputVisitor} to write all generated classes.
     *
     * @param outputVisitor The {@link ClassWriterOutputVisitor}
     * @throws IOException If an error occurs
     */
    @Override
    public void accept(ClassWriterOutputVisitor outputVisitor) throws IOException {
        try (OutputStream outputStream = outputVisitor.visitClass(getBeanDefinitionQualifiedClassName(), getOriginatingElements())) {
            ClassWriter classWriter = generateClassBytes();
            outputStream.write(classWriter.toByteArray());
        }
        outputVisitor.visitServiceDescriptor(
                BeanDefinitionReference.class,
                beanDefinitionReferenceClassName
        );
    }

    /**
     * Set whether the bean should be in context scope.
     *
     * @param contextScope The context scope
     */
    public void setContextScope(boolean contextScope) {
        this.contextScope = contextScope;
    }

    /**
     * Sets whether the {@link BeanDefinition#requiresMethodProcessing()} returns true.
     *
     * @param shouldPreProcess True if they should be pre-processed
     */
    public void setRequiresMethodProcessing(boolean shouldPreProcess) {
        this.requiresMethodProcessing = shouldPreProcess;
    }

    /**
     * Obtains the class name of the bean definition to be written. Java Annotation Processors need
     * this information to create a JavaFileObject using a Filer.
     *
     * @return the class name of the bean definition to be written
     */
    public String getBeanDefinitionQualifiedClassName() {
        String newClassName = beanDefinitionName;
        if (newClassName.endsWith("[]")) {
            newClassName = newClassName.substring(0, newClassName.length() - 2);
        }
        return newClassName + REF_SUFFIX;
    }

    private ClassWriter generateClassBytes() {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);

        Type superType = Type.getType(AbstractInitializableBeanDefinitionReference.class);
        String[] interfaceInternalNames;
        if (interceptedType != null) {
            interfaceInternalNames = new String[] { Type.getType(AdvisedBeanType.class).getInternalName() };
        } else {
            interfaceInternalNames = StringUtils.EMPTY_STRING_ARRAY;
        }
        startService(
                classWriter,
                BeanDefinitionReference.class.getName(),
                beanDefinitionClassInternalName,
                superType,
                interfaceInternalNames
        );
        Type beanDefinitionType = getTypeReferenceForName(beanDefinitionName);
        writeAnnotationMetadataStaticInitializer(classWriter);

        GeneratorAdapter cv = startConstructor(classWriter);

        cv.loadThis();
        // 1: beanTypeName
        cv.push(beanTypeName);
        // 2: beanDefinitionTypeName
        cv.push(beanDefinitionName);
        // 3: annotationMetadata
        if (annotationMetadata == AnnotationMetadata.EMPTY_METADATA || annotationMetadata.isEmpty()) {
            cv.getStatic(Type.getType(AnnotationMetadata.class), "EMPTY_METADATA", Type.getType(AnnotationMetadata.class));
        } else if (annotationMetadata instanceof AnnotationMetadataReference) {
            AnnotationMetadataReference reference = (AnnotationMetadataReference) annotationMetadata;
            String className = reference.getClassName();
            cv.getStatic(getTypeReferenceForName(className), AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class));
        } else {
            cv.getStatic(targetClassType, AbstractAnnotationMetadataWriter.FIELD_ANNOTATION_METADATA, Type.getType(AnnotationMetadata.class));
        }
        // 4: isPrimary
        cv.push(annotationMetadata.hasDeclaredStereotype(Primary.class));
        // 5: isContextScope
        cv.push(contextScope);
        // 6: isConditional
        cv.push(annotationMetadata.hasStereotype(Requires.class));
        // 7: isContainerType
        cv.push(DefaultArgument.CONTAINER_TYPES.stream().anyMatch(clazz -> clazz.getName().equals(beanTypeName)));
        // 8: isSingleton
        cv.push(
                annotationMetadata.hasDeclaredStereotype(AnnotationUtil.SINGLETON) ||
                        (!annotationMetadata.hasDeclaredStereotype(AnnotationUtil.SCOPE) &&
                                annotationMetadata.hasDeclaredStereotype(DefaultScope.class) &&
                                annotationMetadata.stringValue(DefaultScope.class)
                                        .map(t -> t.equals(Singleton.class.getName()) || t.equals(AnnotationUtil.SINGLETON))
                                        .orElse(false))
        );
        // 9: isConfigurationProperties
        cv.push(annotationMetadata.hasDeclaredStereotype(ConfigurationReader.class));
        // 10: hasExposedTypes
        cv.push(
                annotationMetadata.hasDeclaredAnnotation(Bean.class)
                        && annotationMetadata.stringValues(Bean.class, "typed").length > 0
        );
        // 10: requiresMethodProcessing
        cv.push(requiresMethodProcessing);
        // (...)
        cv.invokeConstructor(
                Type.getType(AbstractInitializableBeanDefinitionReference.class),
                BEAN_DEFINITION_REF_CLASS_CONSTRUCTOR
        );
        // RETURN
        cv.visitInsn(RETURN);
        // MAXSTACK = 2
        // MAXLOCALS = 1
        cv.visitMaxs(2, 1);

        // start method: BeanDefinition load()
        GeneratorAdapter loadMethod = startPublicMethodZeroArgs(classWriter, BeanDefinition.class, "load");
        // return new BeanDefinition()
        pushNewInstance(loadMethod, beanDefinitionType);
        // RETURN
        loadMethod.returnValue();
        loadMethod.visitMaxs(2, 1);

        // start method: Class getBeanDefinitionType()
        GeneratorAdapter getBeanDefinitionType = startPublicMethodZeroArgs(classWriter, Class.class, "getBeanDefinitionType");
        getBeanDefinitionType.push(beanDefinitionType);
        getBeanDefinitionType.returnValue();
        getBeanDefinitionType.visitMaxs(2, 1);

        // start method: Class getBeanType()
        GeneratorAdapter getBeanType = startPublicMethodZeroArgs(classWriter, Class.class, "getBeanType");
        getBeanType.push(providedType);
        getBeanType.returnValue();
        getBeanType.visitMaxs(2, 1);

        if (interceptedType != null) {
            super.implementInterceptedTypeMethod(interceptedType, classWriter);
        }
        for (GeneratorAdapter generatorAdapter : loadTypeMethods.values()) {
            generatorAdapter.visitMaxs(3, 1);
        }

        return classWriter;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy