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

org.testifyproject.bytebuddy.dynamic.Transformer Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package org.testifyproject.bytebuddy.dynamic;

import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.description.annotation.AnnotationList;
import org.testifyproject.bytebuddy.description.annotation.AnnotationValue;
import org.testifyproject.bytebuddy.description.field.FieldDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.ParameterDescription;
import org.testifyproject.bytebuddy.description.method.ParameterList;
import org.testifyproject.bytebuddy.description.modifier.ModifierContributor;
import org.testifyproject.bytebuddy.description.type.TypeDefinition;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.testifyproject.bytebuddy.matcher.ElementMatchers.named;
import static org.testifyproject.bytebuddy.matcher.ElementMatchers.none;

/**
 * A transformer is responsible for transforming an object into a compatible instance of the same type.
 *
 * @param  The type of the instance being transformed.
 */
public interface Transformer {

    /**
     * Transforms the supplied target.
     *
     * @param instrumentedType The instrumented type that declares the target being transformed.
     * @param target           The target entity that is being transformed.
     * @return The transformed instance.
     */
    T transform(TypeDescription instrumentedType, T target);

    /**
     * A non-operational transformer that returns the received instance.
     */
    enum NoOp implements Transformer {

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

        /**
         * Creates a transformer in a type-safe manner.
         *
         * @param  The type of the transformed object.
         * @return A non-operational transformer.
         */
        @SuppressWarnings("unchecked")
        public static  Transformer make() {
            return (Transformer) INSTANCE;
        }

        @Override
        public Object transform(TypeDescription instrumentedType, Object target) {
            return target;
        }
    }

    /**
     * A transformer for a field that delegates to another transformer that transforms a {@link org.testifyproject.bytebuddy.description.field.FieldDescription.Token}.
     */
    @EqualsAndHashCode
    class ForField implements Transformer {

        /**
         * The token transformer to apply to a transformed field.
         */
        private final Transformer transformer;

        /**
         * Creates a new simple field transformer.
         *
         * @param transformer The token transformer to apply to a transformed field.
         */
        public ForField(Transformer transformer) {
            this.transformer = transformer;
        }

        /**
         * Creates a field transformer that patches the transformed field by the givien modifier contributors.
         *
         * @param modifierContributor The modifier contributors to apply.
         * @return A suitable field transformer.
         */
        public static Transformer withModifiers(ModifierContributor.ForField... modifierContributor) {
            return withModifiers(Arrays.asList(modifierContributor));
        }

        /**
         * Creates a field transformer that patches the transformed field by the givien modifier contributors.
         *
         * @param modifierContributors The modifier contributors to apply.
         * @return A suitable field transformer.
         */
        public static Transformer withModifiers(List modifierContributors) {
            return new ForField(new FieldModifierTransformer(ModifierContributor.Resolver.of(modifierContributors)));
        }

        @Override
        public FieldDescription transform(TypeDescription instrumentedType, FieldDescription fieldDescription) {
            return new TransformedField(instrumentedType,
                    fieldDescription.getDeclaringType(),
                    transformer.transform(instrumentedType, fieldDescription.asToken(none())),
                    fieldDescription.asDefined());
        }

        /**
         * A transformer for a field's modifiers.
         */
        @EqualsAndHashCode
        protected static class FieldModifierTransformer implements Transformer {

            /**
             * The resolver to apply for transforming the modifiers of a field.
             */
            private final ModifierContributor.Resolver resolver;

            /**
             * Creates a new field token modifier for transforming a field's modifiers.
             *
             * @param resolver The resolver to apply for transforming the modifiers of a field.
             */
            protected FieldModifierTransformer(ModifierContributor.Resolver resolver) {
                this.resolver = resolver;
            }

            @Override
            public FieldDescription.Token transform(TypeDescription instrumentedType, FieldDescription.Token target) {
                return new FieldDescription.Token(target.getName(),
                        resolver.resolve(target.getModifiers()),
                        target.getType(),
                        target.getAnnotations());
            }
        }

        /**
         * An implementation of a transformed field.
         */
        protected static class TransformedField extends FieldDescription.AbstractBase {

            /**
             * The instrumented type for which this field is transformed.
             */
            private final TypeDescription instrumentedType;

            /**
             * The field's declaring type.
             */
            private final TypeDefinition declaringType;

            /**
             * A field token representing the transformed field.
             */
            private final FieldDescription.Token token;

            /**
             * The field's defined shape.
             */
            private final FieldDescription.InDefinedShape fieldDescription;

            /**
             * Creates a new transformed field.
             *
             * @param instrumentedType The instrumented type for which this field is transformed.
             * @param declaringType    The field's declaring type.
             * @param token            A field token representing the transformed field.
             * @param fieldDescription The field's defined shape.
             */
            protected TransformedField(TypeDescription instrumentedType,
                                       TypeDefinition declaringType,
                                       Token token,
                                       InDefinedShape fieldDescription) {
                this.instrumentedType = instrumentedType;
                this.declaringType = declaringType;
                this.token = token;
                this.fieldDescription = fieldDescription;
            }

            @Override
            public TypeDescription.Generic getType() {
                return token.getType().accept(TypeDescription.Generic.Visitor.Substitutor.ForAttachment.of(instrumentedType));
            }

            @Override
            public AnnotationList getDeclaredAnnotations() {
                return token.getAnnotations();
            }

            @Override
            public TypeDefinition getDeclaringType() {
                return declaringType;
            }

            @Override
            public int getModifiers() {
                return token.getModifiers();
            }

            @Override
            public InDefinedShape asDefined() {
                return fieldDescription;
            }

            @Override
            public String getName() {
                return token.getName();
            }
        }
    }

    /**
     * A transformer for a field that delegates to another transformer that transforms a {@link org.testifyproject.bytebuddy.description.method.MethodDescription.Token}.
     */
    @EqualsAndHashCode
    class ForMethod implements Transformer {

        /**
         * The transformer to be applied.
         */
        private final Transformer transformer;

        /**
         * Creates a new transforming method transformer.
         *
         * @param transformer The transformer to be applied.
         */
        public ForMethod(Transformer transformer) {
            this.transformer = transformer;
        }

        /**
         * Creates a transformer that enforces the supplied modifier contributors. All ranges of each contributor is first cleared and then overridden
         * by the specified modifiers in the order they are supplied.
         *
         * @param modifierContributor The modifier transformers in their application order.
         * @return A method transformer where each method's modifiers are adapted to the given modifiers.
         */
        public static Transformer withModifiers(ModifierContributor.ForMethod... modifierContributor) {
            return withModifiers(Arrays.asList(modifierContributor));
        }

        /**
         * Creates a transformer that enforces the supplied modifier contributors. All ranges of each contributor is first cleared and then overridden
         * by the specified modifiers in the order they are supplied.
         *
         * @param modifierContributors The modifier contributors in their application order.
         * @return A method transformer where each method's modifiers are adapted to the given modifiers.
         */
        public static Transformer withModifiers(List modifierContributors) {
            return new ForMethod(new MethodModifierTransformer(ModifierContributor.Resolver.of(modifierContributors)));
        }

        @Override
        public MethodDescription transform(TypeDescription instrumentedType, MethodDescription methodDescription) {
            return new TransformedMethod(instrumentedType,
                    methodDescription.getDeclaringType(),
                    transformer.transform(instrumentedType, methodDescription.asToken(none())),
                    methodDescription.asDefined());
        }

        /**
         * A transformer for a method's modifiers.
         */
        @EqualsAndHashCode
        protected static class MethodModifierTransformer implements Transformer {

            /**
             * The resolver to apply onto the method's modifiers.
             */
            private final ModifierContributor.Resolver resolver;

            /**
             * Creates a new modifier transformation.
             *
             * @param resolver The resolver to apply onto the method's modifiers.
             */
            protected MethodModifierTransformer(ModifierContributor.Resolver resolver) {
                this.resolver = resolver;
            }

            @Override
            public MethodDescription.Token transform(TypeDescription instrumentedType, MethodDescription.Token target) {
                return new MethodDescription.Token(target.getName(),
                        resolver.resolve(target.getModifiers()),
                        target.getTypeVariableTokens(),
                        target.getReturnType(),
                        target.getParameterTokens(),
                        target.getExceptionTypes(),
                        target.getAnnotations(),
                        target.getDefaultValue(),
                        target.getReceiverType());
            }
        }

        /**
         * The transformed method.
         */
        protected static class TransformedMethod extends MethodDescription.AbstractBase {

            /**
             * The instrumented type for which this method is transformed.
             */
            private final TypeDescription instrumentedType;

            /**
             * The method's declaring type.
             */
            private final TypeDefinition declaringType;

            /**
             * The method representing the transformed method.
             */
            private final MethodDescription.Token token;

            /**
             * The defined shape of the transformed method.
             */
            private final MethodDescription.InDefinedShape methodDescription;

            /**
             * Creates a new transformed method.
             *
             * @param instrumentedType  The instrumented type for which this method is transformed.
             * @param declaringType     The method's declaring type.
             * @param token             The method representing the transformed method.
             * @param methodDescription The defined shape of the transformed method.
             */
            protected TransformedMethod(TypeDescription instrumentedType,
                                        TypeDefinition declaringType,
                                        Token token,
                                        InDefinedShape methodDescription) {
                this.instrumentedType = instrumentedType;
                this.declaringType = declaringType;
                this.token = token;
                this.methodDescription = methodDescription;
            }

            @Override
            public TypeList.Generic getTypeVariables() {
                return new TypeList.Generic.ForDetachedTypes.OfTypeVariables(this, token.getTypeVariableTokens(), new AttachmentVisitor());
            }

            @Override
            public TypeDescription.Generic getReturnType() {
                return token.getReturnType().accept(new AttachmentVisitor());
            }

            @Override
            public ParameterList getParameters() {
                return new TransformedParameterList();
            }

            @Override
            public TypeList.Generic getExceptionTypes() {
                return new TypeList.Generic.ForDetachedTypes(token.getExceptionTypes(), new AttachmentVisitor());
            }

            @Override
            public AnnotationList getDeclaredAnnotations() {
                return token.getAnnotations();
            }

            @Override
            public String getInternalName() {
                return token.getName();
            }

            @Override
            public TypeDefinition getDeclaringType() {
                return declaringType;
            }

            @Override
            public int getModifiers() {
                return token.getModifiers();
            }

            @Override
            public AnnotationValue getDefaultValue() {
                return token.getDefaultValue();
            }

            @Override
            public InDefinedShape asDefined() {
                return methodDescription;
            }

            @Override
            public TypeDescription.Generic getReceiverType() {
                TypeDescription.Generic receiverType = token.getReceiverType();
                return receiverType == null
                        ? TypeDescription.Generic.UNDEFINED
                        : receiverType.accept(new AttachmentVisitor());
            }

            /**
             * A parameter list representing the transformed method's parameters.
             */
            protected class TransformedParameterList extends ParameterList.AbstractBase {

                @Override
                public ParameterDescription get(int index) {
                    return new TransformedParameter(index, token.getParameterTokens().get(index));
                }

                @Override
                public int size() {
                    return token.getParameterTokens().size();
                }
            }

            /**
             * A transformed method's parameter.
             */
            protected class TransformedParameter extends ParameterDescription.AbstractBase {

                /**
                 * The index of the transformed method.
                 */
                private final int index;

                /**
                 * The token representing the transformed method parameter's properties.
                 */
                private final ParameterDescription.Token parameterToken;

                /**
                 * Creates a transformed parameter.
                 *
                 * @param index          The index of the transformed method.
                 * @param parameterToken The token representing the transformed method parameter's properties.
                 */
                protected TransformedParameter(int index, ParameterDescription.Token parameterToken) {
                    this.index = index;
                    this.parameterToken = parameterToken;
                }

                @Override
                public TypeDescription.Generic getType() {
                    return parameterToken.getType().accept(new AttachmentVisitor());
                }

                @Override
                public MethodDescription getDeclaringMethod() {
                    return TransformedMethod.this;
                }

                @Override
                public int getIndex() {
                    return index;
                }

                @Override
                public boolean isNamed() {
                    return parameterToken.getName() != null;
                }

                @Override
                public boolean hasModifiers() {
                    return parameterToken.getModifiers() != null;
                }

                @Override
                public String getName() {
                    return isNamed()
                            ? parameterToken.getName()
                            : super.getName();
                }

                @Override
                public int getModifiers() {
                    return hasModifiers()
                            ? parameterToken.getModifiers()
                            : super.getModifiers();
                }

                @Override
                public AnnotationList getDeclaredAnnotations() {
                    return parameterToken.getAnnotations();
                }

                @Override
                public InDefinedShape asDefined() {
                    return methodDescription.getParameters().get(index);
                }
            }

            /**
             * A visitor that attaches type variables based on the transformed method's type variables and the instrumented type. Binding type
             * variables directly for this method is not possible as type variables are already resolved for the instrumented type such
             * that it is required to bind variables for the instrumented type directly.
             */
            protected class AttachmentVisitor extends TypeDescription.Generic.Visitor.Substitutor.WithoutTypeSubstitution {

                @Override
                public TypeDescription.Generic onTypeVariable(TypeDescription.Generic typeVariable) {
                    TypeList.Generic candidates = getTypeVariables().filter(named(typeVariable.getSymbol()));
                    TypeDescription.Generic attached = candidates.isEmpty()
                            ? instrumentedType.findVariable(typeVariable.getSymbol())
                            : candidates.getOnly();
                    if (attached == null) {
                        throw new IllegalArgumentException("Cannot attach undefined variable: " + typeVariable);
                    } else {
                        return new TypeDescription.Generic.OfTypeVariable.WithAnnotationOverlay(attached, typeVariable);
                    }
                }

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

                @Override
                public boolean equals(Object other) {
                    return this == other || (other instanceof AttachmentVisitor && ((AttachmentVisitor) other).getOuter().equals(TransformedMethod.this));
                }

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

    /**
     * A compound transformer.
     *
     * @param  The type of the transformed instance.
     */
    @EqualsAndHashCode
    class Compound implements Transformer {

        /**
         * The list of transformers to apply in their application order.
         */
        private final List> transformers;

        /**
         * Creates a new compound transformer.
         *
         * @param transformer The list of transformers to apply in their application order.
         */
        @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6
        public Compound(Transformer... transformer) {
            this(Arrays.asList(transformer));
        }

        /**
         * Creates a new compound transformer.
         *
         * @param transformers The list of transformers to apply in their application order.
         */
        public Compound(List> transformers) {
            this.transformers = new ArrayList>();
            for (Transformer transformer : transformers) {
                if (transformer instanceof Compound) {
                    this.transformers.addAll(((Compound) transformer).transformers);
                } else if (!(transformer instanceof NoOp)) {
                    this.transformers.add(transformer);
                }
            }
        }

        @Override
        public S transform(TypeDescription instrumentedType, S target) {
            for (Transformer transformer : transformers) {
                target = transformer.transform(instrumentedType, target);
            }
            return target;
        }
    }
}