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

com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationAppender Maven / Gradle / Ivy

The newest version!
package com.ui4j.bytebuddy.instrumentation.attribute.annotation;

import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.jar.asm.*;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;

/**
 * Annotation appenders are capable of writing annotations to a specified target.
 */
public interface AnnotationAppender {

    /**
     * Terminally writes the given annotation to the specified target.
     *
     * @param annotation           The annotation to be written.
     * @param annotationVisibility Determines the annotation visibility for the given annotation.
     * @return Usually {@code this} or any other annotation appender capable of writing another annotation to
     * the specified target.
     */
    AnnotationAppender append(AnnotationDescription annotation, AnnotationVisibility annotationVisibility);

    /**
     * Determines if an annotation should be written to a specified target and if the annotation should be marked
     * as being visible at runtime.
     */
    static enum AnnotationVisibility {

        /**
         * The annotation is preserved in the compiled class and visible at runtime.
         */
        RUNTIME(true, false),

        /**
         * The annotation is preserved in the compiled class but not visible at runtime.
         */
        CLASS_FILE(false, false),

        /**
         * The annotation is ignored.
         */
        INVISIBLE(false, true);

        /**
         * {@code true} if this annotation is visible at runtime.
         */
        private final boolean visible;

        /**
         * {@code true} if this annotation is added to a compiled class.
         */
        private final boolean suppressed;

        /**
         * Creates a new annotation visibility representation.
         *
         * @param visible    {@code true} if this annotation is visible at runtime.
         * @param suppressed {@code true} if this annotation is added to a compiled class.
         */
        private AnnotationVisibility(boolean visible, boolean suppressed) {
            this.visible = visible;
            this.suppressed = suppressed;
        }

        /**
         * Finds the annotation visibility that is declared for a given annotation.
         *
         * @param annotation The annotation of interest.
         * @return The annotation visibility of a given annotation. Annotations with a non-defined visibility or an
         * visibility of type {@link java.lang.annotation.RetentionPolicy#SOURCE} will be silently ignored.
         */
        public static AnnotationVisibility of(AnnotationDescription annotation) {
            AnnotationDescription.Loadable retention = annotation.getAnnotationType()
                    .getDeclaredAnnotations()
                    .ofType(Retention.class);
            if (retention == null || retention.loadSilent().value() == RetentionPolicy.SOURCE) {
                return INVISIBLE;
            } else if (retention.loadSilent().value() == RetentionPolicy.CLASS) {
                return CLASS_FILE;
            } else {
                return RUNTIME;
            }
        }

        /**
         * Checks if this instance represents an annotation that is visible at runtime, i.e. if this instance is
         * {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationAppender.AnnotationVisibility#RUNTIME}.
         *
         * @return {@code true} if this instance represents an annotation to be visible at runtime.
         */
        public boolean isVisible() {
            return visible;
        }

        /**
         * Checks if this instance represents an annotation that is not to be embedded into Java byte code, i.e.
         * if this instance is
         * {@link com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationAppender.AnnotationVisibility#INVISIBLE}.
         *
         * @return {@code true} if this instance represents an annotation to be suppressed from the byte code output.
         */
        public boolean isSuppressed() {
            return suppressed;
        }
    }

    /**
     * Represents a target for an annotation writing process.
     */
    static interface Target {

        /**
         * Creates an annotation visitor that is going to consume an annotation writing.
         *
         * @param annotationTypeDescriptor The type descriptor for the annotation to be written.
         * @param visible                  {@code true} if the annotation is to be visible at runtime.
         * @return An annotation visitor that is going to consume an annotation that is written to the latter
         * by the caller of this method.
         */
        AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible);

        /**
         * Target for an annotation that is written to a Java type.
         */
        static class OnType implements Target {

            /**
             * The class visitor to write the annotation to.
             */
            private final ClassVisitor classVisitor;

            /**
             * Creates a new wrapper for a Java type.
             *
             * @param classVisitor The ASM class visitor to which the annotations are appended to.
             */
            public OnType(ClassVisitor classVisitor) {
                this.classVisitor = classVisitor;
            }

            @Override
            public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
                return classVisitor.visitAnnotation(annotationTypeDescriptor, visible);
            }

            @Override
            public boolean equals(Object other) {
                return this == other || !(other == null || getClass() != other.getClass())
                        && classVisitor.equals(((OnType) other).classVisitor);
            }

            @Override
            public int hashCode() {
                return classVisitor.hashCode();
            }

            @Override
            public String toString() {
                return "AnnotationAppender.Target.OnType{classVisitor=" + classVisitor + '}';
            }
        }

        /**
         * Target for an annotation that is written to a Java method or constructor.
         */
        static class OnMethod implements Target {

            /**
             * The method visitor to write the annotation to.
             */
            private final MethodVisitor methodVisitor;

            /**
             * Creates a new wrapper for a Java method or constructor.
             *
             * @param methodVisitor The ASM method visitor to which the annotations are appended to.
             */
            public OnMethod(MethodVisitor methodVisitor) {
                this.methodVisitor = methodVisitor;
            }

            @Override
            public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
                return methodVisitor.visitAnnotation(annotationTypeDescriptor, visible);
            }

            @Override
            public boolean equals(Object other) {
                return this == other || !(other == null || getClass() != other.getClass())
                        && methodVisitor.equals(((OnMethod) other).methodVisitor);
            }

            @Override
            public int hashCode() {
                return methodVisitor.hashCode();
            }

            @Override
            public String toString() {
                return "AnnotationAppender.Target.OnMethod{methodVisitor=" + methodVisitor + '}';
            }
        }

        /**
         * Target for an annotation that is written to a Java method or constructor parameter.
         */
        static class OnMethodParameter implements Target {

            /**
             * The method visitor to write the annotation to.
             */
            private final MethodVisitor methodVisitor;

            /**
             * The method parameter index to write the annotation to.
             */
            private final int parameterIndex;

            /**
             * Creates a new wrapper for a Java method or constructor.
             *
             * @param methodVisitor  The ASM method visitor to which the annotations are appended to.
             * @param parameterIndex The index of the method parameter.
             */
            public OnMethodParameter(MethodVisitor methodVisitor, int parameterIndex) {
                this.methodVisitor = methodVisitor;
                this.parameterIndex = parameterIndex;
            }

            @Override
            public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
                return methodVisitor.visitParameterAnnotation(parameterIndex, annotationTypeDescriptor, visible);
            }

            @Override
            public boolean equals(Object other) {
                return this == other || !(other == null || getClass() != other.getClass())
                        && parameterIndex == ((OnMethodParameter) other).parameterIndex
                        && methodVisitor.equals(((OnMethodParameter) other).methodVisitor);
            }

            @Override
            public int hashCode() {
                return methodVisitor.hashCode() + 31 * parameterIndex;
            }

            @Override
            public String toString() {
                return "AnnotationAppender.Target.OnMethodParameter{" +
                        "methodVisitor=" + methodVisitor +
                        ", parameterIndex=" + parameterIndex +
                        '}';
            }
        }

        /**
         * Target for an annotation that is written to a Java field.
         */
        static class OnField implements Target {

            /**
             * The field visitor to write the annotation to.
             */
            private final FieldVisitor fieldVisitor;

            /**
             * Creates a new wrapper for a Java field.
             *
             * @param fieldVisitor The ASM field visitor to which the annotations are appended to.
             */
            public OnField(FieldVisitor fieldVisitor) {
                this.fieldVisitor = fieldVisitor;
            }

            @Override
            public AnnotationVisitor visit(String annotationTypeDescriptor, boolean visible) {
                return fieldVisitor.visitAnnotation(annotationTypeDescriptor, visible);
            }

            @Override
            public boolean equals(Object other) {
                return this == other || !(other == null || getClass() != other.getClass())
                        && fieldVisitor.equals(((OnField) other).fieldVisitor);
            }

            @Override
            public int hashCode() {
                return fieldVisitor.hashCode();
            }

            @Override
            public String toString() {
                return "AnnotationAppender.Target.OnField{" +
                        "fieldVisitor=" + fieldVisitor +
                        '}';
            }
        }
    }

    /**
     * A default implementation for an annotation appender that writes annotations to a given byte consumer
     * represented by an ASM {@link com.ui4j.bytebuddy.jar.asm.AnnotationVisitor}.
     */
    static class Default implements AnnotationAppender {

        /**
         * A constant for informing ASM over ignoring a given name.
         */
        private static final String ASM_IGNORE_NAME = null;

        /**
         * The target onto which an annotation write process is to be applied.
         */
        private final Target target;

        /**
         * Creates a default annotation appender.
         *
         * @param target The target to which annotations are written to.
         */
        public Default(Target target) {
            this.target = target;
        }

        @Override
        public AnnotationAppender append(AnnotationDescription annotation, AnnotationVisibility annotationVisibility) {
            if (!annotationVisibility.isSuppressed()) {
                doAppend(annotation, annotationVisibility.isVisible());
            }
            return this;
        }

        /**
         * Tries to append a given annotation by reflectively reading an annotation.
         *
         * @param annotation The annotation to be written.
         * @param visible    {@code true} if this annotation should be treated as visible at runtime.
         */
        private void doAppend(AnnotationDescription annotation, boolean visible) {
            handle(target.visit(annotation.getAnnotationType().getDescriptor(), visible), annotation);
        }

        /**
         * Handles the writing of a single annotation to an annotation visitor.
         *
         * @param annotationVisitor The annotation visitor the write process is to be applied on.
         * @param annotation        The annotation to be written.
         */
        private void handle(AnnotationVisitor annotationVisitor, AnnotationDescription annotation) {
            for (MethodDescription methodDescription : annotation.getAnnotationType().getDeclaredMethods()) {
                apply(annotationVisitor, methodDescription.getReturnType(), methodDescription.getName(), annotation.getValue(methodDescription));
            }
            annotationVisitor.visitEnd();
        }

        /**
         * Performs the writing of a given annotation value to an annotation visitor.
         *
         * @param annotationVisitor The annotation visitor the write process is to be applied on.
         * @param valueType         The type of the annotation value.
         * @param name              The name of the annotation type.
         * @param value             The annotation's value.
         */
        private void apply(AnnotationVisitor annotationVisitor, TypeDescription valueType, String name, Object value) {
            if (valueType.isAnnotation()) {
                handle(annotationVisitor.visitAnnotation(name, valueType.getDescriptor()), (AnnotationDescription) value);
            } else if (valueType.isEnum()) {
                annotationVisitor.visitEnum(name, valueType.getDescriptor(), ((AnnotationDescription.EnumerationValue) value).getValue());
            } else if (valueType.isAssignableFrom(Class.class)) {
                annotationVisitor.visit(name, Type.getType(((TypeDescription) value).getDescriptor()));
            } else if (valueType.isArray()) {
                AnnotationVisitor arrayVisitor = annotationVisitor.visitArray(name);
                int length = Array.getLength(value);
                TypeDescription componentType = valueType.getComponentType();
                for (int index = 0; index < length; index++) {
                    apply(arrayVisitor, componentType, ASM_IGNORE_NAME, Array.get(value, index));
                }
                arrayVisitor.visitEnd();
            } else {
                annotationVisitor.visit(name, value);
            }
        }

        @Override
        public boolean equals(Object other) {
            return this == other || !(other == null || getClass() != other.getClass())
                    && target.equals(((Default) other).target);
        }

        @Override
        public int hashCode() {
            return target.hashCode();
        }

        @Override
        public String toString() {
            return "AnnotationAppender.Default{target=" + target + '}';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy