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

io.micronaut.inject.beans.visitor.BeanPropertyWriter Maven / Gradle / Ivy

There is a newer version: 4.8.7
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
 *
 * 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.inject.beans.visitor;

import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.beans.BeanIntrospection;
import io.micronaut.core.naming.NameUtils;
import io.micronaut.core.naming.Named;
import io.micronaut.core.reflect.ReflectionUtils;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.CollectionUtils;
import io.micronaut.inject.annotation.AnnotationMetadataWriter;
import io.micronaut.inject.annotation.DefaultAnnotationMetadata;
import io.micronaut.inject.ast.ClassElement;
import io.micronaut.core.beans.AbstractBeanProperty;
import io.micronaut.inject.ast.MethodElement;
import io.micronaut.inject.ast.TypedElement;
import io.micronaut.inject.writer.AbstractClassFileWriter;
import io.micronaut.inject.writer.ClassWriterOutputVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Writes out {@link io.micronaut.core.beans.BeanProperty} instances to produce {@link io.micronaut.core.beans.BeanIntrospection} information.
 *
 * @author graemerocher
 * @since 1.1
 */
@Internal
class BeanPropertyWriter extends AbstractClassFileWriter implements Named {

    private static final Type TYPE_BEAN_PROPERTY = getTypeReference(AbstractBeanProperty.class);
    private static final Method METHOD_READ_INTERNAL = Method.getMethod(ReflectionUtils.getRequiredInternalMethod(AbstractBeanProperty.class, "readInternal", Object.class));
    private static final Method METHOD_WRITE_INTERNAL = Method.getMethod(ReflectionUtils.getRequiredInternalMethod(AbstractBeanProperty.class, "writeInternal", Object.class, Object.class));
    private final Type propertyType;
    private final String propertyName;
    private final AnnotationMetadata annotationMetadata;
    private final Type type;
    private final ClassWriter classWriter;
    private final Map typeArguments;
    private final Type beanType;
    private final boolean readOnly;
    private final MethodElement readMethod;
    private final MethodElement writeMethod;
    private final HashMap loadTypeMethods = new HashMap<>();
    private final TypedElement typeElement;
    private final ClassElement declaringElement;

    /**
     * Default constructor.
     * @param introspectionWriter The outer inspection writer.
     * @param typeElement  The type element
     * @param propertyType The property type
     * @param propertyName The property name
     * @param readMethod The read method name
     * @param writeMethod The write method name
     * @param isReadOnly Is the property read only
     * @param index The index for the type
     * @param annotationMetadata The annotation metadata
     * @param typeArguments The type arguments for the property
     */
    BeanPropertyWriter(
            @NonNull BeanIntrospectionWriter introspectionWriter,
            @NonNull TypedElement typeElement,
            @NonNull Type propertyType,
            @NonNull String propertyName,
            @Nullable MethodElement readMethod,
            @Nullable MethodElement writeMethod,
            boolean isReadOnly,
            int index,
            @Nullable AnnotationMetadata annotationMetadata,
            @Nullable Map typeArguments) {

        Type introspectionType = introspectionWriter.getIntrospectionType();
        this.declaringElement = introspectionWriter.getClassElement();
        this.typeElement = typeElement;
        this.beanType = introspectionWriter.getBeanType();
        this.propertyType = propertyType;
        this.readMethod = readMethod;
        this.writeMethod = writeMethod;
        this.propertyName = propertyName;
        this.readOnly = isReadOnly;
        this.annotationMetadata = annotationMetadata == AnnotationMetadata.EMPTY_METADATA ? null : annotationMetadata;
        this.type = getTypeReference(introspectionType.getClassName() + "$$" + index);
        this.classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        if (CollectionUtils.isNotEmpty(typeArguments)) {
            this.typeArguments = typeArguments;
        } else {
            this.typeArguments = null;
        }

    }

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

    /**
     * @return The property name
     */
    @NonNull
    public String getPropertyName() {
        return propertyName;
    }

    /**
     * @return The type for this writer.
     */
    public Type getType() {
        return type;
    }

    @Override
    public void accept(ClassWriterOutputVisitor classWriterOutputVisitor) throws IOException {
        try (OutputStream classOutput = classWriterOutputVisitor.visitClass(getName())) {
            startFinalClass(classWriter, type.getInternalName(), TYPE_BEAN_PROPERTY);

            writeConstructor();

            // the read method
            writeReadMethod();

            // the write method
            writeWriteMethod();

            if (readOnly) {
                // override isReadOnly method
                final GeneratorAdapter isReadOnly = startPublicMethodZeroArgs(classWriter, boolean.class, "isReadOnly");
                isReadOnly.push(true);
                isReadOnly.returnValue();
                isReadOnly.visitMaxs(1, 1);
                isReadOnly.endMethod();
            }

            if (writeMethod != null && readMethod == null) {
                // override isReadOnly method
                final GeneratorAdapter isWriteOnly = startPublicMethodZeroArgs(classWriter, boolean.class, "isWriteOnly");
                isWriteOnly.push(true);
                isWriteOnly.returnValue();
                isWriteOnly.visitMaxs(1, 1);
                isWriteOnly.endMethod();
            }

            for (GeneratorAdapter generator : loadTypeMethods.values()) {
                generator.visitMaxs(3, 1);
                generator.visitEnd();
            }
            classOutput.write(classWriter.toByteArray());
        }
    }

    private void writeWriteMethod() {
        final GeneratorAdapter writeMethod = startPublicMethod(
                classWriter,
                METHOD_WRITE_INTERNAL.getName(),
                void.class.getName(),
                Object.class.getName(),
                Object.class.getName()
        );
        writeMethod.loadArg(0);
        writeMethod.checkCast(beanType);
        writeMethod.loadArg(1);
        pushCastToType(writeMethod, propertyType);
        final boolean hasWriteMethod = this.writeMethod != null;
        final String methodName = hasWriteMethod ? this.writeMethod.getName() : NameUtils.setterNameFor(propertyName);
        final Object returnType = hasWriteMethod ? getTypeForElement(this.writeMethod.getReturnType()) : void.class;
        if (declaringElement.isInterface()) {
            writeMethod.invokeInterface(
                    beanType,
                    new Method(methodName,
                            getMethodDescriptor(returnType, Collections.singleton(propertyType)))
            );
        } else {
            writeMethod.invokeVirtual(
                    beanType,
                    new Method(methodName,
                            getMethodDescriptor(returnType, Collections.singleton(propertyType)))
            );
        }
        writeMethod.visitInsn(RETURN);
        writeMethod.visitMaxs(1, 1);
        writeMethod.visitEnd();

    }

    private void writeReadMethod() {
        final GeneratorAdapter readMethod = startPublicMethod(
                classWriter,
                METHOD_READ_INTERNAL.getName(),
                Object.class.getName(),
                Object.class.getName()
        );
        readMethod.loadArg(0);
        pushCastToType(readMethod, beanType.getClassName());
        final boolean isBoolean = propertyType.getClassName().equals("boolean");
        final String methodName = this.readMethod != null ? this.readMethod.getName() : NameUtils.getterNameFor(propertyName, isBoolean);
        if (declaringElement.isInterface()) {
            readMethod.invokeInterface(beanType, new Method(methodName, getMethodDescriptor(propertyType, Collections.emptyList())));
        } else {
            readMethod.invokeVirtual(beanType, new Method(methodName, getMethodDescriptor(propertyType, Collections.emptyList())));
        }
        pushBoxPrimitiveIfNecessary(propertyType, readMethod);
        readMethod.returnValue();
        readMethod.visitMaxs(1, 1);
        readMethod.visitEnd();
    }

    private void writeConstructor() {
        // a constructor that takes just the introspection
        final GeneratorAdapter constructor = startConstructor(classWriter, BeanIntrospection.class);

        constructor.loadThis();
        // 1st argument: the introspection
        constructor.loadArg(0);

        // 2nd argument: The property type
        constructor.push(propertyType);

        // 3rd argument: The property name
        constructor.push(propertyName);

        // 4th argument: The annotation metadata
        if (annotationMetadata != null && annotationMetadata instanceof DefaultAnnotationMetadata) {
            final DefaultAnnotationMetadata annotationMetadata = (DefaultAnnotationMetadata) this.annotationMetadata;
            if (annotationMetadata.isEmpty()) {
                constructor.visitInsn(ACONST_NULL);
            } else {
                AnnotationMetadataWriter.instantiateNewMetadata(type, classWriter, constructor, annotationMetadata, loadTypeMethods);
            }
        } else {
            constructor.visitInsn(ACONST_NULL);
        }

        // 5th argument: The type arguments
        if (typeArguments != null) {
            pushTypeArgumentElements(constructor, typeElement, typeArguments);
        } else {
            constructor.visitInsn(ACONST_NULL);
        }

        invokeConstructor(constructor, AbstractBeanProperty.class, BeanIntrospection.class, Class.class, String.class, AnnotationMetadata.class, Argument[].class);
        constructor.visitInsn(RETURN);
        constructor.visitMaxs(20, 2);

        constructor.visitEnd();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy