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

com.ui4j.bytebuddy.instrumentation.ExceptionMethod Maven / Gradle / Ivy

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

import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
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.Throw;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.TypeCreation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.constant.TextConstant;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodInvocation;
import com.ui4j.bytebuddy.instrumentation.type.InstrumentedType;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;

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

/**
 * This instrumentation causes a {@link java.lang.Throwable} to be thrown when the instrumented method is invoked.
 * Be aware that the Java Virtual machine does not care about exception declarations and will throw any
 * {@link java.lang.Throwable} from any method even if the method does not declared a checked exception.
 */
public class ExceptionMethod implements Instrumentation, ByteCodeAppender {

    /**
     * The type of the exception that is thrown.
     */
    private final TypeDescription throwableType;

    /**
     * The construction delegation which is responsible for creating the exception to be thrown.
     */
    private final ConstructionDelegate constructionDelegate;

    /**
     * Creates a new instance of an instrumentation for throwing throwables.
     *
     * @param throwableType        The type of the exception to be thrown.
     * @param constructionDelegate A delegate that is responsible for calling the isThrowable's constructor.
     */
    public ExceptionMethod(TypeDescription throwableType,
                           ConstructionDelegate constructionDelegate) {
        this.throwableType = throwableType;
        this.constructionDelegate = constructionDelegate;
    }

    /**
     * Creates an instrumentation that creates a new instance of the given isThrowable type on each method invocation
     * which is then thrown immediately. For this to be possible, the given type must define a default constructor
     * which is visible from the instrumented type.
     *
     * @param throwable The type of the isThrowable.
     * @return An instrumentation that will throw an instance of the isThrowable on each method invocation of the
     * instrumented methods.
     */
    public static Instrumentation throwing(Class throwable) {
        return throwing(new TypeDescription.ForLoadedType(nonNull(throwable)));
    }

    /**
     * Creates an instrumentation that creates a new instance of the given isThrowable type on each method invocation
     * which is then thrown immediately. For this to be possible, the given type must define a default constructor
     * which is visible from the instrumented type.
     *
     * @param throwable The type of the isThrowable.
     * @return An instrumentation that will throw an instance of the isThrowable on each method invocation of the
     * instrumented methods.
     */
    public static Instrumentation throwing(TypeDescription throwable) {
        if (!throwable.isAssignableTo(Throwable.class)) {
            throw new IllegalArgumentException(throwable + " does not extend throwable");
        }
        return new ExceptionMethod(nonNull(throwable), new ConstructionDelegate.ForDefaultConstructor(throwable));
    }

    /**
     * Creates an instrumentation that creates a new instance of the given isThrowable type on each method invocation
     * which is then thrown immediately. For this to be possible, the given type must define a constructor that
     * takes a single {@link java.lang.String} as its argument.
     *
     * @param exceptionType The type of the isThrowable.
     * @param message       The string that is handed to the constructor. Usually an exception message.
     * @return An instrumentation that will throw an instance of the isThrowable on each method invocation of the
     * instrumented methods.
     */
    public static Instrumentation throwing(Class exceptionType, String message) {
        return throwing(new TypeDescription.ForLoadedType(nonNull(exceptionType)), message);
    }

    /**
     * Creates an instrumentation that creates a new instance of the given isThrowable type on each method invocation
     * which is then thrown immediately. For this to be possible, the given type must define a constructor that
     * takes a single {@link java.lang.String} as its argument.
     *
     * @param exceptionType The type of the isThrowable.
     * @param message       The string that is handed to the constructor. Usually an exception message.
     * @return An instrumentation that will throw an instance of the isThrowable on each method invocation of the
     * instrumented methods.
     */
    public static Instrumentation throwing(TypeDescription exceptionType, String message) {
        if (!exceptionType.isAssignableTo(Throwable.class)) {
            throw new IllegalArgumentException(exceptionType + " does not extend throwable");
        }
        return new ExceptionMethod(nonNull(exceptionType), new ConstructionDelegate.ForStringConstructor(exceptionType, nonNull(message)));
    }

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

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

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

    @Override
    public Size apply(MethodVisitor methodVisitor,
                      Context instrumentationContext,
                      MethodDescription instrumentedMethod) {
        StackManipulation.Size stackSize = new StackManipulation.Compound(
                constructionDelegate.make(),
                Throw.INSTANCE
        ).apply(methodVisitor, instrumentationContext);
        return new Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize());
    }

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

    @Override
    public int hashCode() {
        return 31 * throwableType.hashCode() + constructionDelegate.hashCode();
    }

    @Override
    public String toString() {
        return "ExceptionMethod{" +
                "throwableType=" + throwableType +
                ", constructionDelegate=" + constructionDelegate +
                '}';
    }

    /**
     * A construction delegate is responsible for calling a isThrowable's constructor.
     */
    public static interface ConstructionDelegate {

        /**
         * Creates a stack manipulation that creates pushes all constructor arguments onto the operand stack
         * and subsequently calls the constructor.
         *
         * @return A stack manipulation for constructing a isThrowable.
         */
        StackManipulation make();

        /**
         * A construction delegate that calls the default constructor.
         */
        static class ForDefaultConstructor implements ConstructionDelegate {

            /**
             * The type of the exception that is to be thrown.
             */
            private final TypeDescription exceptionType;

            /**
             * The constructor that is used for creating the exception.
             */
            private final MethodDescription targetConstructor;

            /**
             * Creates a new construction delegate that calls a default constructor.
             *
             * @param exceptionType The type of the isThrowable.
             */
            public ForDefaultConstructor(TypeDescription exceptionType) {
                this.exceptionType = exceptionType;
                this.targetConstructor = exceptionType.getDeclaredMethods()
                        .filter(isConstructor().and(takesArguments(0))).getOnly();
            }

            @Override
            public StackManipulation make() {
                return new StackManipulation.Compound(
                        TypeCreation.forType(exceptionType),
                        Duplication.SINGLE,
                        MethodInvocation.invoke(targetConstructor));
            }

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

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

            @Override
            public String toString() {
                return "ExceptionMethod.ConstructionDelegate.ForDefaultConstructor{" +
                        "exceptionType=" + exceptionType +
                        ", targetConstructor=" + targetConstructor +
                        '}';
            }
        }

        /**
         * A construction delegate that calls a constructor that takes a single string as its argument.
         */
        static class ForStringConstructor implements ConstructionDelegate {

            /**
             * The type of the exception that is to be thrown.
             */
            private final TypeDescription exceptionType;

            /**
             * The constructor that is used for creating the exception.
             */
            private final MethodDescription targetConstructor;

            /**
             * The {@link java.lang.String} that is to be passed to the exception's constructor.
             */
            private final String message;

            /**
             * Creates a new construction delegate that calls a constructor by handing it the given string.
             *
             * @param exceptionType The type of the isThrowable.
             * @param message       The string that is handed to the constructor.
             */
            public ForStringConstructor(TypeDescription exceptionType, String message) {
                this.exceptionType = exceptionType;
                this.targetConstructor = exceptionType.getDeclaredMethods()
                        .filter(isConstructor().and(takesArguments(String.class))).getOnly();
                this.message = message;
            }

            @Override
            public StackManipulation make() {
                return new StackManipulation.Compound(
                        TypeCreation.forType(exceptionType),
                        Duplication.SINGLE,
                        new TextConstant(message),
                        MethodInvocation.invoke(targetConstructor));
            }

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

            @Override
            public int hashCode() {
                return 31 * exceptionType.hashCode() + message.hashCode();
            }

            @Override
            public String toString() {
                return "ExceptionMethod.ConstructionDelegate.ForStringConstructor{" +
                        "exceptionType=" + exceptionType +
                        ", targetConstructor=" + targetConstructor +
                        ", message='" + message + '\'' +
                        '}';
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy