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

net.bytebuddy.build.Plugin Maven / Gradle / Ivy

Go to download

Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with all ASM dependencies repackaged into its own name space.

There is a newer version: 1.15.11
Show newest version
package net.bytebuddy.build;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;

import java.io.*;
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;

import static net.bytebuddy.matcher.ElementMatchers.none;

/**
 * A plugin that allows for the application of Byte Buddy transformations during a build process. This plugin's
 * transformation is applied to any type matching this plugin's type matcher. Plugin types must be public,
 * non-abstract and must declare a public default constructor to work.
 */
public interface Plugin extends ElementMatcher, Closeable {

    /**
     * Applies this plugin.
     *
     * @param builder          The builder to use as a basis for the applied transformation.
     * @param typeDescription  The type being transformed.
     * @param classFileLocator A class file locator that can locate other types in the scope of the project.
     * @return The supplied builder with additional transformations registered.
     */
    DynamicType.Builder apply(DynamicType.Builder builder, TypeDescription typeDescription, ClassFileLocator classFileLocator);

    /**
     * A factory for providing a build plugin.
     */
    interface Factory {

        /**
         * Returns a plugin that can be used for a transformation and which is subsequently closed.
         *
         * @return The plugin to use for type transformations.
         */
        Plugin make();

        /**
         * A simple factory that returns a preconstructed plugin instance..
         */
        @HashCodeAndEqualsPlugin.Enhance
        class Simple implements Factory {

            /**
             * The plugin to provide.
             */
            private final Plugin plugin;

            /**
             * Creates a simple plugin factory.
             *
             * @param plugin The plugin to provide.
             */
            public Simple(Plugin plugin) {
                this.plugin = plugin;
            }

            /**
             * {@inheritDoc}
             */
            public Plugin make() {
                return plugin;
            }
        }

        /**
         * A plugin factory that uses reflection for instantiating a plugin.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class UsingReflection implements Factory {

            /**
             * The plugin type.
             */
            private final Class type;

            /**
             * A list of argument providers that can be used for instantiating the plugin.
             */
            private final List argumentResolvers;

            /**
             * Creates a plugin factory that uses reflection for creating a plugin.
             *
             * @param type The plugin type.
             */
            public UsingReflection(Class type) {
                this(type, Collections.emptyList());
            }

            /**
             * Creates a plugin factory that uses reflection for creating a plugin.
             *
             * @param type              The plugin type.
             * @param argumentResolvers A list of argument providers that can be used for instantiating the plugin.
             */
            protected UsingReflection(Class type, List argumentResolvers) {
                this.type = type;
                this.argumentResolvers = argumentResolvers;
            }

            /**
             * Appends the supplied argument resolvers.
             *
             * @param argumentResolver A list of argument providers that can be used for instantiating the plugin.
             * @return A new plugin factory that uses reflection for creating a plugin that also uses the supplied argument resolvers.
             */
            public UsingReflection with(ArgumentResolver... argumentResolver) {
                return with(Arrays.asList(argumentResolver));
            }

            /**
             * Appends the supplied argument resolvers.
             *
             * @param argumentResolvers A list of argument providers that can be used for instantiating the plugin.
             * @return A new plugin factory that uses reflection for creating a plugin that also uses the supplied argument resolvers.
             */
            public UsingReflection with(List argumentResolvers) {
                return new UsingReflection(type, CompoundList.of(argumentResolvers, this.argumentResolvers));
            }

            /**
             * {@inheritDoc}
             */
            @SuppressWarnings("unchecked")
            public Plugin make() {
                Instantiator instantiator = new Instantiator.Unresolved(type);
                candidates:
                for (Constructor constructor : type.getConstructors()) {
                    if (!constructor.isSynthetic()) {
                        List arguments = new ArrayList(constructor.getParameterTypes().length);
                        int index = 0;
                        for (Class type : constructor.getParameterTypes()) {
                            boolean resolved = false;
                            for (ArgumentResolver argumentResolver : argumentResolvers) {
                                ArgumentResolver.Resolution resolution = argumentResolver.resolve(index, type);
                                if (resolution.isResolved()) {
                                    arguments.add(resolution.getArgument());
                                    resolved = true;
                                    break;
                                }
                            }
                            if (resolved) {
                                index += 1;
                            } else {
                                break candidates;
                            }
                        }
                        instantiator = instantiator.replaceBy(new Instantiator.Resolved((Constructor) constructor, arguments));
                    }
                }
                return instantiator.instantiate();
            }

            /**
             * An instantiator is responsible for invoking a plugin constructor reflectively.
             */
            protected interface Instantiator {

                /**
                 * Returns either this instantiator or the supplied instantiator, depending on the instances' states.
                 *
                 * @param instantiator The alternative instantiator.
                 * @return The dominant instantiator.
                 */
                Instantiator replaceBy(Resolved instantiator);

                /**
                 * Instantiates the represented plugin.
                 *
                 * @return The instantiated plugin.
                 */
                Plugin instantiate();

                /**
                 * An instantiator that is not resolved for creating an instance.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class Unresolved implements Instantiator {

                    /**
                     * The type for which no constructor was yet resolved.
                     */
                    private final Class type;

                    /**
                     * Creates a new unresolved constructor.
                     *
                     * @param type The type for which no constructor was yet resolved.
                     */
                    protected Unresolved(Class type) {
                        this.type = type;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Instantiator replaceBy(Resolved instantiator) {
                        return instantiator;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Plugin instantiate() {
                        throw new IllegalStateException("No constructor available for " + type);
                    }
                }

                /**
                 * An instantiator that is resolved for a given constructor with arguments.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class Resolved implements Instantiator {

                    /**
                     * The represented constructor.
                     */
                    private final Constructor constructor;

                    /**
                     * The constructor arguments.
                     */
                    private final List arguments;

                    /**
                     * Creates a new resolved constructor.
                     *
                     * @param constructor The represented constructor.
                     * @param arguments   The constructor arguments.
                     */
                    protected Resolved(Constructor constructor, List arguments) {
                        this.constructor = constructor;
                        this.arguments = arguments;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Instantiator replaceBy(Resolved instantiator) {
                        Priority left = constructor.getAnnotation(Priority.class), right = instantiator.constructor.getAnnotation(Priority.class);
                        int leftPriority = left == null ? Priority.DEFAULT : left.value(), rightPriority = right == null ? Priority.DEFAULT : right.value();
                        if (leftPriority > rightPriority) {
                            return this;
                        } else if (leftPriority < rightPriority) {
                            return instantiator;
                        } else {
                            throw new IllegalStateException("Ambiguous constructors " + constructor + " and " + instantiator.constructor);
                        }
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Plugin instantiate() {
                        try {
                            return constructor.newInstance(arguments.toArray(new Object[arguments.size()]));
                        } catch (InstantiationException exception) {
                            throw new IllegalStateException("Failed to instantiate plugin via " + constructor, exception);
                        } catch (IllegalAccessException exception) {
                            throw new IllegalStateException("Failed to access " + constructor, exception);
                        } catch (InvocationTargetException exception) {
                            throw new IllegalStateException("Error during construction of" + constructor, exception.getCause());
                        }
                    }
                }
            }

            /**
             * Indicates that a constructor should be treated with a given priority if several constructors can be resolved.
             */
            @Documented
            @Target(ElementType.CONSTRUCTOR)
            @Retention(RetentionPolicy.RUNTIME)
            public @interface Priority {

                /**
                 * The default priority that is assumed for non-annotated constructors.
                 */
                int DEFAULT = 0;

                /**
                 * Indicates the priority of the annotated constructor.
                 *
                 * @return The priority of the annotated constructor.
                 */
                int value();
            }

            /**
             * Allows to resolve arguments for a {@link Plugin} constructor.
             */
            public interface ArgumentResolver {

                /**
                 * Attempts the resolution of an argument for a given parameter.
                 *
                 * @param index The parameter's index.
                 * @param type  The parameter's type.
                 * @return The resolution for the parameter.
                 */
                Resolution resolve(int index, Class type);

                /**
                 * A resolution provided by an argument provider.
                 */
                interface Resolution {

                    /**
                     * Returns {@code true} if the represented argument is resolved successfully.
                     *
                     * @return {@code true} if the represented argument is resolved successfully.
                     */
                    boolean isResolved();

                    /**
                     * Returns the resolved argument if the resolution was successful.
                     *
                     * @return The resolved argument if the resolution was successful.
                     */
                    Object getArgument();

                    /**
                     * Represents an unresolved argument resolution.
                     */
                    enum Unresolved implements Resolution {

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

                        /**
                         * {@inheritDoc}
                         */
                        public boolean isResolved() {
                            return false;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        public Object getArgument() {
                            throw new IllegalStateException("Cannot get the argument for an unresolved parameter");
                        }
                    }

                    /**
                     * Represents a resolved argument resolution.
                     */
                    @HashCodeAndEqualsPlugin.Enhance
                    class Resolved implements Resolution {

                        /**
                         * The resolved argument which might be {@code null}.
                         */
                        @HashCodeAndEqualsPlugin.ValueHandling(HashCodeAndEqualsPlugin.ValueHandling.Sort.REVERSE_NULLABILITY)
                        private final Object argument;

                        /**
                         * Creates a resolved argument resolution.
                         *
                         * @param argument The resolved argument which might be {@code null}.
                         */
                        public Resolved(Object argument) {
                            this.argument = argument;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        public boolean isResolved() {
                            return true;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        public Object getArgument() {
                            return argument;
                        }
                    }
                }

                /**
                 * An argument resolver that never resolves an argument.
                 */
                enum NoOp implements ArgumentResolver {

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

                    /**
                     * {@inheritDoc}
                     */
                    public Resolution resolve(int index, Class type) {
                        return Resolution.Unresolved.INSTANCE;
                    }
                }

                /**
                 * An argument resolver that resolves parameters for a given type.
                 *
                 * @param  The type being resolved.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class ForType implements ArgumentResolver {

                    /**
                     * The type being resolved.
                     */
                    private final Class type;

                    /**
                     * The instance to resolve for the represented type.
                     */
                    private final T value;

                    /**
                     * Creates a new argument resolver for a given type.
                     *
                     * @param type  The type being resolved.
                     * @param value The instance to resolve for the represented type.
                     */
                    protected ForType(Class type, T value) {
                        this.type = type;
                        this.value = value;
                    }

                    /**
                     * Creates an argument resolver for a given type.
                     *
                     * @param type  The type being resolved.
                     * @param value The instance to resolve for the represented type.
                     * @param    The type being resolved.
                     * @return An appropriate argument resolver.
                     */
                    public static  ArgumentResolver of(Class type, S value) {
                        return new ForType(type, value);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Resolution resolve(int index, Class type) {
                        return type == this.type
                                ? new Resolution.Resolved(value)
                                : Resolution.Unresolved.INSTANCE;
                    }
                }

                /**
                 * An argument resolver that resolves an argument for a specific parameter index.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class ForIndex implements ArgumentResolver {

                    /**
                     * A mapping of primitive types to their wrapper types.
                     */
                    private static final Map, Class> WRAPPER_TYPES;

                    /*
                     * Creates the primitive to wrapper type mapping.
                     */
                    static {
                        WRAPPER_TYPES = new HashMap, Class>();
                        WRAPPER_TYPES.put(boolean.class, Boolean.class);
                        WRAPPER_TYPES.put(byte.class, Byte.class);
                        WRAPPER_TYPES.put(short.class, Short.class);
                        WRAPPER_TYPES.put(char.class, Character.class);
                        WRAPPER_TYPES.put(int.class, Integer.class);
                        WRAPPER_TYPES.put(long.class, Long.class);
                        WRAPPER_TYPES.put(float.class, Float.class);
                        WRAPPER_TYPES.put(double.class, Double.class);
                    }

                    /**
                     * The index of the parameter to resolve.
                     */
                    private final int index;

                    /**
                     * The value to resolve for the represented index.
                     */
                    private final Object value;

                    /**
                     * Creates an argument resolver for a given index.
                     *
                     * @param index The index of the parameter to resolve.
                     * @param value The value to resolve for the represented index.
                     */
                    public ForIndex(int index, Object value) {
                        this.index = index;
                        this.value = value;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Resolution resolve(int index, Class type) {
                        if (this.index != index) {
                            return Resolution.Unresolved.INSTANCE;
                        } else if (type.isPrimitive()) {
                            return WRAPPER_TYPES.get(type).isInstance(value)
                                    ? new Resolution.Resolved(value)
                                    : Resolution.Unresolved.INSTANCE;
                        } else {
                            return value == null || type.isInstance(value)
                                    ? new Resolution.Resolved(value)
                                    : Resolution.Unresolved.INSTANCE;
                        }
                    }

                    /**
                     * An argument resolver that resolves an argument for a specific parameter index by attempting a conversion via
                     * invoking a static {@code valueOf} method on the target type, if it exists.
                     */
                    @HashCodeAndEqualsPlugin.Enhance
                    public static class WithDynamicType implements ArgumentResolver {

                        /**
                         * The index of the parameter to resolve.
                         */
                        private final int index;

                        /**
                         * A string representation of the supplied value.
                         */
                        private final String value;

                        /**
                         * Creates an argument resolver for a specific parameter index and attempts a dynamic resolution.
                         *
                         * @param index The index of the parameter to resolve.
                         * @param value A string representation of the supplied value.
                         */
                        public WithDynamicType(int index, String value) {
                            this.index = index;
                            this.value = value;
                        }

                        /**
                         * {@inheritDoc}
                         */
                        public Resolution resolve(int index, Class type) {
                            if (this.index != index) {
                                return Resolution.Unresolved.INSTANCE;
                            } else if (type.isPrimitive()) {
                                type = WRAPPER_TYPES.get(type);
                            } else if (type == String.class) {
                                return new Resolution.Resolved(value);
                            }
                            try {
                                Method valueOf = type.getMethod("valueOf", String.class);
                                return Modifier.isStatic(valueOf.getModifiers()) && type.isAssignableFrom(valueOf.getReturnType())
                                        ? new Resolution.Resolved(valueOf.invoke(null, value))
                                        : Resolution.Unresolved.INSTANCE;
                            } catch (IllegalAccessException exception) {
                                throw new IllegalStateException(exception);
                            } catch (InvocationTargetException exception) {
                                throw new IllegalStateException(exception.getCause());
                            } catch (NoSuchMethodException ignored) {
                                return Resolution.Unresolved.INSTANCE;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * A plugin engine allows the application of one or more plugins on class files found at a {@link Source} which are
     * then transferred and consumed by a {@link Target}.
     */
    interface Engine {

        /**
         * The class file extension.
         */
        String CLASS_FILE_EXTENSION = ".class";

        /**
         * The standard location of a Java manifest file.
         */
        String MANIFEST_LOCATION = "META-INF/MANIFEST.MF";

        /**
         * Defines a new type strategy which determines the transformation mode for any instrumented type.
         *
         * @param typeStrategy The type stategy to use.
         * @return A new plugin engine that is equal to this engine but uses the supplied type strategy.
         */
        Engine with(TypeStrategy typeStrategy);

        /**
         * Defines a new pool strategy that determines how types are being described.
         *
         * @param poolStrategy The pool strategy to use.
         * @return A new plugin engine that is equal to this engine but uses the supplied pool strategy.
         */
        Engine with(PoolStrategy poolStrategy);

        /**
         * Appends the supplied class file locator to be queried for class files additionally to any previously registered
         * class file locators.
         *
         * @param classFileLocator The class file locator to append.
         * @return A new plugin engine that is equal to this engine but with the supplied class file locator being appended.
         */
        Engine with(ClassFileLocator classFileLocator);

        /**
         * Appends the supplied listener to this engine.
         *
         * @param listener The listener to append.
         * @return A new plugin engine that is equal to this engine but with the supplied listener being appended.
         */
        Engine with(Listener listener);

        /**
         * Replaces the error handlers of this plugin engine without applying any error handlers.
         *
         * @return A new plugin engine that is equal to this engine but without any error handlers being registered.
         */
        Engine withoutErrorHandlers();

        /**
         * Replaces the error handlers of this plugin engine with the supplied error handlers.
         *
         * @param errorHandler The error handlers to apply.
         * @return A new plugin engine that is equal to this engine but with only the supplied error handlers being applied.
         */
        Engine withErrorHandlers(ErrorHandler... errorHandler);

        /**
         * Replaces the error handlers of this plugin engine with the supplied error handlers.
         *
         * @param errorHandlers The error handlers to apply.
         * @return A new plugin engine that is equal to this engine but with only the supplied error handlers being applied.
         */
        Engine withErrorHandlers(List errorHandlers);

        /**
         * Ignores all types that are matched by this matcher or any previously registered ignore matcher.
         *
         * @param matcher The ignore matcher to append.
         * @return A new plugin engine that is equal to this engine but which ignores any type that is matched by the supplied matcher.
         */
        Engine ignore(ElementMatcher matcher);

        /**
         * Applies this plugin engine onto a given source and target.
         *
         * @param source  The source which is treated as a folder or a jar file, if a folder does not exist.
         * @param target  The target which is treated as a folder or a jar file, if a folder does not exist.
         * @param factory A list of plugin factories to a apply.
         * @return A summary of the applied transformation.
         * @throws IOException If an I/O error occurs.
         */
        Summary apply(File source, File target, Plugin.Factory... factory) throws IOException;

        /**
         * Applies this plugin engine onto a given source and target.
         *
         * @param source    The source which is treated as a folder or a jar file, if a folder does not exist.
         * @param target    The target which is treated as a folder or a jar file, if a folder does not exist.
         * @param factories A list of plugin factories to a apply.
         * @return A summary of the applied transformation.
         * @throws IOException If an I/O error occurs.
         */
        Summary apply(File source, File target, List factories) throws IOException;

        /**
         * Applies this plugin engine onto a given source and target.
         *
         * @param source  The source to use.
         * @param target  The target to use.
         * @param factory A list of plugin factories to a apply.
         * @return A summary of the applied transformation.
         * @throws IOException If an I/O error occurs.
         */
        Summary apply(Source source, Target target, Plugin.Factory... factory) throws IOException;

        /**
         * Applies this plugin engine onto a given source and target.
         *
         * @param source    The source to use.
         * @param target    The target to use.
         * @param factories A list of plugin factories to a apply.
         * @return A summary of the applied transformation.
         * @throws IOException If an I/O error occurs.
         */
        Summary apply(Source source, Target target, List factories) throws IOException;

        /**
         * A type strategy determines the transformation that is applied to a type description.
         */
        interface TypeStrategy {

            /**
             * Creates a builder for a given type.
             *
             * @param byteBuddy        The Byte Buddy instance to use.
             * @param typeDescription  The type being transformed.
             * @param classFileLocator A class file locator for finding the type's class file.
             * @return A dynamic type builder for the provided type.
             */
            DynamicType.Builder builder(ByteBuddy byteBuddy, TypeDescription typeDescription, ClassFileLocator classFileLocator);

            /**
             * Default implementations for type strategies.
             */
            enum Default implements TypeStrategy {

                /**
                 * A type strategy that redefines a type's methods.
                 */
                REDEFINE {
                    /**
                     * {@inheritDoc}
                     */
                    public DynamicType.Builder builder(ByteBuddy byteBuddy, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
                        return byteBuddy.redefine(typeDescription, classFileLocator);
                    }
                },

                /**
                 * A type strategy that rebases a type's methods.
                 */
                REBASE {
                    /**
                     * {@inheritDoc}
                     */
                    public DynamicType.Builder builder(ByteBuddy byteBuddy, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
                        return byteBuddy.rebase(typeDescription, classFileLocator);
                    }
                },

                /**
                 * A type strategy that decorates a type.
                 */
                DECORATE {
                    /**
                     * {@inheritDoc}
                     */
                    public DynamicType.Builder builder(ByteBuddy byteBuddy, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
                        return byteBuddy.decorate(typeDescription, classFileLocator);
                    }
                }
            }

            /**
             * A type strategy that represents a given {@link EntryPoint} for a build tool.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForEntryPoint implements TypeStrategy {

                /**
                 * The represented entry point.
                 */
                private final EntryPoint entryPoint;

                /**
                 * A method name transformer to use for rebasements.
                 */
                private final MethodNameTransformer methodNameTransformer;

                /**
                 * Creates a new type stratrgy for an entry point.
                 *
                 * @param entryPoint            The represented entry point.
                 * @param methodNameTransformer A method name transformer to use for rebasements.
                 */
                public ForEntryPoint(EntryPoint entryPoint, MethodNameTransformer methodNameTransformer) {
                    this.entryPoint = entryPoint;
                    this.methodNameTransformer = methodNameTransformer;
                }

                /**
                 * {@inheritDoc}
                 */
                public DynamicType.Builder builder(ByteBuddy byteBuddy, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
                    return entryPoint.transform(typeDescription, byteBuddy, classFileLocator, methodNameTransformer);
                }
            }
        }

        /**
         * A pool strategy determines the creation of a {@link TypePool} for a plugin engine application.
         */
        interface PoolStrategy {

            /**
             * Creates a type pool.
             *
             * @param classFileLocator The class file locator to use.
             * @return An approptiate type pool.
             */
            TypePool typePool(ClassFileLocator classFileLocator);

            /**
             * A default implementation of a pool strategy where type descriptions are resolved lazily.
             */
            enum Default implements PoolStrategy {

                /**
                 * Enables faster class file parsing that does not process debug information of a class file.
                 */
                FAST(TypePool.Default.ReaderMode.FAST),

                /**
                 * Enables extended class file parsing that extracts parameter names from debug information, if available.
                 */
                EXTENDED(TypePool.Default.ReaderMode.EXTENDED);

                /**
                 * This strategy's reader mode.
                 */
                private final TypePool.Default.ReaderMode readerMode;

                /**
                 * Creates a default pool strategy.
                 *
                 * @param readerMode This strategy's reader mode.
                 */
                Default(TypePool.Default.ReaderMode readerMode) {
                    this.readerMode = readerMode;
                }

                /**
                 * {@inheritDoc}
                 */
                public TypePool typePool(ClassFileLocator classFileLocator) {
                    return new TypePool.Default.WithLazyResolution(new TypePool.CacheProvider.Simple(),
                            classFileLocator,
                            readerMode,
                            TypePool.ClassLoading.ofPlatformLoader());
                }
            }

            /**
             * A pool strategy that resolves type descriptions eagerly. This can avoid additional overhead if the
             * majority of types is assumed to be resolved eventually.
             */
            enum Eager implements PoolStrategy {

                /**
                 * Enables faster class file parsing that does not process debug information of a class file.
                 */
                FAST(TypePool.Default.ReaderMode.FAST),

                /**
                 * Enables extended class file parsing that extracts parameter names from debug information, if available.
                 */
                EXTENDED(TypePool.Default.ReaderMode.EXTENDED);

                /**
                 * This strategy's reader mode.
                 */
                private final TypePool.Default.ReaderMode readerMode;

                /**
                 * Creates an eager pool strategy.
                 *
                 * @param readerMode This strategy's reader mode.
                 */
                Eager(TypePool.Default.ReaderMode readerMode) {
                    this.readerMode = readerMode;
                }

                /**
                 * {@inheritDoc}
                 */
                public TypePool typePool(ClassFileLocator classFileLocator) {
                    return new TypePool.Default(new TypePool.CacheProvider.Simple(),
                            classFileLocator,
                            readerMode,
                            TypePool.ClassLoading.ofPlatformLoader());
                }
            }
        }

        /**
         * An error handler that is used during a plugin engine application.
         */
        interface ErrorHandler {

            /**
             * Invoked if an error occured during a plugin's application on a given type.
             *
             * @param typeDescription The type being matched or transformed.
             * @param plugin          The plugin being applied.
             * @param throwable       The throwable that caused the error.
             */
            void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable);

            /**
             * Invoked after the application of all plugins was attempted if at least one error occured during handling a given type.
             *
             * @param typeDescription The type being transformed.
             * @param throwables      The throwables that caused errors during the application.
             */
            void onError(TypeDescription typeDescription, List throwables);

            /**
             * Invoked at the end of the build if at least one type transformation failed.
             *
             * @param throwables A mapping of types that failed during transformation to the errors that were caught.
             */
            void onError(Map> throwables);

            /**
             * Invoked at the end of the build if a plugin could not be closed.
             *
             * @param plugin    The plugin that could not be closed.
             * @param throwable The error that was caused when the plugin was attempted to be closed.
             */
            void onError(Plugin plugin, Throwable throwable);

            /**
             * Invoked if a type transformation implied a live initializer.
             *
             * @param typeDescription The type that was transformed.
             * @param definingType    The type that implies the initializer which might be the type itself or an auxiliary type.
             */
            void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType);

            /**
             * Invoked if a type could not be resolved.
             *
             * @param typeName The name of the unresolved type.
             */
            void onUnresolved(String typeName);

            /**
             * An implementation of an error handler that fails the plugin engine application.
             */
            enum Failing implements ErrorHandler {

                /**
                 * An error handler that fails the build immediatly on the first error.
                 */
                FAIL_FAST {
                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                        throw new IllegalStateException("Failed to transform " + typeDescription + " using " + plugin, throwable);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, List throwables) {
                        throw new UnsupportedOperationException("onError");
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(Map> throwables) {
                        throw new UnsupportedOperationException("onError");
                    }
                },

                /**
                 * An error handler that fails the build after applying all plugins if at least one plugin failed.
                 */
                FAIL_AFTER_TYPE {
                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                        /* do nothing */
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, List throwables) {
                        throw new IllegalStateException("Failed to transform " + typeDescription + ": " + throwables);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(Map> throwables) {
                        throw new UnsupportedOperationException("onError");
                    }
                },

                /**
                 * An error handler that fails the build after transforming all types if at least one plugin failed.
                 */
                FAIL_LAST {
                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                        /* do nothing */
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(TypeDescription typeDescription, List throwables) {
                        /* do nothing */
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void onError(Map> throwables) {
                        throw new IllegalStateException("Failed to transform at least one type: " + throwables);
                    }
                };

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    throw new IllegalStateException("Failed to close plugin " + plugin, throwable);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    /* do nothing */
                }
            }

            /**
             * An error handler that enforces certain properties of the transformation.
             */
            enum Enforcing implements ErrorHandler {

                /**
                 * Enforces that all types could be resolved.
                 */
                ALL_TYPES_RESOLVED {
                    @Override
                    public void onUnresolved(String typeName) {
                        throw new IllegalStateException("Failed to resolve type description for " + typeName);
                    }
                },

                /**
                 * Enforces that no type has a live initializer.
                 */
                NO_LIVE_INITIALIZERS {
                    @Override
                    public void onLiveInitializer(TypeDescription typeDescription, TypeDescription initializedType) {
                        throw new IllegalStateException("Failed to instrument " + typeDescription + " due to live initializer for " + initializedType);
                    }
                };

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, List throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Map> throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    /* do nothing */
                }
            }

            /**
             * A compound error handler.
             */
            class Compound implements ErrorHandler {

                /**
                 * The error handlers that are represented by this instance.
                 */
                private final List errorHandlers;

                /**
                 * Creates a new compound error handler.
                 *
                 * @param errorHandler The error handlers that are represented by this instance.
                 */
                public Compound(ErrorHandler... errorHandler) {
                    this(Arrays.asList(errorHandler));
                }

                /**
                 * Creates a new compound error handler.
                 *
                 * @param errorHandlers The error handlers that are represented by this instance.
                 */
                public Compound(List errorHandlers) {
                    this.errorHandlers = new ArrayList();
                    for (ErrorHandler errorHandler : errorHandlers) {
                        if (errorHandler instanceof Compound) {
                            this.errorHandlers.addAll(((Compound) errorHandler).errorHandlers);
                        } else if (!(errorHandler instanceof Listener.NoOp)) {
                            this.errorHandlers.add(errorHandler);
                        }
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onError(typeDescription, plugin, throwable);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, List throwables) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onError(typeDescription, throwables);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Map> throwables) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onError(throwables);
                    }

                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onError(plugin, throwable);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onLiveInitializer(typeDescription, definingType);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    for (ErrorHandler errorHandler : errorHandlers) {
                        errorHandler.onUnresolved(typeName);
                    }
                }
            }
        }

        /**
         * A listener that is invoked upon any event during a plugin engine application.
         */
        interface Listener extends ErrorHandler {

            /**
             * Invoked upon discovering a type but prior to its resolution.
             *
             * @param typeName The name of the discovered type.
             */
            void onDiscovery(String typeName);

            /**
             * Invoked after a type was transformed using a specific plugin.
             *
             * @param typeDescription The type being transformed.
             * @param plugin          The plugin that was applied.
             */
            void onTransformation(TypeDescription typeDescription, Plugin plugin);

            /**
             * Invoked after a type was transformed using at least one plugin.
             *
             * @param typeDescription The type being transformed.
             * @param plugins         A list of plugins that were applied.
             */
            void onTransformation(TypeDescription typeDescription, List plugins);

            /**
             * Invoked if a type description is ignored by a given plugin. This callback is not invoked,
             * if the ignore type matcher excluded a type from transformation.
             *
             * @param typeDescription The type being transformed.
             * @param plugin          The plugin that ignored the given type.
             */
            void onIgnored(TypeDescription typeDescription, Plugin plugin);

            /**
             * Invoked if one or more plugins did not transform a type. This callback is also invoked if an
             * ignore matcher excluded a type from transformation.
             *
             * @param typeDescription The type being transformed.
             * @param plugins         the plugins that ignored the type.
             */
            void onIgnored(TypeDescription typeDescription, List plugins);

            /**
             * Invoked upon completing handling a type that was either transformed or ignored.
             *
             * @param typeDescription The type that was transformed.
             */
            void onComplete(TypeDescription typeDescription);

            /**
             * Invoked upon discovering a non-class file.
             *
             * @param name The name of the resource.
             */
            void onResource(String name);

            /**
             * A non-operational listener.
             */
            enum NoOp implements Listener {

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

                /**
                 * {@inheritDoc}
                 */
                public void onDiscovery(String typeName) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, Plugin plugin) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, List plugins) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, Plugin plugin) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, List plugins) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, List throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Map> throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onComplete(TypeDescription typeDescription) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onResource(String name) {
                    /* do nothing */
                }
            }

            /**
             * An adapter that implements all methods non-operational.
             */
            abstract class Adapter implements Listener {

                /**
                 * {@inheritDoc}
                 */
                public void onDiscovery(String typeName) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, Plugin plugin) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, List plugins) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, Plugin plugin) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, List plugins) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, List throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Map> throwables) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onComplete(TypeDescription typeDescription) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void onResource(String name) {
                    /* do nothing */
                }
            }

            /**
             * A listener that forwards significant events of a plugin engine application to a {@link PrintStream}.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class StreamWriting extends Adapter {

                /**
                 * The prefix that is appended to all written messages.
                 */
                protected static final String PREFIX = "[Byte Buddy]";

                /**
                 * The print stream to delegate to.
                 */
                private final PrintStream printStream;

                /**
                 * Creates a new stream writing listener.
                 *
                 * @param printStream The print stream to delegate to.
                 */
                public StreamWriting(PrintStream printStream) {
                    this.printStream = printStream;
                }

                /**
                 * Creates a stream writing listener that prints all events on {@link System#out}.
                 *
                 * @return A listener that writes events to the system output stream.
                 */
                public static StreamWriting toSystemOut() {
                    return new StreamWriting(System.out);
                }

                /**
                 * Creates a stream writing listener that prints all events on {@link System#err}.
                 *
                 * @return A listener that writes events to the system error stream.
                 */
                public static StreamWriting toSystemError() {
                    return new StreamWriting(System.err);
                }

                /**
                 * Returns a new listener that only prints transformation and error events.
                 *
                 * @return A new listener that only prints transformation and error events.
                 */
                public Listener withTransformationsOnly() {
                    return new WithTransformationsOnly(this);
                }

                /**
                 * Returns a new listener that only prints error events.
                 *
                 * @return A new listener that only prints error events.
                 */
                public Listener withErrorsOnly() {
                    return new WithErrorsOnly(this);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onDiscovery(String typeName) {
                    printStream.printf(PREFIX + " DISCOVERY %s", typeName);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, Plugin plugin) {
                    printStream.printf(PREFIX + " TRANSFORM %s for %s", typeDescription, plugin);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, Plugin plugin) {
                    printStream.printf(PREFIX + " IGNORE %s for %s", typeDescription, plugin);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    synchronized (printStream) {
                        printStream.printf(PREFIX + " ERROR %s for %s", typeDescription, plugin);
                        throwable.printStackTrace(printStream);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    synchronized (printStream) {
                        printStream.printf(PREFIX + " ERROR %s", plugin);
                        throwable.printStackTrace(printStream);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    printStream.printf(PREFIX + " UNRESOLVED %s", typeName);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    printStream.printf(PREFIX + " LIVE %s on %s", typeDescription, definingType);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onComplete(TypeDescription typeDescription) {
                    printStream.printf(PREFIX + " COMPLETE %s", typeDescription);
                }

                /**
                 * {@inheritDoc}
                 */
                public void onResource(String name) {
                    printStream.printf(PREFIX + " RESOURCE %s", name);
                }
            }

            /**
             * A decorator for another listener to only print transformation and error events.
             */
            class WithTransformationsOnly extends Adapter {

                /**
                 * The delegate to forward events to.
                 */
                private final Listener delegate;

                /**
                 * Creates a new listener decorator that filter any event that is not related to transformation or errors.
                 *
                 * @param delegate The delegate to forward events to.
                 */
                public WithTransformationsOnly(Listener delegate) {
                    this.delegate = delegate;
                }

                @Override
                public void onTransformation(TypeDescription typeDescription, Plugin plugin) {
                    delegate.onTransformation(typeDescription, plugin);
                }

                @Override
                public void onTransformation(TypeDescription typeDescription, List plugins) {
                    delegate.onTransformation(typeDescription, plugins);
                }

                @Override
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    delegate.onError(typeDescription, plugin, throwable);
                }

                @Override
                public void onError(TypeDescription typeDescription, List throwables) {
                    delegate.onError(typeDescription, throwables);
                }

                @Override
                public void onError(Map> throwables) {
                    delegate.onError(throwables);
                }

                @Override
                public void onError(Plugin plugin, Throwable throwable) {
                    delegate.onError(plugin, throwable);
                }
            }

            /**
             * A decorator for another listener to only print error events.
             */
            class WithErrorsOnly extends Adapter {

                /**
                 * The delegate to forward events to.
                 */
                private final Listener delegate;

                /**
                 * Creates a new listener decorator that filter any event that is not related to errors.
                 *
                 * @param delegate The delegate to forward events to.
                 */
                public WithErrorsOnly(Listener delegate) {
                    this.delegate = delegate;
                }

                @Override
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    delegate.onError(typeDescription, plugin, throwable);
                }

                @Override
                public void onError(TypeDescription typeDescription, List throwables) {
                    delegate.onError(typeDescription, throwables);
                }

                @Override
                public void onError(Map> throwables) {
                    delegate.onError(throwables);
                }

                @Override
                public void onError(Plugin plugin, Throwable throwable) {
                    delegate.onError(plugin, throwable);
                }
            }

            /**
             * A listener decorator that forwards events to an error handler if they are applicable.
             */
            class ForErrorHandler extends Adapter {

                /**
                 * The error handler to delegate to.
                 */
                private final ErrorHandler errorHandler;

                /**
                 * Creates a new listener representation for an error handler.
                 *
                 * @param errorHandler The error handler to delegate to.
                 */
                public ForErrorHandler(ErrorHandler errorHandler) {
                    this.errorHandler = errorHandler;
                }

                @Override
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    errorHandler.onError(typeDescription, plugin, throwable);
                }

                @Override
                public void onError(TypeDescription typeDescription, List throwables) {
                    errorHandler.onError(typeDescription, throwables);
                }

                @Override
                public void onError(Map> throwables) {
                    errorHandler.onError(throwables);
                }

                @Override
                public void onError(Plugin plugin, Throwable throwable) {
                    errorHandler.onError(plugin, throwable);
                }

                @Override
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    errorHandler.onLiveInitializer(typeDescription, definingType);
                }

                @Override
                public void onUnresolved(String typeName) {
                    errorHandler.onUnresolved(typeName);
                }
            }

            /**
             * A compound listener.
             */
            class Compound implements Listener {

                /**
                 * A list of listeners that are represented by this compound instance.
                 */
                private final List listeners;

                /**
                 * Creates a new compound listener.
                 *
                 * @param listener A list of listeners that are represented by this compound instance.
                 */
                public Compound(Listener... listener) {
                    this(Arrays.asList(listener));
                }

                /**
                 * Creates a new compound listener.
                 *
                 * @param listeners A list of listeners that are represented by this compound instance.
                 */
                public Compound(List listeners) {
                    this.listeners = new ArrayList();
                    for (Listener listener : listeners) {
                        if (listener instanceof Listener.Compound) {
                            this.listeners.addAll(((Listener.Compound) listener).listeners);
                        } else if (!(listener instanceof NoOp)) {
                            this.listeners.add(listener);
                        }
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onDiscovery(String typeName) {
                    for (Listener listener : listeners) {
                        listener.onDiscovery(typeName);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, Plugin plugin) {
                    for (Listener listener : listeners) {
                        listener.onTransformation(typeDescription, plugin);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onTransformation(TypeDescription typeDescription, List plugins) {
                    for (Listener listener : listeners) {
                        listener.onTransformation(typeDescription, plugins);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, Plugin plugin) {
                    for (Listener listener : listeners) {
                        listener.onIgnored(typeDescription, plugin);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onIgnored(TypeDescription typeDescription, List plugins) {
                    for (Listener listener : listeners) {
                        listener.onIgnored(typeDescription, plugins);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, Plugin plugin, Throwable throwable) {
                    for (Listener listener : listeners) {
                        listener.onError(typeDescription, plugin, throwable);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(TypeDescription typeDescription, List throwables) {
                    for (Listener listener : listeners) {
                        listener.onError(typeDescription, throwables);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Map> throwables) {
                    for (Listener listener : listeners) {
                        listener.onError(throwables);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onError(Plugin plugin, Throwable throwable) {
                    for (Listener listener : listeners) {
                        listener.onError(plugin, throwable);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onLiveInitializer(TypeDescription typeDescription, TypeDescription definingType) {
                    for (Listener listener : listeners) {
                        listener.onLiveInitializer(typeDescription, definingType);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onComplete(TypeDescription typeDescription) {
                    for (Listener listener : listeners) {
                        listener.onComplete(typeDescription);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onUnresolved(String typeName) {
                    for (Listener listener : listeners) {
                        listener.onUnresolved(typeName);
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void onResource(String name) {
                    for (Listener listener : listeners) {
                        listener.onResource(name);
                    }
                }
            }
        }

        /**
         * A source for a plugin engine provides binary elements to consider for transformation.
         */
        interface Source extends Iterable {

            /**
             * Indicates that no manifest exists.
             */
            Manifest NO_MANIFEST = null;

            /**
             * Returns a class file locator for the represented source.
             *
             * @return A class file locator for locating class files of this instance.
             */
            ClassFileLocator getClassFileLocator();

            /**
             * Returns the manifest file of the source location or {@code null} if no manifest exists.
             *
             * @return This source's manifest or {@code null}.
             * @throws IOException If an I/O error occurs.
             */
            Manifest getManifest() throws IOException;

            /**
             * Represents a binary element found in a source location.
             */
            interface Element {

                /**
                 * Returns the element's relative path and name.
                 *
                 * @return The element's path and name.
                 */
                String getName();

                /**
                 * Returns an input stream to read this element's binary information.
                 *
                 * @return An input stream that represents this element's binary information.
                 * @throws IOException If an I/O error occurs.
                 */
                InputStream getInputStream() throws IOException;

                /**
                 * Resolves this element to a more specialized form if possible. Doing so allows for performance
                 * optimizations if more specialized formats are available.
                 *
                 * @param type The requested spezialized type.
                 * @param   The requested spezialized type.
                 * @return The resolved element or {@code null} if a transformation is impossible.
                 */
                 T resolveAs(Class type);

                /**
                 * An element representation for a byte array.
                 */
                @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Not mutating the byte array is part of the class contract.")
                @HashCodeAndEqualsPlugin.Enhance
                class ForByteArray implements Element {

                    /**
                     * The element's name.
                     */
                    private final String name;

                    /**
                     * The element's binary representation.
                     */
                    private final byte[] binaryRepresentation;

                    /**
                     * Creates an element that is represented by a byte array.
                     *
                     * @param name                 The element's name.
                     * @param binaryRepresentation The element's binary representation.
                     */
                    public ForByteArray(String name, byte[] binaryRepresentation) {
                        this.name = name;
                        this.binaryRepresentation = binaryRepresentation;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public String getName() {
                        return name;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public InputStream getInputStream() {
                        return new ByteArrayInputStream(binaryRepresentation);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public  T resolveAs(Class type) {
                        return null;
                    }
                }

                /**
                 * An element representation for a file.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class ForFile implements Element {

                    /**
                     * The root folder of the represented source.
                     */
                    private final File root;

                    /**
                     * The file location of the represented file that is located within the root directory.
                     */
                    private final File file;

                    /**
                     * Creates an element representation for a file.
                     *
                     * @param root The root folder of the represented source.
                     * @param file The file location of the represented file that is located within the root directory.
                     */
                    public ForFile(File root, File file) {
                        this.root = root;
                        this.file = file;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public String getName() {
                        return root.getAbsoluteFile().toURI().relativize(file.getAbsoluteFile().toURI()).getPath();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public InputStream getInputStream() throws IOException {
                        return new FileInputStream(file);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    @SuppressWarnings("unchecked")
                    public  T resolveAs(Class type) {
                        return File.class.isAssignableFrom(type)
                                ? (T) file
                                : null;
                    }
                }

                /**
                 * Represents a jar file entry as an element.
                 */
                @HashCodeAndEqualsPlugin.Enhance
                class ForJarEntry implements Element {

                    /**
                     * The source's underlying jar file.
                     */
                    private final JarFile file;

                    /**
                     * The entry that is represented by this element.
                     */
                    private final JarEntry entry;

                    /**
                     * Creates a new element representation for a jar file entry.
                     *
                     * @param file  The source's underlying jar file.
                     * @param entry The entry that is represented by this element.
                     */
                    public ForJarEntry(JarFile file, JarEntry entry) {
                        this.file = file;
                        this.entry = entry;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public String getName() {
                        return entry.getName();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public InputStream getInputStream() throws IOException {
                        return file.getInputStream(entry);
                    }

                    /**
                     * {@inheritDoc}
                     */
                    @SuppressWarnings("unchecked")
                    public  T resolveAs(Class type) {
                        return JarEntry.class.isAssignableFrom(type)
                                ? (T) entry
                                : null;
                    }
                }
            }

            /**
             * An empty source that does not contain any elements or a manifest.
             */
            enum Empty implements Source {

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

                /**
                 * {@inheritDoc}
                 */
                public ClassFileLocator getClassFileLocator() {
                    return ClassFileLocator.NoOp.INSTANCE;
                }

                /**
                 * {@inheritDoc}
                 */
                public Manifest getManifest() {
                    return NO_MANIFEST;
                }

                /**
                 * {@inheritDoc}
                 */
                public Iterator iterator() {
                    return Collections.emptySet().iterator();
                }
            }

            /**
             * A source that represents a collection of in-memory resources that are represented as byte arrays.
             */
            class InMemory implements Source {

                /**
                 * A mapping of resource names to their binary representation.
                 */
                private final Map storage;

                /**
                 * Creates a new in-memory source with a single entry.
                 *
                 * @param name                 The name of the only resource.
                 * @param binaryRepresentation The resource's binary representation.
                 */
                public InMemory(String name, byte[] binaryRepresentation) {
                    this(Collections.singletonMap(name, binaryRepresentation));
                }

                /**
                 * Creates a new in-memory source.
                 *
                 * @param storage A mapping of resource names to their binary representation.
                 */
                public InMemory(Map storage) {
                    this.storage = storage;
                }

                /**
                 * Represents a map of type names to their binary representation as an in-memory source.
                 *
                 * @param binaryRepresentations A mapping of type names to their binary representation.
                 * @return A source representing the supplied types.
                 */
                public static Source ofTypes(Map binaryRepresentations) {
                    Map storage = new HashMap();
                    for (Map.Entry entry : binaryRepresentations.entrySet()) {
                        storage.put(entry.getKey().replace('.', '/') + CLASS_FILE_EXTENSION, entry.getValue());
                    }
                    return new InMemory(storage);
                }

                /**
                 * {@inheritDoc}
                 */
                public ClassFileLocator getClassFileLocator() {
                    return new ClassFileLocator.Simple(storage);
                }

                /**
                 * {@inheritDoc}
                 */
                public Manifest getManifest() throws IOException {
                    byte[] binaryRepresentation = storage.get(MANIFEST_LOCATION);
                    if (binaryRepresentation == null) {
                        return NO_MANIFEST;
                    } else {
                        return new Manifest(new ByteArrayInputStream(binaryRepresentation));
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public Iterator iterator() {
                    return new MapEntryIterator(storage.entrySet().iterator());
                }

                /**
                 * An iterator that represents map entries as sources.
                 */
                protected static class MapEntryIterator implements Iterator {

                    /**
                     * The represented iterator.
                     */
                    private final Iterator> iterator;

                    /**
                     * Creates a new map entry iterator.
                     *
                     * @param iterator The represented iterator.
                     */
                    protected MapEntryIterator(Iterator> iterator) {
                        this.iterator = iterator;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public boolean hasNext() {
                        return iterator.hasNext();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Element next() {
                        Map.Entry entry = iterator.next();
                        return new Element.ForByteArray(entry.getKey(), entry.getValue());
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void remove() {
                        throw new UnsupportedOperationException("remove");
                    }
                }
            }

            /**
             * Represents the contents of a folder as class files.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForFolder implements Source {

                /**
                 * The folder to represent.
                 */
                private final File folder;

                /**
                 * Creates a new source representation for a given folder.
                 *
                 * @param folder The folder to represent.
                 */
                public ForFolder(File folder) {
                    this.folder = folder;
                }

                /**
                 * {@inheritDoc}
                 */
                public ClassFileLocator getClassFileLocator() {
                    return new ClassFileLocator.ForFolder(folder);
                }

                /**
                 * {@inheritDoc}
                 */
                public Manifest getManifest() throws IOException {
                    File file = new File(folder, MANIFEST_LOCATION);
                    if (file.exists()) {
                        InputStream inputStream = new FileInputStream(file);
                        try {
                            return new Manifest(inputStream);
                        } finally {
                            inputStream.close();
                        }
                    } else {
                        return NO_MANIFEST;
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public Iterator iterator() {
                    return new FolderIterator(folder);
                }

                /**
                 * An iterator that exposes all files within a folder structure as elements.
                 */
                protected class FolderIterator implements Iterator {

                    /**
                     * A list of files and folders to process.
                     */
                    private final LinkedList files;

                    /**
                     * Creates a new iterator representation for all files within a folder.
                     *
                     * @param folder The root folder.
                     */
                    protected FolderIterator(File folder) {
                        files = new LinkedList(Collections.singleton(folder));
                        File candidate;
                        do {
                            candidate = files.removeFirst();
                            File[] file = candidate.listFiles();
                            if (file != null) {
                                files.addAll(0, Arrays.asList(file));
                            }
                        } while (!files.isEmpty() && (files.peek().isDirectory() || files.peek().equals(new File(folder, MANIFEST_LOCATION))));
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public boolean hasNext() {
                        return !files.isEmpty();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    @SuppressFBWarnings(value = "IT_NO_SUCH_ELEMENT", justification = "Exception is thrown by invoking removeFirst on an empty list.")
                    public Element next() {
                        try {
                            return new Element.ForFile(folder, files.removeFirst());
                        } finally {
                            while (!files.isEmpty() && (files.peek().isDirectory() || files.peek().equals(new File(folder, MANIFEST_LOCATION)))) {
                                File folder = files.removeFirst();
                                File[] file = folder.listFiles();
                                if (file != null) {
                                    files.addAll(0, Arrays.asList(file));
                                }
                            }
                        }
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void remove() {
                        throw new UnsupportedOperationException("remove");
                    }
                }
            }

            /**
             * Represents a jar file as a source.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForJarFile implements Source {

                /**
                 * The jar file being represented by this source.
                 */
                private final JarFile file;

                /**
                 * Creates a new source for a jar file.
                 *
                 * @param file The jar file being represented by this source.
                 * @throws IOException If an I/O error occurs.
                 */
                public ForJarFile(File file) throws IOException {
                    this(new JarFile(file));
                }

                /**
                 * Creates a new source for a jar file.
                 *
                 * @param file The jar file being represented by this source.
                 */
                public ForJarFile(JarFile file) {
                    this.file = file;
                }

                /**
                 * {@inheritDoc}
                 */
                public ClassFileLocator getClassFileLocator() {
                    return new ClassFileLocator.ForJarFile(file);
                }

                /**
                 * {@inheritDoc}
                 */
                public Manifest getManifest() throws IOException {
                    return file.getManifest();
                }

                /**
                 * {@inheritDoc}
                 */
                public Iterator iterator() {
                    return new JarEntryIterator(file);
                }

                /**
                 * An iterator that represents all entries of a jar file.
                 */
                protected class JarEntryIterator implements Iterator {

                    /**
                     * An enumeration of all jar file entries.
                     */
                    private final Enumeration enumeration;

                    /**
                     * Creates a new jar entry iterator.
                     *
                     * @param file The jar file to represent.
                     */
                    protected JarEntryIterator(JarFile file) {
                        enumeration = file.entries();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public boolean hasNext() {
                        return enumeration.hasMoreElements();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public Element next() {
                        return new Element.ForJarEntry(file, enumeration.nextElement());
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void remove() {
                        throw new UnsupportedOperationException("remove");
                    }
                }
            }
        }

        /**
         * A target for a plugin engine represents a sink container for all elements that are supplied by a {@link Source}.
         */
        interface Target {

            /**
             * Initializes this target prior to writing without supplying a manifest.
             *
             * @return The sink to write to.
             * @throws IOException If an I/O error occurs.
             */
            Sink write() throws IOException;

            /**
             * Initializes this target prior to writing.
             *
             * @param manifest The manifest for the target.
             * @return The sink to write to.
             * @throws IOException If an I/O error occurs.
             */
            Sink write(Manifest manifest) throws IOException;

            /**
             * A sink represents an active writing process.
             */
            interface Sink extends Closeable {

                /**
                 * Stores the supplied binary representation of types in this sink.
                 *
                 * @param binaryRepresentations The binary representations to store.
                 * @throws IOException If an I/O error occurs.
                 */
                void store(Map binaryRepresentations) throws IOException;

                /**
                 * Retains the supplied element in its original form.
                 *
                 * @param element The element to retain.
                 * @throws IOException If an I/O error occurs.
                 */
                void retain(Source.Element element) throws IOException;

                /**
                 * Implements a sink for a jar file.
                 */
                class ForJarOutputStream implements Sink {

                    /**
                     * The output stream to write to.
                     */
                    private final JarOutputStream outputStream;

                    /**
                     * Creates a new sink for a jar file.
                     *
                     * @param outputStream The output stream to write to.
                     */
                    public ForJarOutputStream(JarOutputStream outputStream) {
                        this.outputStream = outputStream;
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void store(Map binaryRepresentations) throws IOException {
                        for (Map.Entry entry : binaryRepresentations.entrySet()) {
                            outputStream.putNextEntry(new JarEntry(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION));
                            outputStream.write(entry.getValue());
                            outputStream.closeEntry();
                        }
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void retain(Source.Element element) throws IOException {
                        JarEntry entry = element.resolveAs(JarEntry.class);
                        outputStream.putNextEntry(entry == null
                                ? new JarEntry(element.getName())
                                : entry);
                        InputStream inputStream = element.getInputStream();
                        try {
                            byte[] buffer = new byte[1024];
                            int length;
                            while ((length = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, length);
                            }
                        } finally {
                            inputStream.close();
                        }
                        outputStream.closeEntry();
                    }

                    /**
                     * {@inheritDoc}
                     */
                    public void close() throws IOException {
                        outputStream.close();
                    }
                }
            }

            /**
             * A sink that discards any entry.
             */
            enum Discarding implements Target, Sink {

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

                /**
                 * {@inheritDoc}
                 */
                public Sink write() {
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write(Manifest manifest) {
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public void store(Map binaryRepresentations) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void retain(Source.Element element) {
                    /* do nothing */
                }

                /**
                 * {@inheritDoc}
                 */
                public void close() {
                    /* do nothing */
                }
            }

            /**
             * A sink that stores all elements in a memory map.
             */
            class InMemory implements Target, Sink {

                /**
                 * The map for storing all elements being received.
                 */
                private final Map storage;

                /**
                 * Creates a new in-memory storage.
                 */
                public InMemory() {
                    this(new HashMap());
                }

                /**
                 * Creates a new in-memory storage.
                 *
                 * @param storage The map for storing all elements being received.
                 */
                public InMemory(Map storage) {
                    this.storage = storage;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write() {
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write(Manifest manifest) throws IOException {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    try {
                        manifest.write(outputStream);
                    } finally {
                        outputStream.close();
                    }
                    storage.put(MANIFEST_LOCATION, outputStream.toByteArray());
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public void store(Map binaryRepresentations) {
                    for (Map.Entry entry : binaryRepresentations.entrySet()) {
                        storage.put(entry.getKey().getInternalName() + CLASS_FILE_EXTENSION, entry.getValue());
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void retain(Source.Element element) throws IOException {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    try {
                        InputStream inputStream = element.getInputStream();
                        try {
                            byte[] buffer = new byte[1024];
                            int length;
                            while ((length = inputStream.read(buffer)) != -1) {
                                outputStream.write(buffer, 0, length);
                            }
                        } finally {
                            inputStream.close();
                        }
                    } finally {
                        outputStream.close();
                    }
                    storage.put(element.getName(), outputStream.toByteArray());
                }

                /**
                 * {@inheritDoc}
                 */
                public void close() {
                    /* do nothing */
                }

                /**
                 * Returns the in-memory storage.
                 *
                 * @return The in-memory storage.
                 */
                public Map getStorage() {
                    return storage;
                }

                /**
                 * Returns the in-memory storage as a type-map where all non-class files are discarded.
                 *
                 * @return The in-memory storage as a type map.
                 */
                public Map toTypeMap() {
                    Map binaryRepresentations = new HashMap();
                    for (Map.Entry entry : storage.entrySet()) {
                        if (entry.getKey().endsWith(CLASS_FILE_EXTENSION)) {
                            binaryRepresentations.put(entry.getKey()
                                    .substring(0, entry.getKey().length() - CLASS_FILE_EXTENSION.length())
                                    .replace('/', '.'), entry.getValue());
                        }
                    }
                    return binaryRepresentations;
                }
            }

            /**
             * Represents a folder as the target for a plugin engine's application.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForFolder implements Target, Sink {

                /**
                 * The folder that is represented by this instance.
                 */
                private final File folder;

                /**
                 * Creates a new target for a folder.
                 *
                 * @param folder The folder that is represented by this instance.
                 */
                public ForFolder(File folder) {
                    this.folder = folder;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write() {
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write(Manifest manifest) throws IOException {
                    File target = new File(folder, MANIFEST_LOCATION);
                    if (!target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
                        throw new IOException("Could not create directory: " + target.getParent());
                    }
                    OutputStream outputStream = new FileOutputStream(target);
                    try {
                        manifest.write(outputStream);
                    } finally {
                        outputStream.close();
                    }
                    return this;
                }

                /**
                 * {@inheritDoc}
                 */
                public void store(Map binaryRepresentations) throws IOException {
                    for (Map.Entry entry : binaryRepresentations.entrySet()) {
                        File target = new File(folder, entry.getKey().getInternalName() + CLASS_FILE_EXTENSION);
                        if (!target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
                            throw new IOException("Could not create directory: " + target.getParent());
                        }
                        OutputStream outputStream = new FileOutputStream(target);
                        try {
                            outputStream.write(entry.getValue());
                        } finally {
                            outputStream.close();
                        }
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void retain(Source.Element element) throws IOException {
                    File target = new File(folder, element.getName()), resolved = element.resolveAs(File.class);
                    if (!target.getAbsolutePath().startsWith(folder.getAbsolutePath())) {
                        throw new IllegalStateException(target + " is not a subdirectory of " + folder);
                    } else if (!target.getParentFile().isDirectory() && !target.getParentFile().mkdirs()) {
                        throw new IOException("Could not create directory: " + target.getParent());
                    } else if (resolved == null || !resolved.equals(target)) {
                        InputStream inputStream = element.getInputStream();
                        try {
                            OutputStream outputStream = new FileOutputStream(target);
                            try {
                                byte[] buffer = new byte[1024];
                                int length;
                                while ((length = inputStream.read(buffer)) != -1) {
                                    outputStream.write(buffer, 0, length);
                                }
                            } finally {
                                outputStream.close();
                            }
                        } finally {
                            inputStream.close();
                        }
                    }
                }

                /**
                 * {@inheritDoc}
                 */
                public void close() {
                    /* do nothing */
                }
            }

            /**
             * Represents a jar file as a target.
             */
            @HashCodeAndEqualsPlugin.Enhance
            class ForJarFile implements Target {

                /**
                 * The jar file that is represented by this target.
                 */
                private final File file;

                /**
                 * Creates a new target for a jar file.
                 *
                 * @param file The jar file that is represented by this target.
                 */
                public ForJarFile(File file) {
                    this.file = file;
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write() throws IOException {
                    return new Sink.ForJarOutputStream(new JarOutputStream(new FileOutputStream(file)));
                }

                /**
                 * {@inheritDoc}
                 */
                public Sink write(Manifest manifest) throws IOException {
                    return new Sink.ForJarOutputStream(new JarOutputStream(new FileOutputStream(file), manifest));
                }
            }
        }

        /**
         * A summary of the application of a {@link Engine} to a source and target.
         */
        class Summary {

            /**
             * A list of all types that were transformed.
             */
            private final List transformed;

            /**
             * A mapping of all types that failed during transformation to the exceptions that explain the failure.
             */
            private final Map> failed;

            /**
             * A list of type names that could not be resolved.
             */
            private final List unresolved;

            /**
             * Creates a new summary.
             *
             * @param transformed A list of all types that were transformed.
             * @param failed      A mapping of all types that failed during transformation to the exceptions that explain the failure.
             * @param unresolved  A list of type names that could not be resolved.
             */
            public Summary(List transformed, Map> failed, List unresolved) {
                this.transformed = transformed;
                this.failed = failed;
                this.unresolved = unresolved;
            }

            /**
             * Returns a list of all types that were transformed.
             *
             * @return A list of all types that were transformed.
             */
            public List getTransformed() {
                return transformed;
            }

            /**
             * Returns a mapping of all types that failed during transformation to the exceptions that explain the failure.
             *
             * @return A mapping of all types that failed during transformation to the exceptions that explain the failure.
             */
            public Map> getFailed() {
                return failed;
            }

            /**
             * Returns a list of type names that could not be resolved.
             *
             * @return A list of type names that could not be resolved.
             */
            public List getUnresolved() {
                return unresolved;
            }

            @Override
            public boolean equals(Object object) {
                if (this == object) {
                    return true;
                } else if (object == null || getClass() != object.getClass()) {
                    return false;
                }
                Summary summary = (Summary) object;
                if (!transformed.equals(summary.transformed)) {
                    return false;
                }
                if (!failed.equals(summary.failed)) {
                    return false;
                }
                return unresolved.equals(summary.unresolved);
            }

            @Override
            public int hashCode() {
                int result = transformed.hashCode();
                result = 31 * result + failed.hashCode();
                result = 31 * result + unresolved.hashCode();
                return result;
            }
        }

        /**
         * An abstract base implementation of a plugin engine.
         */
        abstract class AbstractBase implements Engine {

            /**
             * {@inheritDoc}
             */
            public Engine withErrorHandlers(ErrorHandler... errorHandler) {
                return withErrorHandlers(Arrays.asList(errorHandler));
            }

            /**
             * {@inheritDoc}
             */
            public Summary apply(File source, File target, Factory... factory) throws IOException {
                return apply(source, target, Arrays.asList(factory));
            }

            /**
             * {@inheritDoc}
             */
            public Summary apply(File source, File target, List factories) throws IOException {
                return apply(source.isDirectory()
                        ? new Source.ForFolder(source)
                        : new Source.ForJarFile(source), target.isDirectory()
                        ? new Target.ForFolder(target)
                        : new Target.ForJarFile(target), factories);
            }

            /**
             * {@inheritDoc}
             */
            public Summary apply(Source source, Target target, Factory... factory) throws IOException {
                return apply(source, target, Arrays.asList(factory));
            }
        }

        /**
         * A default implementation of a plugin engine.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class Default extends AbstractBase {

            /**
             * The Byte Buddy instance to use.
             */
            private final ByteBuddy byteBuddy;

            /**
             * The type strategy to use.
             */
            private final TypeStrategy typeStrategy;

            /**
             * The pool strategy to use.
             */
            private final PoolStrategy poolStrategy;

            /**
             * The class file locator to use.
             */
            private final ClassFileLocator classFileLocator;

            /**
             * The listener to use.
             */
            private final Listener listener;

            /**
             * The error handler to use.
             */
            private final ErrorHandler errorHandler;

            /**
             * A matcher for types to exclude from transformation.
             */
            private final ElementMatcher.Junction ignoredTypeMatcher;

            /**
             * Creates a new default plugin engine that rebases types and fails fast and on unresolved types and on live initializers.
             */
            public Default() {
                this(new ByteBuddy());
            }

            /**
             * Creates a new default plugin engine that rebases types and fails fast and on unresolved types and on live initializers.
             *
             * @param byteBuddy The Byte Buddy instance to use.
             */
            public Default(ByteBuddy byteBuddy) {
                this(byteBuddy, TypeStrategy.Default.REBASE);
            }

            /**
             * Creates a new default plugin engine.
             *
             * @param byteBuddy    The Byte Buddy instance to use.
             * @param typeStrategy The type strategy to use.
             */
            protected Default(ByteBuddy byteBuddy, TypeStrategy typeStrategy) {
                this(byteBuddy,
                        typeStrategy,
                        PoolStrategy.Default.FAST,
                        ClassFileLocator.NoOp.INSTANCE,
                        Listener.NoOp.INSTANCE,
                        new ErrorHandler.Compound(ErrorHandler.Failing.FAIL_FAST,
                                ErrorHandler.Enforcing.ALL_TYPES_RESOLVED,
                                ErrorHandler.Enforcing.NO_LIVE_INITIALIZERS),
                        none());
            }

            /**
             * Creates a new default plugin engine.
             *
             * @param byteBuddy          The Byte Buddy instance to use.
             * @param typeStrategy       The type strategy to use.
             * @param poolStrategy       The pool strategy to use.
             * @param classFileLocator   The class file locator to use.
             * @param listener           The listener to use.
             * @param errorHandler       The error handler to use.
             * @param ignoredTypeMatcher A matcher for types to exclude from transformation.
             */
            protected Default(ByteBuddy byteBuddy,
                              TypeStrategy typeStrategy,
                              PoolStrategy poolStrategy,
                              ClassFileLocator classFileLocator,
                              Listener listener,
                              ErrorHandler errorHandler,
                              ElementMatcher.Junction ignoredTypeMatcher) {
                this.byteBuddy = byteBuddy;
                this.typeStrategy = typeStrategy;
                this.poolStrategy = poolStrategy;
                this.classFileLocator = classFileLocator;
                this.listener = listener;
                this.errorHandler = errorHandler;
                this.ignoredTypeMatcher = ignoredTypeMatcher;
            }

            /**
             * Creates a plugin engine from an {@link EntryPoint}.
             *
             * @param entryPoint            The entry point to resolve into a plugin engine.
             * @param classFileVersion      The class file version to assume.
             * @param methodNameTransformer The method name transformer to use.
             * @return An appropriate plugin engine.
             */
            public static Engine of(EntryPoint entryPoint, ClassFileVersion classFileVersion, MethodNameTransformer methodNameTransformer) {
                return new Default(entryPoint.byteBuddy(classFileVersion), new TypeStrategy.ForEntryPoint(entryPoint, methodNameTransformer));
            }

            /**
             * Runs a plugin engine using the first and second argument as source and target file location and any additional argument as
             * the fully qualified name of any plugin to apply.
             *
             * @param argument The arguments for running the plugin engine.
             * @throws ClassNotFoundException If a plugin class cannot be found on the system class loader.
             * @throws IOException            If an I/O exception occurs.
             */
            @SuppressWarnings("unchecked")
            public static void main(String... argument) throws ClassNotFoundException, IOException {
                if (argument.length < 2) {
                    throw new IllegalArgumentException("Expected arguments of type:   [, ...]");
                }
                List factories = new ArrayList(argument.length - 2);
                for (String plugin : Arrays.asList(argument).subList(2, argument.length)) {
                    factories.add(new Factory.UsingReflection((Class) Class.forName(plugin)));
                }
                new Default().apply(new File(argument[0]), new File(argument[1]), factories);
            }

            /**
             * {@inheritDoc}
             */
            public Engine with(TypeStrategy typeStrategy) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        listener,
                        errorHandler,
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine with(PoolStrategy poolStrategy) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        listener,
                        errorHandler,
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine with(ClassFileLocator classFileLocator) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        new ClassFileLocator.Compound(this.classFileLocator, classFileLocator),
                        listener,
                        errorHandler,
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine with(Listener listener) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        new Listener.Compound(this.listener, listener),
                        errorHandler,
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine withoutErrorHandlers() {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        listener,
                        Listener.NoOp.INSTANCE,
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine withErrorHandlers(List errorHandlers) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        listener,
                        new ErrorHandler.Compound(errorHandlers),
                        ignoredTypeMatcher);
            }

            /**
             * {@inheritDoc}
             */
            public Engine ignore(ElementMatcher matcher) {
                return new Default(byteBuddy,
                        typeStrategy,
                        poolStrategy,
                        classFileLocator,
                        listener,
                        errorHandler,
                        ignoredTypeMatcher.or(matcher));
            }

            /**
             * {@inheritDoc}
             */
            public Summary apply(Source source, Target target, List factories) throws IOException {
                Listener listener = new Listener.Compound(this.listener, new Listener.ForErrorHandler(errorHandler));
                List transformed = new ArrayList();
                Map> failed = new LinkedHashMap>();
                List unresolved = new ArrayList();
                RuntimeException rethrown = null;
                List plugins = new ArrayList(factories.size());
                try {
                    for (Plugin.Factory factory : factories) {
                        plugins.add(factory.make());
                    }
                    ClassFileLocator classFileLocator = new ClassFileLocator.Compound(source.getClassFileLocator(), this.classFileLocator);
                    TypePool typePool = poolStrategy.typePool(classFileLocator);
                    Manifest manifest = source.getManifest();
                    Target.Sink sink = manifest == null
                            ? target.write()
                            : target.write(manifest);
                    try {
                        for (Source.Element element : source) {
                            String name = element.getName();
                            while (name.startsWith("/")) {
                                name = name.substring(1);
                            }
                            if (name.endsWith(CLASS_FILE_EXTENSION)) {
                                String typeName = name.substring(0, name.length() - CLASS_FILE_EXTENSION.length()).replace('/', '.');
                                listener.onDiscovery(typeName);
                                TypePool.Resolution resolution = typePool.describe(typeName);
                                if (resolution.isResolved()) {
                                    TypeDescription typeDescription = resolution.resolve();
                                    if (!ignoredTypeMatcher.matches(typeDescription)) {
                                        List applied = new ArrayList(), ignored = new ArrayList();
                                        List errored = new ArrayList();
                                        DynamicType.Builder builder = typeStrategy.builder(byteBuddy, typeDescription, classFileLocator);
                                        for (Plugin plugin : plugins) {
                                            try {
                                                if (plugin.matches(typeDescription)) {
                                                    builder = plugin.apply(builder, typeDescription, classFileLocator);
                                                    listener.onTransformation(typeDescription, plugin);
                                                    applied.add(plugin);
                                                } else {
                                                    listener.onIgnored(typeDescription, plugin);
                                                    ignored.add(plugin);
                                                }
                                            } catch (Throwable throwable) {
                                                listener.onError(typeDescription, plugin, throwable);
                                                errored.add(throwable);
                                            }
                                        }
                                        if (!errored.isEmpty()) {
                                            listener.onError(typeDescription, errored);
                                            sink.retain(element);
                                            failed.put(typeDescription, errored);
                                        } else if (!applied.isEmpty()) {
                                            DynamicType dynamicType = builder.make();
                                            listener.onTransformation(typeDescription, applied);
                                            for (Map.Entry entry : dynamicType.getLoadedTypeInitializers().entrySet()) {
                                                if (entry.getValue().isAlive()) {
                                                    listener.onLiveInitializer(typeDescription, entry.getKey());
                                                }
                                            }
                                            sink.store(dynamicType.getAllTypes());
                                            transformed.add(dynamicType.getTypeDescription());
                                        } else {
                                            listener.onIgnored(typeDescription, ignored);
                                            sink.retain(element);
                                        }
                                    } else {
                                        listener.onIgnored(typeDescription, plugins);
                                        sink.retain(element);
                                    }
                                    listener.onComplete(typeDescription);
                                } else {
                                    listener.onUnresolved(typeName);
                                    sink.retain(element);
                                    unresolved.add(typeName);
                                }
                            } else if (!name.equals(MANIFEST_LOCATION)) {
                                listener.onResource(name);
                                sink.retain(element);
                            }
                        }
                        if (!failed.isEmpty()) {
                            listener.onError(failed);
                        }
                    } finally {
                        sink.close();
                    }
                } finally {
                    for (Plugin plugin : plugins) {
                        try {
                            plugin.close();
                        } catch (Throwable throwable) {
                            try {
                                listener.onError(plugin, throwable);
                            } catch (RuntimeException exception) {
                                rethrown = rethrown == null
                                        ? exception
                                        : rethrown;
                            }
                        }
                    }
                }
                if (rethrown == null) {
                    return new Summary(transformed, failed, unresolved);
                } else {
                    throw rethrown;
                }
            }
        }
    }

    /**
     * A non-operational plugin that does not instrument any type. This plugin does not need to be closed.
     */
    @HashCodeAndEqualsPlugin.Enhance
    class NoOp implements Plugin {

        /**
         * {@inheritDoc}
         */
        public boolean matches(TypeDescription target) {
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public DynamicType.Builder apply(DynamicType.Builder builder, TypeDescription typeDescription,
                                            ClassFileLocator classFileLocator) {
            throw new IllegalStateException("Cannot apply non-operational plugin");
        }

        /**
         * {@inheritDoc}
         */
        public void close() {
            /* do nothing */
        }
    }

    /**
     * An abstract base for a {@link Plugin} that matches types by a given {@link ElementMatcher}.
     */
    @HashCodeAndEqualsPlugin.Enhance
    abstract class ForElementMatcher implements Plugin {

        /**
         * The element matcher to apply.
         */
        private final ElementMatcher matcher;

        /**
         * Creates a new plugin that matches types using an element matcher.
         *
         * @param matcher The element matcher to apply.
         */
        protected ForElementMatcher(ElementMatcher matcher) {
            this.matcher = matcher;
        }

        /**
         * {@inheritDoc}
         */
        public boolean matches(TypeDescription target) {
            return matcher.matches(target);
        }
    }

    /**
     * A compound plugin that applies several plugins in a row.
     */
    @HashCodeAndEqualsPlugin.Enhance
    class Compound implements Plugin {

        /**
         * The plugins to apply.
         */
        private final List plugins;

        /**
         * Creates a compound plugin.
         *
         * @param plugin The plugins to apply.
         */
        public Compound(Plugin... plugin) {
            this(Arrays.asList(plugin));
        }

        /**
         * Creates a compound plugin.
         *
         * @param plugins The plugins to apply.
         */
        public Compound(List plugins) {
            this.plugins = new ArrayList();
            for (Plugin plugin : plugins) {
                if (plugin instanceof Compound) {
                    this.plugins.addAll(((Compound) plugin).plugins);
                } else if (!(plugin instanceof NoOp)) {
                    this.plugins.add(plugin);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public boolean matches(TypeDescription target) {
            for (Plugin plugin : plugins) {
                if (plugin.matches(target)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * {@inheritDoc}
         */
        public DynamicType.Builder apply(DynamicType.Builder builder, TypeDescription typeDescription,
                                            ClassFileLocator classFileLocator) {
            for (Plugin plugin : plugins) {
                if (plugin.matches(typeDescription)) {
                    builder = plugin.apply(builder, typeDescription, classFileLocator);
                }
            }
            return builder;
        }

        /**
         * {@inheritDoc}
         */
        public void close() throws IOException {
            for (Plugin plugin : plugins) {
                plugin.close();
            }
        }
    }
}