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

com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
package com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation;

import com.ui4j.bytebuddy.ByteBuddy;
import com.ui4j.bytebuddy.ClassFileVersion;
import com.ui4j.bytebuddy.dynamic.DynamicType;
import com.ui4j.bytebuddy.dynamic.TargetType;
import com.ui4j.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import com.ui4j.bytebuddy.instrumentation.Instrumentation;
import com.ui4j.bytebuddy.instrumentation.attribute.annotation.AnnotationDescription;
import com.ui4j.bytebuddy.instrumentation.field.FieldDescription;
import com.ui4j.bytebuddy.instrumentation.field.FieldList;
import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.MethodList;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.MethodDelegationBinder;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.Duplication;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.TypeCreation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.FieldAccess;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodInvocation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodReturn;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodVariableAccess;
import com.ui4j.bytebuddy.instrumentation.type.InstrumentedType;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.instrumentation.type.auxiliary.AuxiliaryType;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;

import java.io.Serializable;
import java.lang.annotation.*;
import java.util.Collections;

import static com.ui4j.bytebuddy.matcher.ElementMatchers.*;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.nonNull;

/**
 * Using this annotation it is possible to access fields by getter and setter types. Before this annotation can be
 * used, it needs to be installed with two types. The getter type must be defined in a single-method interface
 * with a single method that returns an {@link java.lang.Object} type and takes no arguments. The getter interface
 * must similarly return {@code void} and take a single {@link java.lang.Object} argument. After installing these
 * interfaces with the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field.Binder}, this
 * binder needs to be registered with a {@link com.ui4j.bytebuddy.instrumentation.MethodDelegation} before it can be used.
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Field {

    /**
     * A placeholder name to indicate that a field name should be inferred by the name of the intercepted
     * method by the Java bean naming conventions.
     */
    static final String BEAN_PROPERTY = "";

    /**
     * Determines if the proxy should be serializable.
     *
     * @return {@code true} if the proxy should be serializable.
     */
    boolean serializableProxy() default false;

    /**
     * Determines the name of the field that is to be accessed. If this property is not set, a field name is inferred
     * by the intercepted method after the Java beans naming conventions.
     *
     * @return The name of the field to be accessed.
     */
    String value() default "";

    /**
     * Determines which type defines the field that is to be accessed. If this property is not set, the most field
     * that is defined highest in the type hierarchy is accessed.
     *
     * @return The type that defines the accessed field.
     */
    Class definingType() default void.class;

    /**
     * A binder for the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field} annotation.
     */
    static class Binder implements TargetMethodAnnotationDrivenBinder.ParameterBinder {

        /**
         * A reference to the method that declares the field annotation's defining type property.
         */
        private static final MethodDescription DEFINING_TYPE;

        /**
         * A reference to the method that declares the field annotation's field name property.
         */
        private static final MethodDescription FIELD_NAME;

        /**
         * A reference to the method that declares the field annotation's serializable proxy property.
         */
        private static final MethodDescription SERIALIZABLE_PROXY;

        /**
         * Fetches a reference to all annotation properties.
         */
        static {
            MethodList methodList = new TypeDescription.ForLoadedType(Field.class).getDeclaredMethods();
            DEFINING_TYPE = methodList.filter(named("definingType")).getOnly();
            FIELD_NAME = methodList.filter(named("value")).getOnly();
            SERIALIZABLE_PROXY = methodList.filter(named("serializableProxy")).getOnly();
        }

        /**
         * The getter method to be implemented by a getter proxy.
         */
        private final MethodDescription getterMethod;

        /**
         * The setter method to be implemented by a setter proxy.
         */
        private final MethodDescription setterMethod;

        /**
         * Creates a new binder for the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field}
         * annotation.
         *
         * @param getterMethod The getter method to be implemented by a getter proxy.
         * @param setterMethod The setter method to be implemented by a setter proxy.
         */
        protected Binder(MethodDescription getterMethod, MethodDescription setterMethod) {
            this.getterMethod = getterMethod;
            this.setterMethod = setterMethod;
        }

        /**
         * Creates a binder by installing two proxy types which are implemented by this binder if a field getter
         * or a field setter is requested by using the
         * {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field} annotation.
         *
         * @param getterType The type which should be used for getter proxies. The type must
         *                   represent an interface which defines a single method which returns an
         *                   {@link java.lang.Object} return type and does not take any arguments. The use of generics
         *                   is permitted.
         * @param setterType The type which should be uses for setter proxies. The type must
         *                   represent an interface which defines a single method which returns {@code void}
         *                   and takes a signle {@link java.lang.Object}-typed argument. The use of generics
         *                   is permitted.
         * @return A binder for the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field}
         * annotation.
         */
        public static TargetMethodAnnotationDrivenBinder.ParameterBinder install(Class getterType,
                                                                                        Class setterType) {
            return install(new TypeDescription.ForLoadedType(nonNull(getterType)), new TypeDescription.ForLoadedType(nonNull(setterType)));
        }

        /**
         * Creates a binder by installing two proxy types which are implemented by this binder if a field getter
         * or a field setter is requested by using the
         * {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field} annotation.
         *
         * @param getterType The type which should be used for getter proxies. The type must
         *                   represent an interface which defines a single method which returns an
         *                   {@link java.lang.Object} return type and does not take any arguments. The use of generics
         *                   is permitted.
         * @param setterType The type which should be uses for setter proxies. The type must
         *                   represent an interface which defines a single method which returns {@code void}
         *                   and takes a signle {@link java.lang.Object}-typed argument. The use of generics
         *                   is permitted.
         * @return A binder for the {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field}
         * annotation.
         */
        public static TargetMethodAnnotationDrivenBinder.ParameterBinder install(TypeDescription getterType,
                                                                                        TypeDescription setterType) {
            MethodDescription getterMethod = onlyMethod(nonNull(getterType));
            if (!getterMethod.getReturnType().represents(Object.class)) {
                throw new IllegalArgumentException(getterMethod + " must take a single Object-typed parameter");
            } else if (getterMethod.getParameterTypes().size() != 0) {
                throw new IllegalArgumentException(getterMethod + " must not declare parameters");
            }
            MethodDescription setterMethod = onlyMethod(nonNull(setterType));
            if (!setterMethod.getReturnType().represents(void.class)) {
                throw new IllegalArgumentException(setterMethod + " must return void");
            } else if (setterMethod.getParameterTypes().size() != 1 || !setterMethod.getParameterTypes().get(0).represents(Object.class)) {
                throw new IllegalArgumentException(setterMethod + " must declare a single Object-typed parameters");
            }
            return new Binder(getterMethod, setterMethod);
        }

        /**
         * Extracts the only method from a given type description which is validated for the required properties for
         * using the type as a proxy base type.
         *
         * @param typeDescription The type description to evaluate.
         * @return The only method which was found to be compatible to the proxy requirements.
         */
        private static MethodDescription onlyMethod(TypeDescription typeDescription) {
            if (!typeDescription.isInterface()) {
                throw new IllegalArgumentException(typeDescription + " is not an interface");
            } else if (typeDescription.getInterfaces().size() > 0) {
                throw new IllegalArgumentException(typeDescription + " must not extend other interfaces");
            } else if (!typeDescription.isPublic()) {
                throw new IllegalArgumentException(typeDescription + " is mot public");
            }
            MethodList methodCandidates = typeDescription.getDeclaredMethods().filter(not(isStatic()));
            if (methodCandidates.size() != 1) {
                throw new IllegalArgumentException(typeDescription + " must declare exactly one non-static method");
            }
            return methodCandidates.getOnly();
        }

        @Override
        public Class getHandledType() {
            return Field.class;
        }

        @Override
        public MethodDelegationBinder.ParameterBinding bind(AnnotationDescription.Loadable annotation,
                                                               int targetParameterIndex,
                                                               MethodDescription source,
                                                               MethodDescription target,
                                                               Instrumentation.Target instrumentationTarget,
                                                               Assigner assigner) {
            TypeDescription parameterType = target.getParameterTypes().get(targetParameterIndex);
            AccessType accessType;
            if (parameterType.equals(getterMethod.getDeclaringType())) {
                accessType = AccessType.GETTER;
            } else if (parameterType.equals(setterMethod.getDeclaringType())) {
                accessType = AccessType.SETTER;
            } else {
                throw new IllegalStateException(target + " uses a @Field annotation on an non-installed type");
            }
            FieldLocator.Resolution resolution = FieldLocator.of(annotation.getValue(FIELD_NAME, String.class), source)
                    .lookup(annotation.getValue(DEFINING_TYPE, TypeDescription.class), instrumentationTarget.getTypeDescription())
                    .resolve(instrumentationTarget.getTypeDescription());
            return resolution.isValid()
                    ? new MethodDelegationBinder.ParameterBinding.Anonymous(new AccessorProxy(
                    resolution.getFieldDescription(),
                    assigner,
                    instrumentationTarget.getTypeDescription(),
                    accessType,
                    annotation.getValue(SERIALIZABLE_PROXY, Boolean.class)))
                    : MethodDelegationBinder.ParameterBinding.Illegal.INSTANCE;
        }

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

        @Override
        public int hashCode() {
            int result = getterMethod.hashCode();
            result = 31 * result + setterMethod.hashCode();
            return result;
        }

        @Override
        public String toString() {
            return "Field.Binder{" +
                    "getterMethod=" + getterMethod +
                    ", setterMethod=" + setterMethod +
                    '}';
        }

        /**
         * Represents an instrumentation for implementing a proxy type constructor when a static field is accessed.
         */
        protected static enum StaticFieldConstructor implements Instrumentation {

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

            /**
             * A reference of the {@link Object} type default constructor.
             */
            protected final MethodDescription objectTypeDefaultConstructor;

            /**
             * Creates the constructor call singleton.
             */
            private StaticFieldConstructor() {
                objectTypeDefaultConstructor = new TypeDescription.ForLoadedType(Object.class)
                        .getDeclaredMethods()
                        .filter(isConstructor())
                        .getOnly();
            }

            @Override
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }

            @Override
            public ByteCodeAppender appender(Target instrumentationTarget) {
                return new ByteCodeAppender.Simple(MethodVariableAccess.REFERENCE.loadFromIndex(0),
                        MethodInvocation.invoke(objectTypeDefaultConstructor),
                        MethodReturn.VOID);
            }
        }

        /**
         * Determines the way a field is to be accessed.
         */
        protected static enum AccessType {

            /**
             * Represents getter access for a field.
             */
            GETTER {
                @Override
                protected TypeDescription proxyType(MethodDescription getterMethod, MethodDescription setterMethod) {
                    return getterMethod.getDeclaringType();
                }

                @Override
                protected Instrumentation access(FieldDescription fieldDescription,
                                                 Assigner assigner,
                                                 AuxiliaryType.MethodAccessorFactory methodAccessorFactory) {
                    return new Getter(fieldDescription, assigner, methodAccessorFactory);
                }
            },

            /**
             * Represents setter access for a field.
             */
            SETTER {
                @Override
                protected TypeDescription proxyType(MethodDescription getterMethod, MethodDescription setterMethod) {
                    return setterMethod.getDeclaringType();
                }

                @Override
                protected Instrumentation access(FieldDescription fieldDescription,
                                                 Assigner assigner,
                                                 AuxiliaryType.MethodAccessorFactory methodAccessorFactory) {
                    return new Setter(fieldDescription, assigner, methodAccessorFactory);
                }
            };


            /**
             * Locates the type to be implemented by a field accessor proxy.
             *
             * @param getterMethod The getter method to be implemented by a getter proxy.
             * @param setterMethod The setter method to be implemented by a setter proxy.
             * @return The correct proxy type for the represented sort of accessor.
             */
            protected abstract TypeDescription proxyType(MethodDescription getterMethod, MethodDescription setterMethod);

            /**
             * Returns an instrumentation that implements the sort of accessor implementation that is represented by
             * this instance.
             *
             * @param fieldDescription      The field to be accessed.
             * @param assigner              The assigner to use.
             * @param methodAccessorFactory The accessed type's method accessor factory.
             * @return A suitable instrumentation.
             */
            protected abstract Instrumentation access(FieldDescription fieldDescription,
                                                      Assigner assigner,
                                                      AuxiliaryType.MethodAccessorFactory methodAccessorFactory);

            /**
             * Instrumentation for a getter method.
             */
            protected static class Getter implements Instrumentation {

                /**
                 * The field that is being accessed.
                 */
                private final FieldDescription accessedField;

                /**
                 * The assigner to use.
                 */
                private final Assigner assigner;

                /**
                 * The accessed type's method accessor factory.
                 */
                private final AuxiliaryType.MethodAccessorFactory methodAccessorFactory;

                /**
                 * Creates a new getter instrumentation.
                 *
                 * @param accessedField         The field that is being accessed.
                 * @param assigner              The assigner to use.
                 * @param methodAccessorFactory The accessed type's method accessor factory.
                 */
                protected Getter(FieldDescription accessedField,
                                 Assigner assigner,
                                 AuxiliaryType.MethodAccessorFactory methodAccessorFactory) {
                    this.accessedField = accessedField;
                    this.assigner = assigner;
                    this.methodAccessorFactory = methodAccessorFactory;
                }

                @Override
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
                    return instrumentedType;
                }

                @Override
                public ByteCodeAppender appender(Target instrumentationTarget) {
                    return new Appender(instrumentationTarget);
                }

                @Override
                public boolean equals(Object other) {
                    if (this == other) return true;
                    if (other == null || getClass() != other.getClass()) return false;
                    Getter getter = (Getter) other;
                    return accessedField.equals(getter.accessedField)
                            && assigner.equals(getter.assigner)
                            && methodAccessorFactory.equals(getter.methodAccessorFactory);
                }

                @Override
                public int hashCode() {
                    int result = accessedField.hashCode();
                    result = 31 * result + assigner.hashCode();
                    result = 31 * result + methodAccessorFactory.hashCode();
                    return result;
                }

                @Override
                public String toString() {
                    return "Field.Binder.AccessType.Getter{" +
                            "accessedField=" + accessedField +
                            ", assigner=" + assigner +
                            ", methodAccessorFactory=" + methodAccessorFactory +
                            '}';
                }

                /**
                 * A byte code appender for a getter method.
                 */
                protected class Appender implements ByteCodeAppender {

                    /**
                     * The generated accessor type.
                     */
                    private final TypeDescription typeDescription;

                    /**
                     * Creates a new appender for a setter method.
                     *
                     * @param instrumentationTarget The instrumentation target of the current instrumentation.
                     */
                    protected Appender(Target instrumentationTarget) {
                        typeDescription = instrumentationTarget.getTypeDescription();
                    }

                    @Override
                    public boolean appendsCode() {
                        return true;
                    }

                    @Override
                    public Size apply(MethodVisitor methodVisitor,
                                      Context instrumentationContext,
                                      MethodDescription instrumentedMethod) {
                        MethodDescription getterMethod = methodAccessorFactory.registerGetterFor(accessedField);
                        StackManipulation.Size stackSize = new StackManipulation.Compound(
                                accessedField.isStatic()
                                        ? StackManipulation.LegalTrivial.INSTANCE
                                        : new StackManipulation.Compound(
                                        MethodVariableAccess.REFERENCE.loadFromIndex(0),
                                        FieldAccess.forField(typeDescription.getDeclaredFields()
                                                .filter((named(AccessorProxy.FIELD_NAME))).getOnly()).getter()),
                                MethodInvocation.invoke(getterMethod),
                                assigner.assign(getterMethod.getReturnType(), instrumentedMethod.getReturnType(), true),
                                MethodReturn.returning(instrumentedMethod.getReturnType())
                        ).apply(methodVisitor, instrumentationContext);
                        return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
                    }

                    /**
                     * Returns the outer instance.
                     *
                     * @return The outer instance.
                     */
                    private Getter getOuter() {
                        return Getter.this;
                    }

                    @Override
                    public boolean equals(Object other) {
                        return this == other || !(other == null || getClass() != other.getClass())
                                && Getter.this.equals(((Appender) other).getOuter())
                                && typeDescription.equals(((Appender) other).typeDescription);
                    }

                    @Override
                    public int hashCode() {
                        return typeDescription.hashCode() + 31 * Getter.this.hashCode();
                    }

                    @Override
                    public String toString() {
                        return "Field.Binder.AccessType.Getter.Appender{" +
                                "getter=" + Getter.this +
                                "typeDescription=" + typeDescription +
                                '}';
                    }
                }
            }

            /**
             * Instrumentation for a setter method.
             */
            protected static class Setter implements Instrumentation {

                /**
                 * The field that is being accessed.
                 */
                private final FieldDescription accessedField;

                /**
                 * The assigner to use.
                 */
                private final Assigner assigner;

                /**
                 * The accessed type's method accessor factory.
                 */
                private final AuxiliaryType.MethodAccessorFactory methodAccessorFactory;

                /**
                 * Creates a new setter instrumentation.
                 *
                 * @param accessedField         The field that is being accessed.
                 * @param assigner              The assigner to use.
                 * @param methodAccessorFactory The accessed type's method accessor factory.
                 */
                protected Setter(FieldDescription accessedField,
                                 Assigner assigner,
                                 AuxiliaryType.MethodAccessorFactory methodAccessorFactory) {
                    this.accessedField = accessedField;
                    this.assigner = assigner;
                    this.methodAccessorFactory = methodAccessorFactory;
                }

                @Override
                public InstrumentedType prepare(InstrumentedType instrumentedType) {
                    return instrumentedType;
                }

                @Override
                public ByteCodeAppender appender(Target instrumentationTarget) {
                    return new Appender(instrumentationTarget);
                }

                @Override
                public boolean equals(Object other) {
                    if (this == other) return true;
                    if (other == null || getClass() != other.getClass()) return false;
                    Setter getter = (Setter) other;
                    return accessedField.equals(getter.accessedField)
                            && assigner.equals(getter.assigner)
                            && methodAccessorFactory.equals(getter.methodAccessorFactory);
                }

                @Override
                public int hashCode() {
                    int result = accessedField.hashCode();
                    result = 31 * result + assigner.hashCode();
                    result = 31 * result + methodAccessorFactory.hashCode();
                    return result;
                }

                @Override
                public String toString() {
                    return "Field.Binder.AccessType.Setter{" +
                            "accessedField=" + accessedField +
                            ", assigner=" + assigner +
                            ", methodAccessorFactory=" + methodAccessorFactory +
                            '}';
                }

                /**
                 * A byte code appender for a setter method.
                 */
                protected class Appender implements ByteCodeAppender {

                    /**
                     * The generated accessor type.
                     */
                    private final TypeDescription typeDescription;

                    /**
                     * Creates a new appender for a setter method.
                     *
                     * @param instrumentationTarget The instrumentation target of the current instrumentation.
                     */
                    protected Appender(Target instrumentationTarget) {
                        typeDescription = instrumentationTarget.getTypeDescription();
                    }

                    @Override
                    public boolean appendsCode() {
                        return true;
                    }

                    @Override
                    public Size apply(MethodVisitor methodVisitor,
                                      Context instrumentationContext,
                                      MethodDescription instrumentedMethod) {
                        TypeDescription parameterType = instrumentedMethod.getParameterTypes().get(0);
                        MethodDescription setterMethod = methodAccessorFactory.registerSetterFor(accessedField);
                        StackManipulation.Size stackSize = new StackManipulation.Compound(
                                accessedField.isStatic()
                                        ? StackManipulation.LegalTrivial.INSTANCE
                                        : new StackManipulation.Compound(
                                        MethodVariableAccess.REFERENCE.loadFromIndex(0),
                                        FieldAccess.forField(typeDescription.getDeclaredFields()
                                                .filter((named(AccessorProxy.FIELD_NAME))).getOnly()).getter()),
                                MethodVariableAccess.forType(parameterType).loadFromIndex(1),
                                assigner.assign(parameterType, setterMethod.getParameterTypes().get(0), true),
                                MethodInvocation.invoke(setterMethod),
                                MethodReturn.VOID
                        ).apply(methodVisitor, instrumentationContext);
                        return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
                    }

                    /**
                     * Returns the outer instance.
                     *
                     * @return The outer instance.
                     */
                    private Setter getOuter() {
                        return Setter.this;
                    }

                    @Override
                    public boolean equals(Object other) {
                        return this == other || !(other == null || getClass() != other.getClass())
                                && Setter.this.equals(((Appender) other).getOuter())
                                && typeDescription.equals(((Appender) other).typeDescription);
                    }

                    @Override
                    public int hashCode() {
                        return typeDescription.hashCode() + 31 * Setter.this.hashCode();
                    }

                    @Override
                    public String toString() {
                        return "Field.Binder.AccessType.Setter.Appender{" +
                                "setter=" + Setter.this +
                                "typeDescription=" + typeDescription +
                                '}';
                    }
                }
            }
        }

        /**
         * Represents an instrumentation for implementing a proxy type constructor when a non-static field is accessed.
         */
        protected static class InstanceFieldConstructor implements Instrumentation {

            /**
             * The instrumented type from which a field is to be accessed.
             */
            private final TypeDescription instrumentedType;

            /**
             * Creates a new instrumentation for implementing a field accessor proxy's constructor when accessing
             * a non-static field.
             *
             * @param instrumentedType The instrumented type from which a field is to be accessed.
             */
            protected InstanceFieldConstructor(TypeDescription instrumentedType) {
                this.instrumentedType = instrumentedType;
            }

            @Override
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType.withField(AccessorProxy.FIELD_NAME,
                        this.instrumentedType,
                        Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE);
            }

            @Override
            public ByteCodeAppender appender(Target instrumentationTarget) {
                return new Appender(instrumentationTarget);
            }

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

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

            @Override
            public String toString() {
                return "Field.Binder.InstanceFieldConstructor{" +
                        "instrumentedType=" + instrumentedType +
                        '}';
            }

            /**
             * An appender for implementing an
             * {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field.Binder.InstanceFieldConstructor}.
             */
            protected static class Appender implements ByteCodeAppender {

                /**
                 * The field to be set within the constructor.
                 */
                private final FieldDescription fieldDescription;

                /**
                 * Creates a new appender.
                 *
                 * @param instrumentationTarget The instrumentation target of the current instrumentation.
                 */
                protected Appender(Target instrumentationTarget) {
                    fieldDescription = instrumentationTarget.getTypeDescription()
                            .getDeclaredFields()
                            .filter((named(AccessorProxy.FIELD_NAME)))
                            .getOnly();
                }

                @Override
                public boolean appendsCode() {
                    return true;
                }

                @Override
                public Size apply(MethodVisitor methodVisitor,
                                  Context instrumentationContext,
                                  MethodDescription instrumentedMethod) {
                    StackManipulation.Size stackSize = new StackManipulation.Compound(
                            MethodVariableAccess.REFERENCE.loadFromIndex(0),
                            MethodInvocation.invoke(StaticFieldConstructor.INSTANCE.objectTypeDefaultConstructor),
                            MethodVariableAccess.loadThisReferenceAndArguments(instrumentedMethod),
                            FieldAccess.forField(fieldDescription).putter(),
                            MethodReturn.VOID
                    ).apply(methodVisitor, instrumentationContext);
                    return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
                }

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

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

                @Override
                public String toString() {
                    return "Field.Binder.InstanceFieldConstructor.Appender{" +
                            "fieldDescription=" + fieldDescription +
                            '}';
                }
            }
        }

        /**
         * A field locator is responsible for locating the type a field is defined in.
         */
        protected abstract static class FieldLocator {

            /**
             * Returns a field locator for a given field.
             *
             * @param fieldName         The field's name which might represent
             *                          {@link com.ui4j.bytebuddy.instrumentation.method.bytecode.bind.annotation.Field#BEAN_PROPERTY}
             *                          if the field's name should be derived from a method's name.
             * @param methodDescription The intercepted method.
             * @return An appropriate field locator.
             */
            protected static FieldLocator of(String fieldName, MethodDescription methodDescription) {
                return BEAN_PROPERTY.equals(fieldName)
                        ? Legal.consider(methodDescription)
                        : new Legal(fieldName);
            }

            /**
             * Locates a field of a given name on a specific type.
             *
             * @param typeDescription  The type which defines the field or a representation of {@code void} for
             *                         looking up a type implicitly within the type hierarchy.
             * @param instrumentedType The instrumented type from which a field is to be accessed.
             * @return A corresponding lookup engine.
             */
            protected abstract LookupEngine lookup(TypeDescription typeDescription, TypeDescription instrumentedType);

            /**
             * A resolution represents the result of a field location.
             */
            protected abstract static class Resolution {

                /**
                 * Determines if a field lookup was successful.
                 *
                 * @return {@code true} if a field lookup was successful.
                 */
                protected abstract boolean isValid();

                /**
                 * Returns a description of the located field. This method must only be called for valid
                 * resolutions.
                 *
                 * @return The located field.
                 */
                protected abstract FieldDescription getFieldDescription();

                /**
                 * A resolution for a non-located field.
                 */
                protected static class Unresolved extends Resolution {

                    @Override
                    protected boolean isValid() {
                        return false;
                    }

                    @Override
                    protected FieldDescription getFieldDescription() {
                        throw new IllegalStateException("Cannot resolve an unresolved field lookup");
                    }

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

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

                    @Override
                    public String toString() {
                        return "Field.Binder.FieldLocator.Resolution.Unresolved{}";
                    }
                }

                /**
                 * A resolution for a successfully located field.
                 */
                protected static class Resolved extends Resolution {

                    /**
                     * The located field.
                     */
                    private final FieldDescription fieldDescription;

                    /**
                     * Creates a new successful resolution.
                     *
                     * @param fieldDescription The located field.
                     */
                    protected Resolved(FieldDescription fieldDescription) {
                        this.fieldDescription = fieldDescription;
                    }

                    @Override
                    protected boolean isValid() {
                        return true;
                    }

                    @Override
                    protected FieldDescription getFieldDescription() {
                        return fieldDescription;
                    }

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

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

                    @Override
                    public String toString() {
                        return "Field.Binder.FieldLocator.Resolution.Resolved{" +
                                "fieldDescription=" + fieldDescription +
                                '}';
                    }
                }
            }

            /**
             * A lookup engine is responsible for finding a specific field in a type hierarchy.
             */
            protected abstract static class LookupEngine {

                /**
                 * Locates a field if possible and returns a corresponding resolution.
                 *
                 * @param instrumentedType The instrumented type from which a field is to be accessed.
                 * @return A resolution of the field name lookup.
                 */
                protected abstract Resolution resolve(TypeDescription instrumentedType);

                /**
                 * Represents a lookup engine that can only produce illegal look-ups.
                 */
                protected static class Illegal extends LookupEngine {

                    @Override
                    protected Resolution resolve(TypeDescription instrumentedType) {
                        return new Resolution.Unresolved();
                    }

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

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

                    @Override
                    public String toString() {
                        return "Field.Binder.FieldLocator.LookupEngine.Illegal{}";
                    }
                }

                /**
                 * Represents a lookup engine that tries to find the most specific field in a class hierarchy.
                 */
                protected static class ForHierarchy extends LookupEngine {

                    /**
                     * The name of the field to be found.
                     */
                    private final String fieldName;

                    /**
                     * Creates a new lookup engine that looks up a field name within the class hierarchy of the
                     * instrumented type.
                     *
                     * @param fieldName The name of the field to be found.
                     */
                    protected ForHierarchy(String fieldName) {
                        this.fieldName = fieldName;
                    }

                    @Override
                    protected Resolution resolve(TypeDescription instrumentedType) {
                        TypeDescription currentType = instrumentedType;
                        do {
                            FieldList fieldList = currentType.getDeclaredFields().filter(named(fieldName).and(isVisibleTo(instrumentedType)));
                            if (fieldList.size() == 1) {
                                return new Resolution.Resolved(fieldList.getOnly());
                            }
                        } while ((currentType = currentType.getSupertype()) != null);
                        return new Resolution.Unresolved();
                    }

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

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

                    @Override
                    public String toString() {
                        return "Field.Binder.FieldLocator.LookupEngine.ForHierarchy{" +
                                "fieldName='" + fieldName + '\'' +
                                '}';
                    }
                }

                /**
                 * Represents a lookup engine that tries to find a field for a given type.
                 */
                protected static class ForExplicitType extends LookupEngine {

                    /**
                     * The name of the field.
                     */
                    private final String fieldName;

                    /**
                     * The type which is supposed to define a field with the given field name.
                     */
                    private final TypeDescription typeDescription;

                    /**
                     * Creates a new lookup engine for a given type.
                     *
                     * @param fieldName       The name of the field to be found.
                     * @param typeDescription The type which is supposed to define a field with the given field name.
                     */
                    protected ForExplicitType(String fieldName, TypeDescription typeDescription) {
                        this.fieldName = fieldName;
                        this.typeDescription = typeDescription;
                    }

                    @Override
                    protected Resolution resolve(TypeDescription instrumentedType) {
                        FieldList fieldList = typeDescription.getDeclaredFields().filter(named(fieldName).and(isVisibleTo(instrumentedType)));
                        return fieldList.size() == 1
                                ? new Resolution.Resolved(fieldList.getOnly())
                                : new Resolution.Unresolved();
                    }

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

                    @Override
                    public int hashCode() {
                        int result = fieldName.hashCode();
                        result = 31 * result + typeDescription.hashCode();
                        return result;
                    }

                    @Override
                    public String toString() {
                        return "Field.Binder.FieldLocator.LookupEngine.ForExplicitType{" +
                                "fieldName='" + fieldName + '\'' +
                                ", typeDescription=" + typeDescription +
                                '}';
                    }
                }
            }

            /**
             * Represents a field locator for a field whos name could be located.
             */
            protected static class Legal extends FieldLocator {

                /**
                 * The name of the field.
                 */
                private final String fieldName;

                /**
                 * Creates a new field locator for a legal field name.
                 *
                 * @param fieldName The name of the field.
                 */
                protected Legal(String fieldName) {
                    this.fieldName = fieldName;
                }

                /**
                 * Considers a given method to expose a field name by following the Java bean naming conventions
                 * for getter and setter methods.
                 *
                 * @param methodDescription The method to consider for such a field name identification.
                 * @return A correspoding field name locator.
                 */
                protected static FieldLocator consider(MethodDescription methodDescription) {
                    String fieldName;
                    if (isSetter().matches(methodDescription)) {
                        fieldName = methodDescription.getInternalName().substring(3);
                    } else if (isGetter().matches(methodDescription)) {
                        fieldName = methodDescription.getInternalName()
                                .substring(methodDescription.getInternalName().startsWith("is") ? 2 : 3);
                    } else {
                        return new Illegal();
                    }
                    return new Legal(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1));
                }

                @Override
                protected LookupEngine lookup(TypeDescription typeDescription, TypeDescription instrumentedType) {
                    return typeDescription.represents(void.class)
                            ? new LookupEngine.ForHierarchy(fieldName)
                            : new LookupEngine.ForExplicitType(fieldName,
                            typeDescription.represents(TargetType.class) ? instrumentedType : typeDescription);
                }

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

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

                @Override
                public String toString() {
                    return "Field.Binder.FieldLocator.Legal{" +
                            "fieldName='" + fieldName + '\'' +
                            '}';
                }
            }

            /**
             * Represents an illegal field locator which can impossible locate a field.
             */
            protected static class Illegal extends FieldLocator {

                @Override
                protected LookupEngine lookup(TypeDescription typeDescription, TypeDescription instrumentedType) {
                    return new LookupEngine.Illegal();
                }

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

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

                @Override
                public String toString() {
                    return "Field.Binder.FieldLocator.Illegal{}";
                }
            }
        }

        /**
         * A proxy type for accessing a field either by a getter or a setter.
         */
        protected class AccessorProxy implements AuxiliaryType, StackManipulation {

            /**
             * The name of the field that stores the accessed instance if any.
             */
            protected static final String FIELD_NAME = "instance";

            /**
             * The field that is being accessed.
             */
            private final FieldDescription accessedField;

            /**
             * The type which is accessed.
             */
            private final TypeDescription instrumentedType;

            /**
             * The assigner to use.
             */
            private final Assigner assigner;

            /**
             * The access type to implement.
             */
            private final AccessType accessType;

            /**
             * {@code true} if the generated proxy should be serializable.
             */
            private final boolean serializableProxy;

            /**
             * @param accessedField     The field that is being accessed.
             * @param assigner          The assigner to use.
             * @param instrumentedType  The type which is accessed.
             * @param accessType        The assigner to use.
             * @param serializableProxy {@code true} if the generated proxy should be serializable.
             */
            protected AccessorProxy(FieldDescription accessedField,
                                    Assigner assigner,
                                    TypeDescription instrumentedType,
                                    AccessType accessType,
                                    boolean serializableProxy) {
                this.accessedField = accessedField;
                this.assigner = assigner;
                this.instrumentedType = instrumentedType;
                this.accessType = accessType;
                this.serializableProxy = serializableProxy;
            }

            @Override
            public DynamicType make(String auxiliaryTypeName,
                                    ClassFileVersion classFileVersion,
                                    MethodAccessorFactory methodAccessorFactory) {
                return new ByteBuddy(classFileVersion)
                        .subclass(accessType.proxyType(getterMethod, setterMethod), ConstructorStrategy.Default.NO_CONSTRUCTORS)
                        .name(auxiliaryTypeName)
                        .modifiers(DEFAULT_TYPE_MODIFIER)
                        .implement(serializableProxy ? new Class[]{Serializable.class} : new Class[0])
                        .defineConstructor(accessedField.isStatic()
                                ? Collections.emptyList()
                                : Collections.singletonList(instrumentedType))
                        .intercept(accessedField.isStatic()
                                ? StaticFieldConstructor.INSTANCE
                                : new InstanceFieldConstructor(instrumentedType))
                        .method(isDeclaredBy(accessType.proxyType(getterMethod, setterMethod)))
                        .intercept(accessType.access(accessedField, assigner, methodAccessorFactory))
                        .make();
            }

            @Override
            public boolean isValid() {
                return true;
            }

            @Override
            public Size apply(MethodVisitor methodVisitor, Instrumentation.Context instrumentationContext) {
                TypeDescription auxiliaryType = instrumentationContext.register(this);
                return new Compound(
                        TypeCreation.forType(auxiliaryType),
                        Duplication.SINGLE,
                        accessedField.isStatic()
                                ? LegalTrivial.INSTANCE
                                : MethodVariableAccess.REFERENCE.loadFromIndex(0),
                        MethodInvocation.invoke(auxiliaryType.getDeclaredMethods().filter(isConstructor()).getOnly())
                ).apply(methodVisitor, instrumentationContext);
            }

            /**
             * Returns the outer instance.
             *
             * @return The outer instance.
             */
            private Binder getOuter() {
                return Binder.this;
            }

            @Override
            public boolean equals(Object other) {
                if (this == other) return true;
                if (other == null || getClass() != other.getClass()) return false;
                AccessorProxy that = (AccessorProxy) other;
                return serializableProxy == that.serializableProxy
                        && accessType == that.accessType
                        && accessedField.equals(that.accessedField)
                        && assigner.equals(that.assigner)
                        && Binder.this.equals(that.getOuter())
                        && instrumentedType.equals(that.instrumentedType);
            }

            @Override
            public int hashCode() {
                int result = accessedField.hashCode();
                result = 31 * result + instrumentedType.hashCode();
                result = 31 * result + assigner.hashCode();
                result = 31 * result + Binder.this.hashCode();
                result = 31 * result + accessType.hashCode();
                result = 31 * result + (serializableProxy ? 1 : 0);
                return result;
            }

            @Override
            public String toString() {
                return "Field.Binder.AccessorProxy{" +
                        "accessedField=" + accessedField +
                        ", instrumentedType=" + instrumentedType +
                        ", assigner=" + assigner +
                        ", accessType=" + accessType +
                        ", serializableProxy=" + serializableProxy +
                        ", binder=" + Binder.this +
                        '}';
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy