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

com.ui4j.bytebuddy.instrumentation.attribute.MethodAttributeAppender Maven / Gradle / Ivy

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

import com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationAppender;
import com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription;
import com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationList;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.ParameterDescription;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * An appender that writes attributes or annotations to a given ASM {@link com.ui4j.bytebuddy.jar.asm.MethodVisitor}.
 */
public interface MethodAttributeAppender {

    /**
     * Applies this attribute appender to a given method visitor.
     *
     * @param methodVisitor     The method visitor to which the attributes that are represented by this attribute
     *                          appender are written to.
     * @param methodDescription The description of the method for which the given method visitor creates an
     *                          instrumentation for.
     */
    void apply(MethodVisitor methodVisitor, MethodDescription methodDescription);

    /**
     * A method attribute appender that does not append any attributes.
     */
    static enum NoOp implements MethodAttributeAppender, Factory {

        /**
         * The singleton instance.
         */
        INSTANCE;

        @Override
        public MethodAttributeAppender make(TypeDescription typeDescription) {
            return this;
        }

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            /* do nothing */
        }
    }

    /**
     * Implementation of a method attribute appender that writes all annotations of the instrumented method to the
     * method that is being created. This includes method and parameter annotations.
     */
    static enum ForInstrumentedMethod implements MethodAttributeAppender, Factory {

        /**
         * The singleton instance.
         */
        INSTANCE;

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            AnnotationAppender methodAppender =
                    new AnnotationAppender.Default(new AnnotationAppender.Target.OnMethod(methodVisitor));
            for (AnnotationDescription annotation : methodDescription.getDeclaredAnnotations()) {
                methodAppender.append(annotation, AnnotationAppender.AnnotationVisibility.of(annotation));
            }
            int index = 0;
            for (ParameterDescription parameterDescription : methodDescription.getParameters()) {
                AnnotationAppender parameterAppender = new AnnotationAppender.Default(new AnnotationAppender.Target.OnMethodParameter(methodVisitor, index++));
                for (AnnotationDescription annotation : parameterDescription.getDeclaredAnnotations()) {
                    parameterAppender.append(annotation, AnnotationAppender.AnnotationVisibility.of(annotation));
                }
            }
        }

        @Override
        public MethodAttributeAppender make(TypeDescription typeDescription) {
            return this;
        }
    }

    /**
     * A factory that creates method attribute appenders for a given type.
     */
    static interface Factory {

        /**
         * Returns a method attribute appender that is applicable for a given type description.
         *
         * @param typeDescription The type for which a method attribute appender is to be applied for.
         * @return The method attribute appender which should be applied for the given type.
         */
        MethodAttributeAppender make(TypeDescription typeDescription);

        /**
         * A method attribute appender factory that combines several method attribute appender factories to be
         * represented as a single factory.
         */
        static class Compound implements Factory {

            /**
             * The factories this compound factory represents in their application order.
             */
            private final Factory[] factory;

            /**
             * Creates a new compound method attribute appender factory.
             *
             * @param factory The factories that are to be combined by this compound factory in the order of their
             *                application.
             */
            public Compound(Factory... factory) {
                this.factory = factory;
            }

            @Override
            public MethodAttributeAppender make(TypeDescription typeDescription) {
                MethodAttributeAppender[] methodAttributeAppender = new MethodAttributeAppender[factory.length];
                int index = 0;
                for (Factory factory : this.factory) {
                    methodAttributeAppender[index++] = factory.make(typeDescription);
                }
                return new MethodAttributeAppender.Compound(methodAttributeAppender);
            }

            @Override
            public boolean equals(Object other) {
                return this == other || !(other == null || getClass() != other.getClass())
                        && Arrays.equals(factory, ((Compound) other).factory);
            }

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

            @Override
            public String toString() {
                return "MethodAttributeAppender.Factory.Compound{factory=" + Arrays.toString(factory) + '}';
            }
        }
    }

    /**
     * Appends an annotation to a method or method parameter. The visibility of the annotation is determined by the
     * annotation type's {@link java.lang.annotation.RetentionPolicy} annotation.
     */
    static class ForAnnotation implements MethodAttributeAppender, Factory {

        /**
         * the annotations this method attribute appender is writing to its target.
         */
        private final AnnotationList annotations;

        /**
         * The target to which the annotations are written to.
         */
        private final Target target;

        /**
         * Create a new annotation appender for a method.
         *
         * @param annotation The annotations to append to the target method.
         */
        public ForAnnotation(Annotation... annotation) {
            annotations = new AnnotationList.ForLoadedAnnotation(annotation);
            target = Target.OnMethod.INSTANCE;
        }

        /**
         * Create a new annotation appender for a method parameter.
         *
         * @param parameterIndex The index of the target parameter.
         * @param annotation     The annotations to append to the target method parameter.
         */
        public ForAnnotation(int parameterIndex, Annotation... annotation) {
            annotations = new AnnotationList.ForLoadedAnnotation(annotation);
            target = new Target.OnMethodParameter(parameterIndex);
        }

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            AnnotationAppender appender = new AnnotationAppender.Default(target.make(methodVisitor, methodDescription));
            for (AnnotationDescription annotation : this.annotations) {
                appender.append(annotation, AnnotationAppender.AnnotationVisibility.of(annotation));
            }
        }

        @Override
        public MethodAttributeAppender make(TypeDescription typeDescription) {
            return this;
        }

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

        @Override
        public int hashCode() {
            return 31 * annotations.hashCode() + target.hashCode();
        }

        @Override
        public String toString() {
            return "MethodAttributeAppender.ForAnnotation{" +
                    "annotations=" + annotations +
                    ", target=" + target +
                    '}';
        }

        /**
         * Represents the target on which this method attribute appender should write its annotations to.
         */
        protected static interface Target {

            /**
             * Materializes the target for a given creation process.
             *
             * @param methodVisitor     The method visitor to which the attributes that are represented by this
             *                          attribute appender are written to.
             * @param methodDescription The description of the method for which the given method visitor creates an
             *                          instrumentation for.
             * @return The target of the annotation appender this target represents.
             */
            AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription);

            /**
             * A method attribute appender target for writing annotations directly onto the method.
             */
            static enum OnMethod implements Target {

                /**
                 * The singleton instance.
                 */
                INSTANCE;

                @Override
                public AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription) {
                    return new AnnotationAppender.Target.OnMethod(methodVisitor);
                }
            }

            /**
             * A method attribute appender target for writing annotations onto a given method parameter.
             */
            static class OnMethodParameter implements Target {

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

                /**
                 * Creates a target for a method attribute appender for a method parameter of the given index.
                 *
                 * @param parameterIndex The index of the target parameter.
                 */
                protected OnMethodParameter(int parameterIndex) {
                    this.parameterIndex = parameterIndex;
                }

                @Override
                public AnnotationAppender.Target make(MethodVisitor methodVisitor, MethodDescription methodDescription) {
                    if (parameterIndex >= methodDescription.getParameters().size()) {
                        throw new IllegalArgumentException("Method " + methodDescription + " has less then " + parameterIndex + " parameters");
                    }
                    return new AnnotationAppender.Target.OnMethodParameter(methodVisitor, parameterIndex);
                }

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

                @Override
                public int hashCode() {
                    return parameterIndex;
                }

                @Override
                public String toString() {
                    return "MethodAttributeAppender.ForAnnotation.Target.OnMethodParameter{parameterIndex=" + parameterIndex + '}';
                }
            }
        }
    }

    /**
     * Implementation of a method attribute appender that writes all annotations of a given loaded method to the
     * method that is being created. This includes method and parameter annotations. In order to being able to do so,
     * the target method and the given method must have compatible signatures, i.e. an identical number of method
     * parameters. Otherwise, an exception is thrown when this attribute appender is applied on a method.
     */
    static class ForLoadedMethod implements MethodAttributeAppender, Factory {

        /**
         * The method of which the annotations are to be copied.
         */
        private final Method method;

        /**
         * Creates a new attribute appender for a loaded method.
         *
         * @param method The method which is read for annotations to write to an instrumented method.
         */
        public ForLoadedMethod(Method method) {
            this.method = method;
        }

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            if (method.getParameterTypes().length > methodDescription.getParameters().size()) {
                throw new IllegalArgumentException("The constructor " + method + " has more parameters than the instrumented method " + methodDescription);
            }
            // Instead of implementing the appender we piggy-back on an existing implementation.
            ForInstrumentedMethod.INSTANCE.apply(methodVisitor, new MethodDescription.ForLoadedMethod(method));
        }

        @Override
        public MethodAttributeAppender make(TypeDescription typeDescription) {
            return this;
        }

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

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

        @Override
        public String toString() {
            return "MethodAttributeAppender.ForLoadedMethod{method=" + method + '}';
        }
    }

    /**
     * Implementation of a method attribute appender that writes all annotations of a given loaded constructor to the
     * method that is being created. This includes method and parameter annotations. In order to being able to do so,
     * the target method and the given constructor must have compatible signatures, i.e. an identical number of method
     * parameters. Otherwise, an exception is thrown when this attribute appender is applied on a method.
     */
    static class ForLoadedConstructor implements MethodAttributeAppender, Factory {

        /**
         * The constructor for which the annotations are to be copied.
         */
        private final Constructor constructor;

        /**
         * Creates a new attribute appender for a loaded constructor.
         *
         * @param constructor The constructor which is read for annotations to write to an instrumented method.
         */
        public ForLoadedConstructor(Constructor constructor) {
            this.constructor = constructor;
        }

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            if (constructor.getParameterTypes().length > methodDescription.getParameters().size()) {
                throw new IllegalArgumentException("The constructor " + constructor + " has more parameters than the instrumented method " + methodDescription);
            }
            ForInstrumentedMethod.INSTANCE.apply(methodVisitor, new MethodDescription.ForLoadedConstructor(constructor));
        }

        @Override
        public MethodAttributeAppender make(TypeDescription typeDescription) {
            return this;
        }

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

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

        @Override
        public String toString() {
            return "MethodAttributeAppender.ForLoadedConstructor{constructor=" + constructor + '}';
        }
    }

    /**
     * A method attribute appender that combines several method attribute appenders to be represented as a single
     * method attribute appender.
     */
    static class Compound implements MethodAttributeAppender {

        /**
         * The method attribute appenders this compound appender represents in their application order.
         */
        private final MethodAttributeAppender[] methodAttributeAppender;

        /**
         * Creates a new compound method attribute appender.
         *
         * @param methodAttributeAppender The method attribute appenders that are to be combined by this compound appender
         *                                in the order of their application.
         */
        public Compound(MethodAttributeAppender... methodAttributeAppender) {
            this.methodAttributeAppender = methodAttributeAppender;
        }

        @Override
        public void apply(MethodVisitor methodVisitor, MethodDescription methodDescription) {
            for (MethodAttributeAppender methodAttributeAppender : this.methodAttributeAppender) {
                methodAttributeAppender.apply(methodVisitor, methodDescription);
            }
        }

        @Override
        public boolean equals(Object other) {
            return this == other || !(other == null || getClass() != other.getClass())
                    && Arrays.equals(methodAttributeAppender, ((Compound) other).methodAttributeAppender);
        }

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

        @Override
        public String toString() {
            return "MethodAttributeAppender.Compound{methodAttributeAppender=" + Arrays.toString(methodAttributeAppender) + '}';
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy