/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.dynamic.scaffold;

import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.Transformer;
import net.bytebuddy.dynamic.VisibilityBridgeStrategy;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.LoadedTypeInitializer;
import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.LatentMatcher;
import net.bytebuddy.utility.CompoundList;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static net.bytebuddy.matcher.ElementMatchers.anyOf;
import static net.bytebuddy.matcher.ElementMatchers.failSafe;
import static net.bytebuddy.matcher.ElementMatchers.hasParameters;
import static net.bytebuddy.matcher.ElementMatchers.hasType;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.isVirtual;
import static net.bytebuddy.matcher.ElementMatchers.isVisibleTo;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static net.bytebuddy.matcher.ElementMatchers.returns;
import static net.bytebuddy.matcher.ElementMatchers.whereNone;

/**
 * A method registry is responsible for storing information on how a method is intercepted.
 */
public interface MethodRegistry {

    /**
     * Prepends the given method definition to this method registry, i.e. this configuration is applied first.
     *
     * @param methodMatcher            A matcher to identify any method that this definition concerns.
     * @param handler                  The handler to instrument any matched method.
     * @param attributeAppenderFactory A method attribute appender to apply to any matched method.
     * @param transformer              The method transformer to be applied to implemented methods.
     * @return An adapted version of this method registry.
     */
    MethodRegistry prepend(LatentMatcher<? super MethodDescription> methodMatcher,
                           Handler handler,
                           MethodAttributeAppender.Factory attributeAppenderFactory,
                           Transformer<MethodDescription> transformer);

    /**
     * Appends the given method definition to this method registry, i.e. this configuration is applied last.
     *
     * @param methodMatcher            A matcher to identify all entries that are to be matched.
     * @param handler                  The handler to instrument any matched method.
     * @param attributeAppenderFactory A method attribute appender to apply to any matched method.
     * @param transformer              The method transformer to be applied to implemented methods.
     * @return An adapted version of this method registry.
     */
    MethodRegistry append(LatentMatcher<? super MethodDescription> methodMatcher,
                          Handler handler,
                          MethodAttributeAppender.Factory attributeAppenderFactory,
                          Transformer<MethodDescription> transformer);

    /**
     * Prepares this method registry.
     *
     * @param instrumentedType         The instrumented type that should be created.
     * @param methodGraphCompiler      The method graph compiler to be used for analyzing the fully assembled instrumented type.
     * @param typeValidation           Determines if a type should be explicitly validated.
     * @param visibilityBridgeStrategy The visibility bridge strategy to apply.
     * @param ignoredMethods           A filter that only matches methods that should be instrumented.
     * @return A prepared version of this method registry.
     */
    Prepared prepare(InstrumentedType instrumentedType,
                     MethodGraph.Compiler methodGraphCompiler,
                     TypeValidation typeValidation,
                     VisibilityBridgeStrategy visibilityBridgeStrategy,
                     LatentMatcher<? super MethodDescription> ignoredMethods);

    /**
     * A handler for implementing a method.
     */
    interface Handler extends InstrumentedType.Prepareable {

        /**
         * Compiles this handler.
         *
         * @param implementationTarget The implementation target to compile this handler for.
         * @return A compiled handler.
         */
        Handler.Compiled compile(Implementation.Target implementationTarget);

        /**
         * A handler for defining an abstract or native method.
         */
        enum ForAbstractMethod implements Handler, Compiled {

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

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(Implementation.Target implementationTarget) {
                return this;
            }

            /**
             * {@inheritDoc}
             */
            public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
                return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithoutBody(methodDescription, attributeAppender, visibility);
            }
        }

        /**
         * A handler for implementing a visibility bridge.
         */
        enum ForVisibilityBridge implements Handler {

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

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                throw new IllegalStateException("A visibility bridge handler must not apply any preparations");
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(Implementation.Target implementationTarget) {
                return new Compiled(implementationTarget.getInstrumentedType());
            }

            /**
             * A compiled handler for a visibility bridge handler.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class Compiled implements Handler.Compiled {

                /**
                 * The instrumented type.
                 */
                private final TypeDescription instrumentedType;

                /**
                 * Creates a new compiled handler for a visibility bridge.
                 *
                 * @param instrumentedType The instrumented type.
                 */
                protected Compiled(TypeDescription instrumentedType) {
                    this.instrumentedType = instrumentedType;
                }

                /**
                 * {@inheritDoc}
                 */
                public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
                    return TypeWriter.MethodPool.Record.ForDefinedMethod.OfVisibilityBridge.of(instrumentedType, methodDescription, attributeAppender);
                }
            }
        }

        /**
         * A compiled handler for implementing a method.
         */
        interface Compiled {

            /**
             * Assembles this compiled entry with a method attribute appender.
             *
             * @param methodDescription The method description to apply with this handler.
             * @param attributeAppender The method attribute appender to apply together with this handler.
             * @param visibility        The represented method's minimum visibility.
             * @return A method pool entry representing this handler and the given attribute appender.
             */
            TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility);
        }

        /**
         * A handler for a method that is implemented as byte code.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class ForImplementation implements Handler {

            /**
             * The implementation to apply.
             */
            private final Implementation implementation;

            /**
             * Creates a new handler for implementing a method with byte code.
             *
             * @param implementation The implementation to apply.
             */
            public ForImplementation(Implementation implementation) {
                this.implementation = implementation;
            }

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return implementation.prepare(instrumentedType);
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(Implementation.Target implementationTarget) {
                return new Compiled(implementation.appender(implementationTarget));
            }

            /**
             * A compiled handler for implementing a method.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class Compiled implements Handler.Compiled {

                /**
                 * The byte code appender to apply.
                 */
                private final ByteCodeAppender byteCodeAppender;

                /**
                 * Creates a new compiled handler for a method implementation.
                 *
                 * @param byteCodeAppender The byte code appender to apply.
                 */
                protected Compiled(ByteCodeAppender byteCodeAppender) {
                    this.byteCodeAppender = byteCodeAppender;
                }

                /**
                 * {@inheritDoc}
                 */
                public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
                    return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithBody(methodDescription, byteCodeAppender, attributeAppender, visibility);
                }
            }
        }

        /**
         * A handler for defining a default annotation value for a method.
         */
        @HashCodeAndEqualsPlugin.Enhance
        class ForAnnotationValue implements Handler, Compiled {

            /**
             * The annotation value to set as a default value.
             */
            private final AnnotationValue<?, ?> annotationValue;

            /**
             * Creates a handler for defining a default annotation value for a method.
             *
             * @param annotationValue The annotation value to set as a default value.
             */
            public ForAnnotationValue(AnnotationValue<?, ?> annotationValue) {
                this.annotationValue = annotationValue;
            }

            /**
             * {@inheritDoc}
             */
            public InstrumentedType prepare(InstrumentedType instrumentedType) {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public Compiled compile(Implementation.Target implementationTarget) {
                return this;
            }

            /**
             * {@inheritDoc}
             */
            public TypeWriter.MethodPool.Record assemble(MethodDescription methodDescription, MethodAttributeAppender attributeAppender, Visibility visibility) {
                return new TypeWriter.MethodPool.Record.ForDefinedMethod.WithAnnotationDefaultValue(methodDescription, annotationValue, attributeAppender);
            }
        }
    }

    /**
     * A method registry that fully prepared the instrumented type.
     */
    interface Prepared {

        /**
         * Returns the fully prepared instrumented type.
         *
         * @return The fully prepared instrumented type.
         */
        TypeDescription getInstrumentedType();

        /**
         * Returns the declared or virtually inherited methods of this type.
         *
         * @return The declared or virtually inherited methods of this type.
         */
        MethodList<?> getMethods();

        /**
         * Returns a list of all methods that should be instrumented.
         *
         * @return A list of all methods that should be instrumented.
         */
        MethodList<?> getInstrumentedMethods();

        /**
         * Returns the loaded type initializer of the instrumented type.
         *
         * @return The loaded type initializer of the instrumented type.
         */
        LoadedTypeInitializer getLoadedTypeInitializer();

        /**
         * The type initializer of the instrumented type.
         *
         * @return The type initializer of the instrumented type.
         */
        TypeInitializer getTypeInitializer();

        /**
         * Compiles this prepared method registry.
         *
         * @param implementationTargetFactory A factory for creating an implementation target.
         * @param classFileVersion            The type's class file version.
         * @return A factory for creating an implementation target.
         */
        Compiled compile(Implementation.Target.Factory implementationTargetFactory, ClassFileVersion classFileVersion);
    }

    /**
     * A compiled version of a method registry.
     */
    interface Compiled extends TypeWriter.MethodPool {

        /**
         * Returns the instrumented type that is to be created.
         *
         * @return The instrumented type that is to be created.
         */
        TypeDescription getInstrumentedType();

        /**
         * Returns the declared or virtually inherited methods of this type.
         *
         * @return The declared or virtually inherited methods of this type.
         */
        MethodList<?> getMethods();

        /**
         * Returns a list of all methods that should be instrumented.
         *
         * @return A list of all methods that should be instrumented.
         */
        MethodList<?> getInstrumentedMethods();

        /**
         * Returns the loaded type initializer of the instrumented type.
         *
         * @return The loaded type initializer of the instrumented type.
         */
        LoadedTypeInitializer getLoadedTypeInitializer();

        /**
         * The type initializer of the instrumented type.
         *
         * @return The type initializer of the instrumented type.
         */
        TypeInitializer getTypeInitializer();
    }

    /**
     * A default implementation of a method registry.
     */
    @HashCodeAndEqualsPlugin.Enhance
    class Default implements MethodRegistry {

        /**
         * The list of currently registered entries in their application order.
         */
        private final List<Entry> entries;

        /**
         * Creates a new default method registry without entries.
         */
        public Default() {
            entries = Collections.emptyList();
        }

        /**
         * Creates a new default method registry.
         *
         * @param entries The currently registered entries.
         */
        private Default(List<Entry> entries) {
            this.entries = entries;
        }

        /**
         * {@inheritDoc}
         */
        public MethodRegistry prepend(LatentMatcher<? super MethodDescription> matcher,
                                      Handler handler,
                                      MethodAttributeAppender.Factory attributeAppenderFactory,
                                      Transformer<MethodDescription> transformer) {
            return new Default(CompoundList.of(new Entry(matcher, handler, attributeAppenderFactory, transformer), entries));
        }

        /**
         * {@inheritDoc}
         */
        public MethodRegistry append(LatentMatcher<? super MethodDescription> matcher,
                                     Handler handler,
                                     MethodAttributeAppender.Factory attributeAppenderFactory,
                                     Transformer<MethodDescription> transformer) {
            return new Default(CompoundList.of(entries, new Entry(matcher, handler, attributeAppenderFactory, transformer)));
        }

        /**
         * {@inheritDoc}
         */
        public MethodRegistry.Prepared prepare(InstrumentedType instrumentedType,
                                               MethodGraph.Compiler methodGraphCompiler,
                                               TypeValidation typeValidation,
                                               VisibilityBridgeStrategy visibilityBridgeStrategy,
                                               LatentMatcher<? super MethodDescription> ignoredMethods) {
            LinkedHashMap<MethodDescription, Prepared.Entry> implementations = new LinkedHashMap<MethodDescription, Prepared.Entry>();
            Set<Handler> handlers = new HashSet<Handler>();
            Set<MethodDescription> declaredMethods = new HashSet<MethodDescription>(instrumentedType.getDeclaredMethods());
            for (Entry entry : entries) {
                if (handlers.add(entry.getHandler())) {
                    InstrumentedType typeDescription = entry.getHandler().prepare(instrumentedType);
                    if (instrumentedType != typeDescription) { // Avoid unnecessary scanning for helper methods if instrumented type was not changed.
                        for (MethodDescription methodDescription : typeDescription.getDeclaredMethods()) {
                            if (!declaredMethods.contains(methodDescription)) {
                                implementations.put(methodDescription, entry.asSupplementaryEntry(methodDescription));
                                declaredMethods.add(methodDescription);
                            }
                        }
                        instrumentedType = typeDescription;
                    }
                }
            }
            MethodGraph.Linked methodGraph = methodGraphCompiler.compile((TypeDefinition) instrumentedType);
            // Casting required for Java 6 compiler.
            ElementMatcher<? super MethodDescription> relevanceMatcher = (ElementMatcher<? super MethodDescription>) failSafe(not(anyOf(implementations.keySet()))
                    .and(returns(isVisibleTo(instrumentedType)))
                    .and(hasParameters(whereNone(hasType(not(isVisibleTo(instrumentedType))))))).and(ignoredMethods.resolve(instrumentedType));
            List<MethodDescription> methods = new ArrayList<MethodDescription>();
            for (MethodGraph.Node node : methodGraph.listNodes()) {
                MethodDescription methodDescription = node.getRepresentative();
                boolean visibilityBridge = instrumentedType.isPublic() && !instrumentedType.isInterface();
                if (relevanceMatcher.matches(methodDescription)) {
                    for (Entry entry : entries) {
                        if (entry.resolve(instrumentedType).matches(methodDescription)) {
                            implementations.put(methodDescription, entry.asPreparedEntry(instrumentedType,
                                    methodDescription,
                                    node.getMethodTypes(),
                                    node.getVisibility()));
                            visibilityBridge = false;
                            break;
                        }
                    }
                }
                if (visibilityBridge
                        && !node.getSort().isMadeVisible()
                        && methodDescription.isPublic()
                        && !(methodDescription.isAbstract() || methodDescription.isFinal())
                        && methodDescription.getDeclaringType().isPackagePrivate()
                        && visibilityBridgeStrategy.generateVisibilityBridge(methodDescription)) {
                    // Visibility bridges are required for public classes that inherit a public method from a package-private class.
                    implementations.put(methodDescription, Prepared.Entry.forVisibilityBridge(methodDescription, node.getVisibility()));
                }
                methods.add(methodDescription);
            }
            for (MethodDescription methodDescription : CompoundList.of(
                    instrumentedType.getDeclaredMethods().filter(not(isVirtual()).and(relevanceMatcher)),
                    new MethodDescription.Latent.TypeInitializer(instrumentedType))) {
                for (Entry entry : entries) {
                    if (entry.resolve(instrumentedType).matches(methodDescription)) {
                        implementations.put(methodDescription, entry.asPreparedEntry(instrumentedType, methodDescription, methodDescription.getVisibility()));
                        break;
                    }
                }
                methods.add(methodDescription);
            }
            return new Prepared(implementations,
                    instrumentedType.getLoadedTypeInitializer(),
                    instrumentedType.getTypeInitializer(),
                    typeValidation.isEnabled()
                            ? instrumentedType.validated()
                            : instrumentedType,
                    methodGraph,
                    new MethodList.Explicit<MethodDescription>(methods));
        }

        /**
         * An entry of a default method registry.
         */
        @HashCodeAndEqualsPlugin.Enhance
        protected static class Entry implements LatentMatcher<MethodDescription> {

            /**
             * The latent method matcher that this entry represents.
             */
            private final LatentMatcher<? super MethodDescription> matcher;

            /**
             * The handler to apply to all matched entries.
             */
            private final Handler handler;

            /**
             * A method attribute appender factory to apply to all entries.
             */
            private final MethodAttributeAppender.Factory attributeAppenderFactory;

            /**
             * The method transformer to be applied to implemented methods.
             */
            private final Transformer<MethodDescription> transformer;

            /**
             * Creates a new entry.
             *
             * @param matcher                  The latent method matcher that this entry represents.
             * @param handler                  The handler to apply to all matched entries.
             * @param attributeAppenderFactory A method attribute appender factory to apply to all entries.
             * @param transformer              The method transformer to be applied to implemented methods.
             */
            protected Entry(LatentMatcher<? super MethodDescription> matcher,
                            Handler handler,
                            MethodAttributeAppender.Factory attributeAppenderFactory,
                            Transformer<MethodDescription> transformer) {
                this.matcher = matcher;
                this.handler = handler;
                this.attributeAppenderFactory = attributeAppenderFactory;
                this.transformer = transformer;
            }

            /**
             * Transforms this entry into a prepared state.
             *
             * @param instrumentedType  The instrumented type.
             * @param methodDescription The non-transformed method to be implemented.
             * @param visibility        The represented method's minimum visibility.
             * @return A prepared version of this entry.
             */
            protected Prepared.Entry asPreparedEntry(TypeDescription instrumentedType, MethodDescription methodDescription, Visibility visibility) {
                return asPreparedEntry(instrumentedType, methodDescription, Collections.<MethodDescription.TypeToken>emptySet(), visibility);
            }

            /**
             * Transforms this entry into a prepared state.
             *
             * @param instrumentedType  The instrumented type.
             * @param methodDescription The non-transformed method to be implemented.
             * @param methodTypes       The method types this method represents.
             * @param visibility        The represented method's minimum visibility.
             * @return A prepared version of this entry.
             */
            protected Prepared.Entry asPreparedEntry(TypeDescription instrumentedType,
                                                     MethodDescription methodDescription,
                                                     Set<MethodDescription.TypeToken> methodTypes,
                                                     Visibility visibility) {
                return new Prepared.Entry(handler,
                        attributeAppenderFactory,
                        transformer.transform(instrumentedType, methodDescription),
                        methodTypes,
                        visibility,
                        false);
            }

            /**
             * Returns a prepared entry for a supplementary method.
             *
             * @param methodDescription The method to be implemented.
             * @return An entry for a supplementary entry that is defined by a method implementation instance.
             */
            protected Prepared.Entry asSupplementaryEntry(MethodDescription methodDescription) {
                return new Prepared.Entry(handler,
                        MethodAttributeAppender.Explicit.of(methodDescription),
                        methodDescription,
                        Collections.<MethodDescription.TypeToken>emptySet(),
                        methodDescription.getVisibility(),
                        false);
            }

            /**
             * Returns this entry's handler.
             *
             * @return The handler of this entry.
             */
            protected Handler getHandler() {
                return handler;
            }

            /**
             * {@inheritDoc}
             */
            public ElementMatcher<? super MethodDescription> resolve(TypeDescription typeDescription) {
                return matcher.resolve(typeDescription);
            }
        }

        /**
         * A prepared version of a default method registry.
         */
        @HashCodeAndEqualsPlugin.Enhance
        protected static class Prepared implements MethodRegistry.Prepared {

            /**
             * A map of all method descriptions mapped to their handling entries.
             */
            private final LinkedHashMap<MethodDescription, Entry> implementations;

            /**
             * The loaded type initializer of the instrumented type.
             */
            private final LoadedTypeInitializer loadedTypeInitializer;

            /**
             * The type initializer of the instrumented type.
             */
            private final TypeInitializer typeInitializer;

            /**
             * The instrumented type.
             */
            private final TypeDescription instrumentedType;

            /**
             * A method graph describing the instrumented type.
             */
            private final MethodGraph.Linked methodGraph;

            /**
             * The declared or virtually inherited methods of this type.
             */
            private final MethodList<?> methods;

            /**
             * Creates a prepared version of a default method registry.
             *
             * @param implementations       A map of all method descriptions mapped to their handling entries.
             * @param loadedTypeInitializer The loaded type initializer of the instrumented type.
             * @param typeInitializer       The type initializer of the instrumented type.
             * @param instrumentedType      The instrumented type.
             * @param methodGraph           A method graph describing the instrumented type.
             * @param methods               The declared or virtually inherited methods of this type.
             */
            protected Prepared(LinkedHashMap<MethodDescription, Entry> implementations,
                               LoadedTypeInitializer loadedTypeInitializer,
                               TypeInitializer typeInitializer,
                               TypeDescription instrumentedType,
                               MethodGraph.Linked methodGraph,
                               MethodList<?> methods) {
                this.implementations = implementations;
                this.loadedTypeInitializer = loadedTypeInitializer;
                this.typeInitializer = typeInitializer;
                this.instrumentedType = instrumentedType;
                this.methodGraph = methodGraph;
                this.methods = methods;
            }

            /**
             * {@inheritDoc}
             */
            public TypeDescription getInstrumentedType() {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public LoadedTypeInitializer getLoadedTypeInitializer() {
                return loadedTypeInitializer;
            }

            /**
             * {@inheritDoc}
             */
            public TypeInitializer getTypeInitializer() {
                return typeInitializer;
            }

            /**
             * {@inheritDoc}
             */
            public MethodList<?> getMethods() {
                return methods;
            }

            /**
             * {@inheritDoc}
             */
            public MethodList<?> getInstrumentedMethods() {
                return new MethodList.Explicit<MethodDescription>(new ArrayList<MethodDescription>(implementations.keySet())).filter(not(isTypeInitializer()));
            }

            /**
             * {@inheritDoc}
             */
            public MethodRegistry.Compiled compile(Implementation.Target.Factory implementationTargetFactory, ClassFileVersion classFileVersion) {
                Map<Handler, Handler.Compiled> compilationCache = new HashMap<Handler, Handler.Compiled>();
                Map<MethodAttributeAppender.Factory, MethodAttributeAppender> attributeAppenderCache = new HashMap<MethodAttributeAppender.Factory, MethodAttributeAppender>();
                LinkedHashMap<MethodDescription, Compiled.Entry> entries = new LinkedHashMap<MethodDescription, Compiled.Entry>();
                Implementation.Target implementationTarget = implementationTargetFactory.make(instrumentedType, methodGraph, classFileVersion);
                for (Map.Entry<MethodDescription, Entry> entry : implementations.entrySet()) {
                    Handler.Compiled cachedHandler = compilationCache.get(entry.getValue().getHandler());
                    if (cachedHandler == null) {
                        cachedHandler = entry.getValue().getHandler().compile(implementationTarget);
                        compilationCache.put(entry.getValue().getHandler(), cachedHandler);
                    }
                    MethodAttributeAppender cachedAttributeAppender = attributeAppenderCache.get(entry.getValue().getAppenderFactory());
                    if (cachedAttributeAppender == null) {
                        cachedAttributeAppender = entry.getValue().getAppenderFactory().make(instrumentedType);
                        attributeAppenderCache.put(entry.getValue().getAppenderFactory(), cachedAttributeAppender);
                    }
                    entries.put(entry.getKey(), new Compiled.Entry(cachedHandler,
                            cachedAttributeAppender,
                            entry.getValue().getMethodDescription(),
                            entry.getValue().resolveBridgeTypes(),
                            entry.getValue().getVisibility(),
                            entry.getValue().isBridgeMethod()));
                }
                return new Compiled(instrumentedType,
                        loadedTypeInitializer,
                        typeInitializer,
                        methods,
                        entries,
                        classFileVersion.isAtLeast(ClassFileVersion.JAVA_V5));
            }

            /**
             * An entry of a prepared method registry.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class Entry {

                /**
                 * The handler for implementing methods.
                 */
                private final Handler handler;

                /**
                 * A attribute appender factory for appending attributes for any implemented method.
                 */
                private final MethodAttributeAppender.Factory attributeAppenderFactory;

                /**
                 * The method this entry represents.
                 */
                private final MethodDescription methodDescription;

                /**
                 * The method's type tokens.
                 */
                private final Set<MethodDescription.TypeToken> typeTokens;

                /**
                 * The minimum required visibility of this method.
                 */
                private final Visibility visibility;

                /**
                 * Is {@code true} if this entry represents a bridge method.
                 */
                private final boolean bridgeMethod;

                /**
                 * Creates a new prepared entry.
                 *
                 * @param handler                  The handler for implementing methods.
                 * @param attributeAppenderFactory A attribute appender factory for appending attributes for any implemented method.
                 * @param methodDescription        The method this entry represents.
                 * @param typeTokens               A set of bridges representing the bridge methods of this method.
                 * @param visibility               The minimum required visibility of this method.
                 * @param bridgeMethod             {@code true} if this entry represents a bridge method.
                 */
                protected Entry(Handler handler,
                                MethodAttributeAppender.Factory attributeAppenderFactory,
                                MethodDescription methodDescription,
                                Set<MethodDescription.TypeToken> typeTokens,
                                Visibility visibility,
                                boolean bridgeMethod) {
                    this.handler = handler;
                    this.attributeAppenderFactory = attributeAppenderFactory;
                    this.methodDescription = methodDescription;
                    this.typeTokens = typeTokens;
                    this.visibility = visibility;
                    this.bridgeMethod = bridgeMethod;
                }

                /**
                 * Creates an entry for a visibility bridge.
                 *
                 * @param bridgeTarget The bridge method's target.
                 * @param visibility   The represented method's minimum visibility.
                 * @return An entry representing a visibility bridge.
                 */
                protected static Entry forVisibilityBridge(MethodDescription bridgeTarget, Visibility visibility) {
                    return new Entry(Handler.ForVisibilityBridge.INSTANCE,
                            MethodAttributeAppender.Explicit.of(bridgeTarget),
                            bridgeTarget,
                            Collections.<MethodDescription.TypeToken>emptySet(),
                            visibility,
                            true);
                }

                /**
                 * Returns this entry's handler.
                 *
                 * @return The entry's handler.
                 */
                protected Handler getHandler() {
                    return handler;
                }

                /**
                 * Returns this entry's attribute appender factory.
                 *
                 * @return This entry's attribute appender factory.
                 */
                protected MethodAttributeAppender.Factory getAppenderFactory() {
                    return attributeAppenderFactory;
                }

                /**
                 * Returns the method description this entry represents.
                 *
                 * @return The method description this entry represents.
                 */
                protected MethodDescription getMethodDescription() {
                    return methodDescription;
                }

                /**
                 * Resolves the type tokens of all bridge methods that are required to be implemented for this entry.
                 *
                 * @return A set of type tokens representing the bridge methods required for implementing this type.
                 */
                protected Set<MethodDescription.TypeToken> resolveBridgeTypes() {
                    HashSet<MethodDescription.TypeToken> typeTokens = new HashSet<MethodDescription.TypeToken>(this.typeTokens);
                    typeTokens.remove(methodDescription.asTypeToken());
                    return typeTokens;
                }

                /**
                 * Returns the represented method's minimum visibility.
                 *
                 * @return The represented method's minimum visibility.
                 */
                protected Visibility getVisibility() {
                    return visibility;
                }

                /**
                 * Returns {@code true} if this entry represents a bridge method.
                 *
                 * @return {@code true} if this entry represents a bridge method.
                 */
                protected boolean isBridgeMethod() {
                    return bridgeMethod;
                }
            }
        }

        /**
         * A compiled version of a default method registry.
         */
        @HashCodeAndEqualsPlugin.Enhance
        protected static class Compiled implements MethodRegistry.Compiled {

            /**
             * The instrumented type.
             */
            private final TypeDescription instrumentedType;

            /**
             * The loaded type initializer of the instrumented type.
             */
            private final LoadedTypeInitializer loadedTypeInitializer;

            /**
             * The type initializer of the instrumented type.
             */
            private final TypeInitializer typeInitializer;

            /**
             * The declared or virtually inherited methods of this type.
             */
            private final MethodList<?> methods;

            /**
             * A map of all method descriptions mapped to their handling entries.
             */
            private final LinkedHashMap<MethodDescription, Entry> implementations;

            /**
             * {@code true} if the created type supports bridge methods.
             */
            private final boolean supportsBridges;

            /**
             * Creates a new compiled version of a default method registry.
             *
             * @param instrumentedType      The instrumented type.
             * @param loadedTypeInitializer The loaded type initializer of the instrumented type.
             * @param typeInitializer       The type initializer of the instrumented type.
             * @param methods               The declared or virtually inherited methods of this type.
             * @param implementations       A map of all method descriptions mapped to their handling entries.
             * @param supportsBridges       {@code true} if the created type supports bridge methods.
             */
            protected Compiled(TypeDescription instrumentedType,
                               LoadedTypeInitializer loadedTypeInitializer,
                               TypeInitializer typeInitializer,
                               MethodList<?> methods,
                               LinkedHashMap<MethodDescription, Entry> implementations,
                               boolean supportsBridges) {
                this.instrumentedType = instrumentedType;
                this.loadedTypeInitializer = loadedTypeInitializer;
                this.typeInitializer = typeInitializer;
                this.methods = methods;
                this.implementations = implementations;
                this.supportsBridges = supportsBridges;
            }

            /**
             * {@inheritDoc}
             */
            public TypeDescription getInstrumentedType() {
                return instrumentedType;
            }

            /**
             * {@inheritDoc}
             */
            public LoadedTypeInitializer getLoadedTypeInitializer() {
                return loadedTypeInitializer;
            }

            /**
             * {@inheritDoc}
             */
            public TypeInitializer getTypeInitializer() {
                return typeInitializer;
            }

            /**
             * {@inheritDoc}
             */
            public MethodList<?> getMethods() {
                return methods;
            }

            /**
             * {@inheritDoc}
             */
            public MethodList<?> getInstrumentedMethods() {
                return new MethodList.Explicit<MethodDescription>(new ArrayList<MethodDescription>(implementations.keySet())).filter(not(isTypeInitializer()));
            }

            /**
             * {@inheritDoc}
             */
            public Record target(MethodDescription methodDescription) {
                Entry entry = implementations.get(methodDescription);
                return entry == null
                        ? new Record.ForNonImplementedMethod(methodDescription)
                        : entry.bind(instrumentedType, supportsBridges);
            }

            /**
             * An entry of a compiled method registry.
             */
            @HashCodeAndEqualsPlugin.Enhance
            protected static class Entry {

                /**
                 * The handler to be used for implementing a method.
                 */
                private final Handler.Compiled handler;

                /**
                 * The attribute appender of a compiled method.
                 */
                private final MethodAttributeAppender attributeAppender;

                /**
                 * The method to be implemented including potential transformations.
                 */
                private final MethodDescription methodDescription;

                /**
                 * The type tokens representing all bridge methods for the method.
                 */
                private final Set<MethodDescription.TypeToken> bridgeTypes;

                /**
                 * The represented method's minimum visibility.
                 */
                private final Visibility visibility;

                /**
                 * {@code true} if this entry represents a bridge method.
                 */
                private final boolean bridgeMethod;

                /**
                 * Creates a new entry for a compiled method registry.
                 *
                 * @param handler           The handler to be used for implementing a method.
                 * @param attributeAppender The attribute appender of a compiled method.
                 * @param methodDescription The method to be implemented including potential transformations.
                 * @param bridgeTypes       The type tokens representing all bridge methods for the method.
                 * @param visibility        The represented method's minimum visibility.
                 * @param bridgeMethod      {@code true} if this entry represents a bridge method.
                 */
                protected Entry(Handler.Compiled handler,
                                MethodAttributeAppender attributeAppender,
                                MethodDescription methodDescription,
                                Set<MethodDescription.TypeToken> bridgeTypes,
                                Visibility visibility,
                                boolean bridgeMethod) {
                    this.handler = handler;
                    this.attributeAppender = attributeAppender;
                    this.methodDescription = methodDescription;
                    this.bridgeTypes = bridgeTypes;
                    this.visibility = visibility;
                    this.bridgeMethod = bridgeMethod;
                }

                /**
                 * Transforms this entry into a method record.
                 *
                 * @param instrumentedType The instrumented type to bind.
                 * @param supportsBridges  {@code true} if the record should support bridge methods.
                 * @return A record representing this entry's properties.
                 */
                protected Record bind(TypeDescription instrumentedType, boolean supportsBridges) {
                    if (bridgeMethod && !supportsBridges) {
                        return new Record.ForNonImplementedMethod(methodDescription);
                    }
                    Record record = handler.assemble(methodDescription, attributeAppender, visibility);
                    return supportsBridges
                            ? TypeWriter.MethodPool.Record.AccessBridgeWrapper.of(record, instrumentedType, methodDescription, bridgeTypes, attributeAppender)
                            : record;
                }
            }
        }
    }
}
