/*
 * Copyright 2003,2004 The Apache Software Foundation
 *
 *  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.sf.cglib.core;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

import net.sf.cglib.core.internal.CustomizerRegistry;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;

public class EmitUtils {
    private static final Signature CSTRUCT_NULL =
      TypeUtils.parseConstructor("");
    private static final Signature CSTRUCT_THROWABLE =
      TypeUtils.parseConstructor("Throwable");

    private static final Signature GET_NAME =
      TypeUtils.parseSignature("String getName()");
    private static final Signature HASH_CODE =
      TypeUtils.parseSignature("int hashCode()");
    private static final Signature EQUALS =
      TypeUtils.parseSignature("boolean equals(Object)");
    private static final Signature STRING_LENGTH =
      TypeUtils.parseSignature("int length()");
    private static final Signature STRING_CHAR_AT =
      TypeUtils.parseSignature("char charAt(int)");
    private static final Signature FOR_NAME =
      TypeUtils.parseSignature("Class forName(String)");
    private static final Signature DOUBLE_TO_LONG_BITS =
      TypeUtils.parseSignature("long doubleToLongBits(double)");
    private static final Signature FLOAT_TO_INT_BITS =
      TypeUtils.parseSignature("int floatToIntBits(float)");
    private static final Signature TO_STRING =
      TypeUtils.parseSignature("String toString()");
    private static final Signature APPEND_STRING =
      TypeUtils.parseSignature("StringBuffer append(String)");
    private static final Signature APPEND_INT =
      TypeUtils.parseSignature("StringBuffer append(int)");
    private static final Signature APPEND_DOUBLE =
      TypeUtils.parseSignature("StringBuffer append(double)");
    private static final Signature APPEND_FLOAT =
      TypeUtils.parseSignature("StringBuffer append(float)");
    private static final Signature APPEND_CHAR =
      TypeUtils.parseSignature("StringBuffer append(char)");
    private static final Signature APPEND_LONG =
      TypeUtils.parseSignature("StringBuffer append(long)");
    private static final Signature APPEND_BOOLEAN =
      TypeUtils.parseSignature("StringBuffer append(boolean)");
    private static final Signature LENGTH =
      TypeUtils.parseSignature("int length()");
    private static final Signature SET_LENGTH =
      TypeUtils.parseSignature("void setLength(int)");
    private static final Signature GET_DECLARED_METHOD =
      TypeUtils.parseSignature("java.lang.reflect.Method getDeclaredMethod(String, Class[])");

    public static final ArrayDelimiters DEFAULT_DELIMITERS = new ArrayDelimiters("{", ", ", "}");

    private EmitUtils() {
    }

    public static void factory_method(ClassEmitter ce, Signature sig) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, sig, null);
        e.new_instance_this();
        e.dup();
        e.load_args();
        e.invoke_constructor_this(TypeUtils.parseConstructor(sig.getArgumentTypes()));
        e.return_value();
        e.end_method();
    }

    public static void null_constructor(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, CSTRUCT_NULL, null);
        e.load_this();
        e.super_invoke_constructor();
        e.return_value();
        e.end_method();
    }

    /**
     * Process an array on the stack. Assumes the top item on the stack
     * is an array of the specified type. For each element in the array,
     * puts the element on the stack and triggers the callback.
     * @param type the type of the array (type.isArray() must be true)
     * @param callback the callback triggered for each element
     */
    public static void process_array(CodeEmitter e, Type type, ProcessArrayCallback callback) {
        Type componentType = TypeUtils.getComponentType(type);
        Local array = e.make_local();
        Local loopvar = e.make_local(Type.INT_TYPE);
        Label loopbody = e.make_label();
        Label checkloop = e.make_label();
        e.store_local(array);
        e.push(0);
        e.store_local(loopvar);
        e.goTo(checkloop);

        e.mark(loopbody);
        e.load_local(array);
        e.load_local(loopvar);
        e.array_load(componentType);
        callback.processElement(componentType);
        e.iinc(loopvar, 1);

        e.mark(checkloop);
        e.load_local(loopvar);
        e.load_local(array);
        e.arraylength();
        e.if_icmp(e.LT, loopbody);
    }

    /**
     * Process two arrays on the stack in parallel. Assumes the top two items on the stack
     * are arrays of the specified class. The arrays must be the same length. For each pair
     * of elements in the arrays, puts the pair on the stack and triggers the callback.
     * @param type the type of the arrays (type.isArray() must be true)
     * @param callback the callback triggered for each pair of elements
     */
    public static void process_arrays(CodeEmitter e, Type type, ProcessArrayCallback callback) {
        Type componentType = TypeUtils.getComponentType(type);
        Local array1 = e.make_local();
        Local array2 = e.make_local();
        Local loopvar = e.make_local(Type.INT_TYPE);
        Label loopbody = e.make_label();
        Label checkloop = e.make_label();
        e.store_local(array1);
        e.store_local(array2);
        e.push(0);
        e.store_local(loopvar);
        e.goTo(checkloop);

        e.mark(loopbody);
        e.load_local(array1);
        e.load_local(loopvar);
        e.array_load(componentType);
        e.load_local(array2);
        e.load_local(loopvar);
        e.array_load(componentType);
        callback.processElement(componentType);
        e.iinc(loopvar, 1);

        e.mark(checkloop);
        e.load_local(loopvar);
        e.load_local(array1);
        e.arraylength();
        e.if_icmp(e.LT, loopbody);
    }

    public static void string_switch(CodeEmitter e, String[] strings, int switchStyle, ObjectSwitchCallback callback) {
        try {
            switch (switchStyle) {
            case Constants.SWITCH_STYLE_TRIE:
                string_switch_trie(e, strings, callback);
                break;
            case Constants.SWITCH_STYLE_HASH:
                string_switch_hash(e, strings, callback, false);
                break;
            case Constants.SWITCH_STYLE_HASHONLY:
                string_switch_hash(e, strings, callback, true);
                break;
            default:
                throw new IllegalArgumentException("unknown switch style " + switchStyle);
            }
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Error ex) {
            throw ex;
        } catch (Exception ex) {
            throw new CodeGenerationException(ex);
        }
    }

    private static void string_switch_trie(final CodeEmitter e,
                                           String[] strings,
                                           final ObjectSwitchCallback callback) throws Exception {
        final Label def = e.make_label();
        final Label end = e.make_label();
        final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() {
            public Object transform(Object value) {
                return new Integer(((String)value).length());
            }
        });
        e.dup();
        e.invoke_virtual(Constants.TYPE_STRING, STRING_LENGTH);
        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
                public void processCase(int key, Label ignore_end) throws Exception {
                    List bucket = (List)buckets.get(new Integer(key));
                    stringSwitchHelper(e, bucket, callback, def, end, 0);
                }
                public void processDefault() {
                    e.goTo(def);
                }
            });
        e.mark(def);
        e.pop();
        callback.processDefault();
        e.mark(end);
    }

    private static void stringSwitchHelper(final CodeEmitter e,
                                           List strings,
                                           final ObjectSwitchCallback callback,
                                           final Label def,
                                           final Label end,
                                           final int index) throws Exception {
        final int len = ((String)strings.get(0)).length();
        final Map buckets = CollectionUtils.bucket(strings, new Transformer() {
            public Object transform(Object value) {
                return new Integer(((String)value).charAt(index));
            }
        });
        e.dup();
        e.push(index);
        e.invoke_virtual(Constants.TYPE_STRING, STRING_CHAR_AT);
        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
                public void processCase(int key, Label ignore_end) throws Exception {
                    List bucket = (List)buckets.get(new Integer(key));
                    if (index + 1 == len) {
                        e.pop();
                        callback.processCase(bucket.get(0), end);
                    } else {
                        stringSwitchHelper(e, bucket, callback, def, end, index + 1);
                    }
                }
                public void processDefault() {
                    e.goTo(def);
                }
            });
    }

    static int[] getSwitchKeys(Map buckets) {
        int[] keys = new int[buckets.size()];
        int index = 0;
        for (Iterator it = buckets.keySet().iterator(); it.hasNext();) {
            keys[index++] = ((Integer)it.next()).intValue();
        }
        Arrays.sort(keys);
        return keys;
    }

    private static void string_switch_hash(final CodeEmitter e,
                                           final String[] strings,
                                           final ObjectSwitchCallback callback,
                                           final boolean skipEquals) throws Exception {
        final Map buckets = CollectionUtils.bucket(Arrays.asList(strings), new Transformer() {
            public Object transform(Object value) {
                return new Integer(value.hashCode());
            }
        });
        final Label def = e.make_label();
        final Label end = e.make_label();
        e.dup();
        e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE);
        e.process_switch(getSwitchKeys(buckets), new ProcessSwitchCallback() {
            public void processCase(int key, Label ignore_end) throws Exception {
                List bucket = (List)buckets.get(new Integer(key));
                Label next = null;
                if (skipEquals && bucket.size() == 1) {
                    if (skipEquals)
                        e.pop();
                    callback.processCase((String)bucket.get(0), end);
                } else {
                    for (Iterator it = bucket.iterator(); it.hasNext();) {
                        String string = (String)it.next();
                        if (next != null) {
                            e.mark(next);
                        }
                        if (it.hasNext()) {
                            e.dup();
                        }
                        e.push(string);
                        e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
                        if (it.hasNext()) {
                            e.if_jump(e.EQ, next = e.make_label());
                            e.pop();
                        } else {
                            e.if_jump(e.EQ, def);
                        }
                        callback.processCase(string, end);
                    }
                }
            }
            public void processDefault() {
                e.pop();
            }
        });
        e.mark(def);
        callback.processDefault();
        e.mark(end);
    }

    public static void load_class_this(CodeEmitter e) {
        load_class_helper(e, e.getClassEmitter().getClassType());
    }

    public static void load_class(CodeEmitter e, Type type) {
        if (TypeUtils.isPrimitive(type)) {
            if (type == Type.VOID_TYPE) {
                throw new IllegalArgumentException("cannot load void type");
            }
            e.getstatic(TypeUtils.getBoxedType(type), "TYPE", Constants.TYPE_CLASS);
        } else {
            load_class_helper(e, type);
        }
    }

    private static void load_class_helper(CodeEmitter e, final Type type) {
        if (e.isStaticHook()) {
            // have to fall back on non-optimized load
            e.push(TypeUtils.emulateClassGetName(type));
            e.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
        } else {
            ClassEmitter ce = e.getClassEmitter();
            String typeName = TypeUtils.emulateClassGetName(type);

            // TODO: can end up with duplicated field names when using chained transformers; incorporate static hook # somehow
            String fieldName = "CGLIB$load_class$" + TypeUtils.escapeType(typeName);
            if (!ce.isFieldDeclared(fieldName)) {
                ce.declare_field(Constants.PRIVATE_FINAL_STATIC, fieldName, Constants.TYPE_CLASS, null);
                CodeEmitter hook = ce.getStaticHook();
                hook.push(typeName);
                hook.invoke_static(Constants.TYPE_CLASS, FOR_NAME);
                hook.putstatic(ce.getClassType(), fieldName, Constants.TYPE_CLASS);
            }
            e.getfield(fieldName);
        }
    }

    public static void push_array(CodeEmitter e, Object[] array) {
        e.push(array.length);
        e.newarray(Type.getType(remapComponentType(array.getClass().getComponentType())));
        for (int i = 0; i < array.length; i++) {
            e.dup();
            e.push(i);
            push_object(e, array[i]);
            e.aastore();
        }
    }

    private static Class remapComponentType(Class componentType) {
        if (componentType.equals(Type.class))
            return Class.class;
        return componentType;
    }

    public static void push_object(CodeEmitter e, Object obj) {
        if (obj == null) {
            e.aconst_null();
        } else {
            Class type = obj.getClass();
            if (type.isArray()) {
                push_array(e, (Object[])obj);
            } else if (obj instanceof String) {
                e.push((String)obj);
            } else if (obj instanceof Type) {
                load_class(e, (Type)obj);
            } else if (obj instanceof Class) {
                load_class(e, Type.getType((Class)obj));
            } else if (obj instanceof BigInteger) {
                e.new_instance(Constants.TYPE_BIG_INTEGER);
                e.dup();
                e.push(obj.toString());
                e.invoke_constructor(Constants.TYPE_BIG_INTEGER);
            } else if (obj instanceof BigDecimal) {
                e.new_instance(Constants.TYPE_BIG_DECIMAL);
                e.dup();
                e.push(obj.toString());
                e.invoke_constructor(Constants.TYPE_BIG_DECIMAL);
            } else {
                throw new IllegalArgumentException("unknown type: " + obj.getClass());
            }
        }
    }

    /**
     * @deprecated use {@link #hash_code(CodeEmitter, Type, int, CustomizerRegistry)} instead
     */
    @Deprecated
    public static void hash_code(CodeEmitter e, Type type, int multiplier, final Customizer customizer) {
    	hash_code(e, type, multiplier, CustomizerRegistry.singleton(customizer));
    }

    public static void hash_code(CodeEmitter e, Type type, int multiplier, final CustomizerRegistry registry) {
        if (TypeUtils.isArray(type)) {
            hash_array(e, type, multiplier, registry);
        } else {
            e.swap(Type.INT_TYPE, type);
            e.push(multiplier);
            e.math(e.MUL, Type.INT_TYPE);
            e.swap(type, Type.INT_TYPE);
            if (TypeUtils.isPrimitive(type)) {
                hash_primitive(e, type);
            } else {
                hash_object(e, type, registry);
            }
            e.math(e.ADD, Type.INT_TYPE);
        }
    }

    private static void hash_array(final CodeEmitter e, Type type, final int multiplier, final CustomizerRegistry registry) {
        Label skip = e.make_label();
        Label end = e.make_label();
        e.dup();
        e.ifnull(skip);
        EmitUtils.process_array(e, type, new ProcessArrayCallback() {
            public void processElement(Type type) {
                hash_code(e, type, multiplier, registry);
            }
        });
        e.goTo(end);
        e.mark(skip);
        e.pop();
        e.mark(end);
    }

    private static void hash_object(CodeEmitter e, Type type, CustomizerRegistry registry) {
        // (f == null) ? 0 : f.hashCode();
        Label skip = e.make_label();
        Label end = e.make_label();
        e.dup();
        e.ifnull(skip);
        boolean customHashCode = false;
        for (HashCodeCustomizer customizer : registry.get(HashCodeCustomizer.class)) {
            if (customizer.customize(e, type)) {
                customHashCode = true;
                break;
            }
        }
        if (!customHashCode) {
            for (Customizer customizer : registry.get(Customizer.class)) {
                customizer.customize(e, type);
            }
            e.invoke_virtual(Constants.TYPE_OBJECT, HASH_CODE);
        }
        e.goTo(end);
        e.mark(skip);
        e.pop();
        e.push(0);
        e.mark(end);
    }

    private static void hash_primitive(CodeEmitter e, Type type) {
        switch (type.getSort()) {
        case Type.BOOLEAN:
            // f ? 0 : 1
            e.push(1);
            e.math(e.XOR, Type.INT_TYPE);
            break;
        case Type.FLOAT:
            // Float.floatToIntBits(f)
            e.invoke_static(Constants.TYPE_FLOAT, FLOAT_TO_INT_BITS);
            break;
        case Type.DOUBLE:
            // Double.doubleToLongBits(f), hash_code(Long.TYPE)
            e.invoke_static(Constants.TYPE_DOUBLE, DOUBLE_TO_LONG_BITS);
            // fall through
        case Type.LONG:
            hash_long(e);
        }
    }

    private static void hash_long(CodeEmitter e) {
        // (int)(f ^ (f >>> 32))
        e.dup2();
        e.push(32);
        e.math(e.USHR, Type.LONG_TYPE);
        e.math(e.XOR, Type.LONG_TYPE);
        e.cast_numeric(Type.LONG_TYPE, Type.INT_TYPE);
    }

//     public static void not_equals(CodeEmitter e, Type type, Label notEquals) {
//         not_equals(e, type, notEquals, null);
//     }

    /**
     * @deprecated use {@link #not_equals(CodeEmitter, Type, Label, CustomizerRegistry)} instead
     */
    @Deprecated
    public static void not_equals(CodeEmitter e, Type type, final Label notEquals, final Customizer customizer) {
    	not_equals(e, type, notEquals, CustomizerRegistry.singleton(customizer));
    }

    /**
     * Branches to the specified label if the top two items on the stack
     * are not equal. The items must both be of the specified
     * class. Equality is determined by comparing primitive values
     * directly and by invoking the equals method for
     * Objects. Arrays are recursively processed in the same manner.
     */
    public static void not_equals(final CodeEmitter e, Type type, final Label notEquals, final CustomizerRegistry registry) {
        (new ProcessArrayCallback() {
            public void processElement(Type type) {
                not_equals_helper(e, type, notEquals, registry, this);
            }
        }).processElement(type);
    }

    private static void not_equals_helper(CodeEmitter e,
                                          Type type,
                                          Label notEquals,
                                          CustomizerRegistry registry,
                                          ProcessArrayCallback callback) {
        if (TypeUtils.isPrimitive(type)) {
            e.if_cmp(type, e.NE, notEquals);
        } else {
            Label end = e.make_label();
            nullcmp(e, notEquals, end);
            if (TypeUtils.isArray(type)) {
                Label checkContents = e.make_label();
                e.dup2();
                e.arraylength();
                e.swap();
                e.arraylength();
                e.if_icmp(e.EQ, checkContents);
                e.pop2();
                e.goTo(notEquals);
                e.mark(checkContents);
                EmitUtils.process_arrays(e, type, callback);
            } else {
                List<Customizer> customizers = registry.get(Customizer.class);
                if (!customizers.isEmpty()) {
                    for (Customizer customizer : customizers) {
                        customizer.customize(e, type);
                    }
                    e.swap();
                    for (Customizer customizer : customizers) {
                        customizer.customize(e, type);
                    }
                }
                e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
                e.if_jump(e.EQ, notEquals);
            }
            e.mark(end);
        }
    }

    /**
     * If both objects on the top of the stack are non-null, does nothing.
     * If one is null, or both are null, both are popped off and execution
     * branches to the respective label.
     * @param oneNull label to branch to if only one of the objects is null
     * @param bothNull label to branch to if both of the objects are null
     */
    private static void nullcmp(CodeEmitter e, Label oneNull, Label bothNull) {
        e.dup2();
        Label nonNull = e.make_label();
        Label oneNullHelper = e.make_label();
        Label end = e.make_label();
        e.ifnonnull(nonNull);
        e.ifnonnull(oneNullHelper);
        e.pop2();
        e.goTo(bothNull);

        e.mark(nonNull);
        e.ifnull(oneNullHelper);
        e.goTo(end);

        e.mark(oneNullHelper);
        e.pop2();
        e.goTo(oneNull);

        e.mark(end);
    }

    /*
    public static void to_string(CodeEmitter e,
                                 Type type,
                                 ArrayDelimiters delims,
                                 CustomizerRegistry registry) {
        e.new_instance(Constants.TYPE_STRING_BUFFER);
        e.dup();
        e.invoke_constructor(Constants.TYPE_STRING_BUFFER);
        e.swap();
        append_string(e, type, delims, registry);
        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, TO_STRING);
    }
    */

    /**
      * @deprecated use {@link #append_string(CodeEmitter, Type, ArrayDelimiters, CustomizerRegistry)} instead
      */
    @Deprecated
    public static void append_string(final CodeEmitter e,
                                     Type type,
                                     final ArrayDelimiters delims,
                                     final Customizer customizer) {
        append_string(e, type, delims, CustomizerRegistry.singleton(customizer));
    }

    public static void append_string(final CodeEmitter e,
                                     Type type,
                                     final ArrayDelimiters delims,
                                     final CustomizerRegistry registry) {
        final ArrayDelimiters d = (delims != null) ? delims : DEFAULT_DELIMITERS;
        ProcessArrayCallback callback = new ProcessArrayCallback() {
            public void processElement(Type type) {
                append_string_helper(e, type, d, registry, this);
                e.push(d.inside);
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
            }
        };
        append_string_helper(e, type, d, registry, callback);
    }

    private static void append_string_helper(CodeEmitter e,
                                             Type type,
                                             ArrayDelimiters delims,
                                             CustomizerRegistry registry,
                                             ProcessArrayCallback callback) {
        Label skip = e.make_label();
        Label end = e.make_label();
        if (TypeUtils.isPrimitive(type)) {
            switch (type.getSort()) {
            case Type.INT:
            case Type.SHORT:
            case Type.BYTE:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_INT);
                break;
            case Type.DOUBLE:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_DOUBLE);
                break;
            case Type.FLOAT:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_FLOAT);
                break;
            case Type.LONG:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_LONG);
                break;
            case Type.BOOLEAN:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_BOOLEAN);
                break;
            case Type.CHAR:
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_CHAR);
                break;
            }
        } else if (TypeUtils.isArray(type)) {
            e.dup();
            e.ifnull(skip);
            e.swap();
            if (delims != null && delims.before != null && !"".equals(delims.before)) {
                e.push(delims.before);
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
                e.swap();
            }
            EmitUtils.process_array(e, type, callback);
            shrinkStringBuffer(e, 2);
            if (delims != null && delims.after != null && !"".equals(delims.after)) {
                e.push(delims.after);
                e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
            }
        } else {
            e.dup();
            e.ifnull(skip);
            for (Customizer customizer : registry.get(Customizer.class)) {
                customizer.customize(e, type);
            }
            e.invoke_virtual(Constants.TYPE_OBJECT, TO_STRING);
            e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
        }
        e.goTo(end);
        e.mark(skip);
        e.pop();
        e.push("null");
        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, APPEND_STRING);
        e.mark(end);
    }

    private static void shrinkStringBuffer(CodeEmitter e, int amt) {
        e.dup();
        e.dup();
        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, LENGTH);
        e.push(amt);
        e.math(e.SUB, Type.INT_TYPE);
        e.invoke_virtual(Constants.TYPE_STRING_BUFFER, SET_LENGTH);
    }

    public static class ArrayDelimiters {
        private String before;
        private String inside;
        private String after;

        public ArrayDelimiters(String before, String inside, String after) {
            this.before = before;
            this.inside = inside;
            this.after = after;
        }
    }

    public static void load_method(CodeEmitter e, MethodInfo method) {
        load_class(e, method.getClassInfo().getType());
        e.push(method.getSignature().getName());
        push_object(e, method.getSignature().getArgumentTypes());
        e.invoke_virtual(Constants.TYPE_CLASS, GET_DECLARED_METHOD);
    }

    private interface ParameterTyper {
        Type[] getParameterTypes(MethodInfo member);
    }

    public static void method_switch(CodeEmitter e,
                                     List methods,
                                     ObjectSwitchCallback callback) {
        member_switch_helper(e, methods, callback, true);
    }

    public static void constructor_switch(CodeEmitter e,
                                          List constructors,
                                          ObjectSwitchCallback callback) {
        member_switch_helper(e, constructors, callback, false);
    }

    private static void member_switch_helper(final CodeEmitter e,
                                             List members,
                                             final ObjectSwitchCallback callback,
                                             boolean useName) {
        try {
            final Map cache = new HashMap();
            final ParameterTyper cached = new ParameterTyper() {
                    public Type[] getParameterTypes(MethodInfo member) {
                        Type[] types = (Type[])cache.get(member);
                        if (types == null) {
                            cache.put(member, types = member.getSignature().getArgumentTypes());
                        }
                        return types;
                    }
                };
            final Label def = e.make_label();
            final Label end = e.make_label();
            if (useName) {
                e.swap();
                final Map buckets = CollectionUtils.bucket(members, new Transformer() {
                        public Object transform(Object value) {
                            return ((MethodInfo)value).getSignature().getName();
                        }
                    });
                String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]);
                EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
                        public void processCase(Object key, Label dontUseEnd) throws Exception {
                            member_helper_size(e, (List)buckets.get(key), callback, cached, def, end);
                        }
                        public void processDefault() throws Exception {
                            e.goTo(def);
                        }
                    });
            } else {
                member_helper_size(e, members, callback, cached, def, end);
            }
            e.mark(def);
            e.pop();
            callback.processDefault();
            e.mark(end);
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Error ex) {
            throw ex;
        } catch (Exception ex) {
            throw new CodeGenerationException(ex);
        }
    }

    private static void member_helper_size(final CodeEmitter e,
                                           List members,
                                           final ObjectSwitchCallback callback,
                                           final ParameterTyper typer,
                                           final Label def,
                                           final Label end) throws Exception {
        final Map buckets = CollectionUtils.bucket(members, new Transformer() {
            public Object transform(Object value) {
                return new Integer(typer.getParameterTypes((MethodInfo)value).length);
            }
        });
        e.dup();
        e.arraylength();
        e.process_switch(EmitUtils.getSwitchKeys(buckets), new ProcessSwitchCallback() {
            public void processCase(int key, Label dontUseEnd) throws Exception {
                List bucket = (List)buckets.get(new Integer(key));
                member_helper_type(e, bucket, callback, typer, def, end, new BitSet());
            }
            public void processDefault() throws Exception {
                e.goTo(def);
            }
        });
    }

    private static void member_helper_type(final CodeEmitter e,
                                           List members,
                                           final ObjectSwitchCallback callback,
                                           final ParameterTyper typer,
                                           final Label def,
                                           final Label end,
                                           final BitSet checked) throws Exception {
        if (members.size() == 1) {
            MethodInfo member = (MethodInfo)members.get(0);
            Type[] types = typer.getParameterTypes(member);
            // need to check classes that have not already been checked via switches
            for (int i = 0; i < types.length; i++) {
                if (checked == null || !checked.get(i)) {
                    e.dup();
                    e.aaload(i);
                    e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME);
                    e.push(TypeUtils.emulateClassGetName(types[i]));
                    e.invoke_virtual(Constants.TYPE_OBJECT, EQUALS);
                    e.if_jump(e.EQ, def);
                }
            }
            e.pop();
            callback.processCase(member, end);
        } else {
            // choose the index that has the best chance of uniquely identifying member
            Type[] example = typer.getParameterTypes((MethodInfo)members.get(0));
            Map buckets = null;
            int index = -1;
            for (int i = 0; i < example.length; i++) {
                final int j = i;
                Map test = CollectionUtils.bucket(members, new Transformer() {
                    public Object transform(Object value) {
                        return TypeUtils.emulateClassGetName(typer.getParameterTypes((MethodInfo)value)[j]);
                    }
                });
                if (buckets == null || test.size() > buckets.size()) {
                    buckets = test;
                    index = i;
                }
            }
            if (buckets == null || buckets.size() == 1) {
                // TODO: switch by returnType
                // must have two methods with same name, types, and different return types
                e.goTo(def);
            } else {
                checked.set(index);

                e.dup();
                e.aaload(index);
                e.invoke_virtual(Constants.TYPE_CLASS, GET_NAME);

                final Map fbuckets = buckets;
                String[] names = (String[])buckets.keySet().toArray(new String[buckets.size()]);
                EmitUtils.string_switch(e, names, Constants.SWITCH_STYLE_HASH, new ObjectSwitchCallback() {
                    public void processCase(Object key, Label dontUseEnd) throws Exception {
                        member_helper_type(e, (List)fbuckets.get(key), callback, typer, def, end, checked);
                    }
                    public void processDefault() throws Exception {
                        e.goTo(def);
                    }
                });
            }
        }
    }

    public static void wrap_throwable(Block block, Type wrapper) {
        CodeEmitter e = block.getCodeEmitter();
        e.catch_exception(block, Constants.TYPE_THROWABLE);
        e.new_instance(wrapper);
        e.dup_x1();
        e.swap();
        e.invoke_constructor(wrapper, CSTRUCT_THROWABLE);
        e.athrow();
    }

    public static void add_properties(ClassEmitter ce, String[] names, Type[] types) {
        for (int i = 0; i < names.length; i++) {
            String fieldName = "$cglib_prop_" + names[i];
            ce.declare_field(Constants.ACC_PRIVATE, fieldName, types[i], null);
            EmitUtils.add_property(ce, names[i], types[i], fieldName);
        }
    }

    public static void add_property(ClassEmitter ce, String name, Type type, String fieldName) {
        String property = TypeUtils.upperFirst(name);
        CodeEmitter e;
        e = ce.begin_method(Constants.ACC_PUBLIC,
                            new Signature("get" + property,
                                          type,
                                          Constants.TYPES_EMPTY),
                            null);
        e.load_this();
        e.getfield(fieldName);
        e.return_value();
        e.end_method();

        e = ce.begin_method(Constants.ACC_PUBLIC,
                            new Signature("set" + property,
                                          Type.VOID_TYPE,
                                          new Type[]{ type }),
                            null);
        e.load_this();
        e.load_arg(0);
        e.putfield(fieldName);
        e.return_value();
        e.end_method();
    }

    /* generates:
       } catch (RuntimeException e) {
         throw e;
       } catch (Error e) {
         throw e;
       } catch (<DeclaredException> e) {
         throw e;
       } catch (Throwable e) {
         throw new <Wrapper>(e);
       }
    */
    public static void wrap_undeclared_throwable(CodeEmitter e, Block handler, Type[] exceptions, Type wrapper) {
        Set set = (exceptions == null) ? Collections.EMPTY_SET : new HashSet(Arrays.asList(exceptions));

        if (set.contains(Constants.TYPE_THROWABLE))
            return;

        boolean needThrow = exceptions != null;
        if (!set.contains(Constants.TYPE_RUNTIME_EXCEPTION)) {
            e.catch_exception(handler, Constants.TYPE_RUNTIME_EXCEPTION);
            needThrow = true;
        }
        if (!set.contains(Constants.TYPE_ERROR)) {
            e.catch_exception(handler, Constants.TYPE_ERROR);
            needThrow = true;
        }
        if (exceptions != null) {
            for (int i = 0; i < exceptions.length; i++) {
                e.catch_exception(handler, exceptions[i]);
            }
        }
        if (needThrow) {
            e.athrow();
        }
        // e -> eo -> oeo -> ooe -> o
        e.catch_exception(handler, Constants.TYPE_THROWABLE);
        e.new_instance(wrapper);
        e.dup_x1();
        e.swap();
        e.invoke_constructor(wrapper, CSTRUCT_THROWABLE);
        e.athrow();
    }

    public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method) {
        return begin_method(e, method, method.getModifiers());
    }

    public static CodeEmitter begin_method(ClassEmitter e, MethodInfo method, int access) {
        return e.begin_method(access,
                              method.getSignature(),
                              method.getExceptionTypes());
    }
}


/*
 * Copyright 2011 The Apache Software Foundation
 *
 *  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.sf.cglib.proxy;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import net.sf.cglib.core.Constants;
import net.sf.cglib.core.Signature;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Uses bytecode reflection to figure out the targets of all bridge methods that use invokespecial
 * and invokeinterface, so that we can later rewrite them to use invokevirtual.
 *
 * <p>For interface bridges, using invokesuper will fail since the method being bridged to is in a
 * superinterface, not a superclass. Starting in Java 8, javac emits default bridge methods in
 * interfaces, which use invokeinterface to bridge to the target method.
 *
 * @author sberlin@gmail.com (Sam Berlin)
 */
class BridgeMethodResolver {

    private final Map/* <Class, Set<Signature> */declToBridge;
    private final ClassLoader classLoader;

    public BridgeMethodResolver(Map declToBridge, ClassLoader classLoader) {
        this.declToBridge = declToBridge;
        this.classLoader = classLoader;
    }

    /**
     * Finds all bridge methods that are being called with invokespecial &
     * returns them.
     */
    public Map/*<Signature, Signature>*/resolveAll() {
        Map resolved = new HashMap();
        for (Iterator entryIter = declToBridge.entrySet().iterator(); entryIter.hasNext(); ) {
            Map.Entry entry = (Map.Entry) entryIter.next();
            Class owner = (Class) entry.getKey();
            Set bridges = (Set) entry.getValue();
            try {
                InputStream is = classLoader.getResourceAsStream(owner.getName().replace('.', '/') + ".class");
                if (is == null) {
                    return resolved;
                }
                try {
                    new ClassReader(is)
                            .accept(new BridgedFinder(bridges, resolved),
                                    ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
                } finally {
                    is.close();
                }
            } catch (IOException ignored) {}
        }
        return resolved;
    }

    private static class BridgedFinder extends ClassVisitor {
        private Map/*<Signature, Signature>*/ resolved;
        private Set/*<Signature>*/ eligibleMethods;

        private Signature currentMethod = null;

        BridgedFinder(Set eligibleMethods, Map resolved) {
            super(Constants.ASM_API);
            this.resolved = resolved;
            this.eligibleMethods = eligibleMethods;
        }

        public void visit(int version, int access, String name,
                String signature, String superName, String[] interfaces) {
        }

        public MethodVisitor visitMethod(int access, String name, String desc,
                String signature, String[] exceptions) {
            Signature sig = new Signature(name, desc);
            if (eligibleMethods.remove(sig)) {
                currentMethod = sig;
                return new MethodVisitor(Constants.ASM_API) {
                    public void visitMethodInsn(
                            int opcode, String owner, String name, String desc, boolean itf) {
                        if ((opcode == Opcodes.INVOKESPECIAL
                                        || (itf && opcode == Opcodes.INVOKEINTERFACE))
                                && currentMethod != null) {
                            Signature target = new Signature(name, desc);
                            // If the target signature is the same as the current,
                            // we shouldn't change our bridge becaues invokespecial
                            // is the only way to make progress (otherwise we'll
                            // get infinite recursion).  This would typically
                            // only happen when a bridge method is created to widen
                            // the visibility of a superclass' method.
                            if (!target.equals(currentMethod)) {
                                resolved.put(currentMethod, target);
                            }
                            currentMethod = null;
                        }
                    }
                };
            } else {
                return null;
            }
        }
    }

}


/*
 * Copyright 2002,2003,2004 The Apache Software Foundation
 *
 *  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.sf.cglib.proxy;

import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.*;

import net.sf.cglib.core.*;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.Label;

/**
 * Generates dynamic subclasses to enable method interception. This
 * class started as a substitute for the standard Dynamic Proxy support
 * included with JDK 1.3, but one that allowed the proxies to extend a
 * concrete base class, in addition to implementing interfaces. The dynamically
 * generated subclasses override the non-final methods of the superclass and
 * have hooks which callback to user-defined interceptor
 * implementations.
 *

 * The original and most general callback type is the {@link MethodInterceptor}, which
 * in AOP terms enables "around advice"--that is, you can invoke custom code both before
 * and after the invocation of the "super" method. In addition you can modify the
 * arguments before calling the super method, or not call it at all.
 *

 * Although MethodInterceptor is generic enough to meet any
 * interception need, it is often overkill. For simplicity and performance, additional
 * specialized callback types, such as {@link LazyLoader} are also available.
 * Often a single callback will be used per enhanced class, but you can control
 * which callback is used on a per-method basis with a {@link CallbackFilter}.
 *

 * The most common uses of this class are embodied in the static helper methods. For
 * advanced needs, such as customizing the ClassLoader to use, you should create
 * a new instance of Enhancer. Other classes within CGLIB follow a similar pattern.
 *

 * All enhanced objects implement the {@link Factory} interface, unless {@link #setUseFactory} is
 * used to explicitly disable this feature. The Factory interface provides an API
 * to change the callbacks of an existing object, as well as a faster and easier way to create
 * new instances of the same type.
 *

 * For an almost drop-in replacement for
 * java.lang.reflect.Proxy, see the {@link Proxy} class.
 */
public class Enhancer extends AbstractClassGenerator
{
    private static final CallbackFilter ALL_ZERO = new CallbackFilter(){
        public int accept(Method method) {
            return 0;
        }
    };

    private static final Source SOURCE = new Source(Enhancer.class.getName());
    private static final EnhancerKey KEY_FACTORY =
      (EnhancerKey)KeyFactory.create(EnhancerKey.class, KeyFactory.HASH_ASM_TYPE, null);

    private static final String BOUND_FIELD = "CGLIB$BOUND";
    private static final String FACTORY_DATA_FIELD = "CGLIB$FACTORY_DATA";
    private static final String THREAD_CALLBACKS_FIELD = "CGLIB$THREAD_CALLBACKS";
    private static final String STATIC_CALLBACKS_FIELD = "CGLIB$STATIC_CALLBACKS";
    private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS";
    private static final String SET_STATIC_CALLBACKS_NAME = "CGLIB$SET_STATIC_CALLBACKS";
    private static final String CONSTRUCTED_FIELD = "CGLIB$CONSTRUCTED";
    /**
     * {@link net.sf.cglib.core.AbstractClassGenerator.ClassLoaderData#generatedClasses} requires to keep cache key
     * in a good shape (the keys should be up and running if the proxy class is alive), and one of the cache keys is
     * {@link CallbackFilter}. That is why the generated class contains static field that keeps strong reference to
     * the {@link #filter}.
     *
This dance achieves two goals: ensures generated class is reusable and available through generatedClasses
     * cache, and it enables to unload classloader and the related {@link CallbackFilter} in case user does not need
     * that

     */
    private static final String CALLBACK_FILTER_FIELD = "CGLIB$CALLBACK_FILTER";

    private static final Type OBJECT_TYPE =
      TypeUtils.parseType("Object");
    private static final Type FACTORY =
      TypeUtils.parseType("net.sf.cglib.proxy.Factory");
    private static final Type ILLEGAL_STATE_EXCEPTION =
      TypeUtils.parseType("IllegalStateException");
    private static final Type ILLEGAL_ARGUMENT_EXCEPTION =
      TypeUtils.parseType("IllegalArgumentException");
    private static final Type THREAD_LOCAL =
      TypeUtils.parseType("ThreadLocal");
    private static final Type CALLBACK =
      TypeUtils.parseType("net.sf.cglib.proxy.Callback");
    private static final Type CALLBACK_ARRAY =
      Type.getType(Callback[].class);
    private static final Signature CSTRUCT_NULL =
      TypeUtils.parseConstructor("");
    private static final Signature SET_THREAD_CALLBACKS =
      new Signature(SET_THREAD_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
    private static final Signature SET_STATIC_CALLBACKS =
      new Signature(SET_STATIC_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
    private static final Signature NEW_INSTANCE =
      new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK_ARRAY });
    private static final Signature MULTIARG_NEW_INSTANCE =
      new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{
          Constants.TYPE_CLASS_ARRAY,
          Constants.TYPE_OBJECT_ARRAY,
          CALLBACK_ARRAY,
      });
    private static final Signature SINGLE_NEW_INSTANCE =
      new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK });
    private static final Signature SET_CALLBACK =
      new Signature("setCallback", Type.VOID_TYPE, new Type[]{ Type.INT_TYPE, CALLBACK });
    private static final Signature GET_CALLBACK =
      new Signature("getCallback", CALLBACK, new Type[]{ Type.INT_TYPE });
    private static final Signature SET_CALLBACKS =
      new Signature("setCallbacks", Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
    private static final Signature GET_CALLBACKS =
      new Signature("getCallbacks", CALLBACK_ARRAY, new Type[0]);
    private static final Signature THREAD_LOCAL_GET =
      TypeUtils.parseSignature("Object get()");
    private static final Signature THREAD_LOCAL_SET =
      TypeUtils.parseSignature("void set(Object)");
    private static final Signature BIND_CALLBACKS =
      TypeUtils.parseSignature("void CGLIB$BIND_CALLBACKS(Object)");

    private EnhancerFactoryData currentData;
    private Object currentKey;

    /** Internal interface, only public due to ClassLoader issues. */
    public interface EnhancerKey {
        public Object newInstance(String type,
                                  String[] interfaces,
                                  WeakCacheKey<CallbackFilter> filter,
                                  Type[] callbackTypes,
                                  boolean useFactory,
                                  boolean interceptDuringConstruction,
                                  Long serialVersionUID);
    }

    private Class[] interfaces;
    private CallbackFilter filter;
    private Callback[] callbacks;
    private Type[] callbackTypes;
    private boolean validateCallbackTypes;
    private boolean classOnly;
    private Class superclass;
    private Class[] argumentTypes;
    private Object[] arguments;
    private boolean useFactory = true;
    private Long serialVersionUID;
    private boolean interceptDuringConstruction = true;

    /**
     * Create a new Enhancer. A new Enhancer
     * object should be used for each generated object, and should not
     * be shared across threads. To create additional instances of a
     * generated class, use the Factory interface.
     * @see Factory
     */
    public Enhancer() {
        super(SOURCE);
    }

    /**
     * Set the class which the generated class will extend. As a convenience,
     * if the supplied superclass is actually an interface, setInterfaces
     * will be called with the appropriate argument instead.
     * A non-interface argument must not be declared as final, and must have an
     * accessible constructor.
     * @param superclass class to extend or interface to implement
     * @see #setInterfaces(Class[])
     */
    public void setSuperclass(Class superclass) {
        if (superclass != null && superclass.isInterface()) {
            setInterfaces(new Class[]{ superclass });
        } else if (superclass != null && superclass.equals(Object.class)) {
            // affects choice of ClassLoader
            this.superclass = null;
        } else {
            this.superclass = superclass;
        }
    }

    /**
     * Set the interfaces to implement. The Factory interface will
     * always be implemented regardless of what is specified here.
     * @param interfaces array of interfaces to implement, or null
     * @see Factory
     */
    public void setInterfaces(Class[] interfaces) {
        this.interfaces = interfaces;
    }

    /**
     * Set the {@link CallbackFilter} used to map the generated class' methods
     * to a particular callback index.
     * New object instances will always use the same mapping, but may use different
     * actual callback objects.
     * @param filter the callback filter to use when generating a new class
     * @see #setCallbacks
     */
    public void setCallbackFilter(CallbackFilter filter) {
        this.filter = filter;
    }

    /**
     * Set the single {@link Callback} to use.
     * Ignored if you use {@link #createClass}.
     * @param callback the callback to use for all methods
     * @see #setCallbacks
     */
    public void setCallback(final Callback callback) {
        setCallbacks(new Callback[]{ callback });
    }

    /**
     * Set the array of callbacks to use.
     * Ignored if you use {@link #createClass}.
     * You must use a {@link CallbackFilter} to specify the index into this
     * array for each method in the proxied class.
     * @param callbacks the callback array
     * @see #setCallbackFilter
     * @see #setCallback
     */
    public void setCallbacks(Callback[] callbacks) {
        if (callbacks != null && callbacks.length == 0) {
            throw new IllegalArgumentException("Array cannot be empty");
        }
        this.callbacks = callbacks;
    }

    /**
     * Set whether the enhanced object instances should implement
     * the {@link Factory} interface.
     * This was added for tools that need for proxies to be more
     * indistinguishable from their targets. Also, in some cases it may
     * be necessary to disable the Factory interface to
     * prevent code from changing the underlying callbacks.
     * @param useFactory whether to implement Factory; default is true
     */
    public void setUseFactory(boolean useFactory) {
        this.useFactory = useFactory;
    }

    /**
     * Set whether methods called from within the proxy's constructer
     * will be intercepted. The default value is true. Unintercepted methods
     * will call the method of the proxy's base class, if it exists.
     * @param interceptDuringConstruction whether to intercept methods called from the constructor
     */
    public void setInterceptDuringConstruction(boolean interceptDuringConstruction) {
        this.interceptDuringConstruction = interceptDuringConstruction;
    }

    /**
     * Set the single type of {@link Callback} to use.
     * This may be used instead of {@link #setCallback} when calling
     * {@link #createClass}, since it may not be possible to have
     * an array of actual callback instances.
     * @param callbackType the type of callback to use for all methods
     * @see #setCallbackTypes
     */
    public void setCallbackType(Class callbackType) {
        setCallbackTypes(new Class[]{ callbackType });
    }

    /**
     * Set the array of callback types to use.
     * This may be used instead of {@link #setCallbacks} when calling
     * {@link #createClass}, since it may not be possible to have
     * an array of actual callback instances.
     * You must use a {@link CallbackFilter} to specify the index into this
     * array for each method in the proxied class.
     * @param callbackTypes the array of callback types
     */
    public void setCallbackTypes(Class[] callbackTypes) {
        if (callbackTypes != null && callbackTypes.length == 0) {
            throw new IllegalArgumentException("Array cannot be empty");
        }
        this.callbackTypes = CallbackInfo.determineTypes(callbackTypes);
    }

    /**
     * Generate a new class if necessary and uses the specified
     * callbacks (if any) to create a new object instance.
     * Uses the no-arg constructor of the superclass.
     * @return a new instance
     */
    public Object create() {
        classOnly = false;
        argumentTypes = null;
        return createHelper();
    }

    /**
     * Generate a new class if necessary and uses the specified
     * callbacks (if any) to create a new object instance.
     * Uses the constructor of the superclass matching the argumentTypes
     * parameter, with the given arguments.
     * @param argumentTypes constructor signature
     * @param arguments compatible wrapped arguments to pass to constructor
     * @return a new instance
     */
    public Object create(Class[] argumentTypes, Object[] arguments) {
        classOnly = false;
        if (argumentTypes == null || arguments == null || argumentTypes.length != arguments.length) {
            throw new IllegalArgumentException("Arguments must be non-null and of equal length");
        }
        this.argumentTypes = argumentTypes;
        this.arguments = arguments;
        return createHelper();
    }

    /**
     * Generate a new class if necessary and return it without creating a new instance.
     * This ignores any callbacks that have been set.
     * To create a new instance you will have to use reflection, and methods
     * called during the constructor will not be intercepted. To avoid this problem,
     * use the multi-arg create method.
     * @see #create(Class[], Object[])
     */
    public Class createClass() {
        classOnly = true;
        return (Class)createHelper();
    }

    /**
     * Insert a static serialVersionUID field into the generated class.
     * @param sUID the field value, or null to avoid generating field.
     */
    public void setSerialVersionUID(Long sUID) {
        serialVersionUID = sUID;
    }

    private void preValidate() {
        if (callbackTypes == null) {
            callbackTypes = CallbackInfo.determineTypes(callbacks, false);
            validateCallbackTypes = true;
        }
        if (filter == null) {
            if (callbackTypes.length > 1) {
                throw new IllegalStateException("Multiple callback types possible but no filter specified");
            }
            filter = ALL_ZERO;
        }
    }

    private void validate() {
        if (classOnly ^ (callbacks == null)) {
            if (classOnly) {
                throw new IllegalStateException("createClass does not accept callbacks");
            } else {
                throw new IllegalStateException("Callbacks are required");
            }
        }
        if (classOnly && (callbackTypes == null)) {
            throw new IllegalStateException("Callback types are required");
        }
        if (validateCallbackTypes) {
            callbackTypes = null;
        }
        if (callbacks != null && callbackTypes != null) {
            if (callbacks.length != callbackTypes.length) {
                throw new IllegalStateException("Lengths of callback and callback types array must be the same");
            }
            Type[] check = CallbackInfo.determineTypes(callbacks);
            for (int i = 0; i < check.length; i++) {
                if (!check[i].equals(callbackTypes[i])) {
                    throw new IllegalStateException("Callback " + check[i] + " is not assignable to " + callbackTypes[i]);
                }
            }
        } else if (callbacks != null) {
            callbackTypes = CallbackInfo.determineTypes(callbacks);
        }
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                if (interfaces[i] == null) {
                    throw new IllegalStateException("Interfaces cannot be null");
                }
                if (!interfaces[i].isInterface()) {
                    throw new IllegalStateException(interfaces[i] + " is not an interface");
                }
            }
        }
    }

    /**
     * The idea of the class is to cache relevant java.lang.reflect instances so
     * proxy-class can be instantiated faster that when using {@link ReflectUtils#newInstance(Class, Class[], Object[])}
     * and {@link Enhancer#setThreadCallbacks(Class, Callback[])}
     */
    static class EnhancerFactoryData {
        public final Class generatedClass;
        private final Method setThreadCallbacks;
        private final Class[] primaryConstructorArgTypes;
        private final Constructor primaryConstructor;

        public EnhancerFactoryData(Class generatedClass, Class[] primaryConstructorArgTypes, boolean classOnly) {
            this.generatedClass = generatedClass;
            try {
                setThreadCallbacks = getCallbacksSetter(generatedClass, SET_THREAD_CALLBACKS_NAME);
                if (classOnly) {
                    this.primaryConstructorArgTypes = null;
                    this.primaryConstructor = null;
                } else {
                    this.primaryConstructorArgTypes = primaryConstructorArgTypes;
                    this.primaryConstructor = ReflectUtils.getConstructor(generatedClass, primaryConstructorArgTypes);
                }
            } catch (NoSuchMethodException e) {
                throw new CodeGenerationException(e);
            }
        }

        /**
         * Creates proxy instance for given argument types, and assigns the callbacks.
         * Ideally, for each proxy class, just one set of argument types should be used,
         * otherwise it would have to spend time on constructor lookup.
         * Technically, it is a re-implementation of {@link Enhancer#createUsingReflection(Class)},
         * with "cache {@link #setThreadCallbacks} and {@link #primaryConstructor}"
         *
         * @see #createUsingReflection(Class)
         * @param argumentTypes constructor argument types
         * @param arguments constructor arguments
         * @param callbacks callbacks to set for the new instance
         * @return newly created proxy
         */
        public Object newInstance(Class[] argumentTypes, Object[] arguments, Callback[] callbacks) {
            setThreadCallbacks(callbacks);
            try {
                // Explicit reference equality is added here just in case Arrays.equals does not have one
                if (primaryConstructorArgTypes == argumentTypes ||
                        Arrays.equals(primaryConstructorArgTypes, argumentTypes)) {
                    // If we have relevant Constructor instance at hand, just call it
                    // This skips "get constructors" machinery
                    return ReflectUtils.newInstance(primaryConstructor, arguments);
                }
                // Take a slow path if observing unexpected argument types
                return ReflectUtils.newInstance(generatedClass, argumentTypes, arguments);
            } finally {
                // clear thread callbacks to allow them to be gc'd
                setThreadCallbacks(null);
            }

        }

        private void setThreadCallbacks(Callback[] callbacks) {
            try {
                setThreadCallbacks.invoke(generatedClass, (Object) callbacks);
            } catch (IllegalAccessException e) {
                throw new CodeGenerationException(e);
            } catch (InvocationTargetException e) {
                throw new CodeGenerationException(e.getTargetException());
            }
        }
    }

    private Object createHelper() {
        preValidate();
        Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
                ReflectUtils.getNames(interfaces),
                filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
                callbackTypes,
                useFactory,
                interceptDuringConstruction,
                serialVersionUID);
        this.currentKey = key;
        Object result = super.create(key);
        return result;
    }

    @Override
    protected Class generate(ClassLoaderData data) {
        validate();
        if (superclass != null) {
            setNamePrefix(superclass.getName());
        } else if (interfaces != null) {
            setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
        }
        return super.generate(data);
    }

    protected ClassLoader getDefaultClassLoader() {
        if (superclass != null) {
            return superclass.getClassLoader();
        } else if (interfaces != null) {
            return interfaces[0].getClassLoader();
        } else {
            return null;
        }
    }

    protected ProtectionDomain getProtectionDomain() {
        if (superclass != null) {
        	return ReflectUtils.getProtectionDomain(superclass);
        } else if (interfaces != null) {
        	return ReflectUtils.getProtectionDomain(interfaces[0]);
        } else {
            return null;
        }
    }

    private Signature rename(Signature sig, int index) {
        return new Signature("CGLIB$" + sig.getName() + "$" + index,
                             sig.getDescriptor());
    }

    /**
     * Finds all of the methods that will be extended by an
     * Enhancer-generated class using the specified superclass and
     * interfaces. This can be useful in building a list of Callback
     * objects. The methods are added to the end of the given list.  Due
     * to the subclassing nature of the classes generated by Enhancer,
     * the methods are guaranteed to be non-static, non-final, and
     * non-private. Each method signature will only occur once, even if
     * it occurs in multiple classes.
     * @param superclass the class that will be extended, or null
     * @param interfaces the list of interfaces that will be implemented, or null
     * @param methods the list into which to copy the applicable methods
     */
    public static void getMethods(Class superclass, Class[] interfaces, List methods)
    {
        getMethods(superclass, interfaces, methods, null, null);
    }

    private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic)
    {
        ReflectUtils.addAllMethods(superclass, methods);
        List target = (interfaceMethods != null) ? interfaceMethods : methods;
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                if (interfaces[i] != Factory.class) {
                    ReflectUtils.addAllMethods(interfaces[i], target);
                }
            }
        }
        if (interfaceMethods != null) {
            if (forcePublic != null) {
                forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));
            }
            methods.addAll(interfaceMethods);
        }
        CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC));
        CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
        CollectionUtils.filter(methods, new DuplicatesPredicate(methods));
        CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL));
    }

    public void generateClass(ClassVisitor v) throws Exception {
        Class sc = (superclass == null) ? Object.class : superclass;

        if (TypeUtils.isFinal(sc.getModifiers()))
            throw new IllegalArgumentException("Cannot subclass final class " + sc.getName());
        List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors()));
        filterConstructors(sc, constructors);

        // Order is very important: must add superclass, then
        // its superclass chain, then each interface and
        // its superinterfaces.
        List actualMethods = new ArrayList();
        List interfaceMethods = new ArrayList();
        final Set forcePublic = new HashSet();
        getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);

        List methods = CollectionUtils.transform(actualMethods, new Transformer() {
            public Object transform(Object value) {
                Method method = (Method)value;
                int modifiers = Constants.ACC_FINAL
                    | (method.getModifiers()
                       & ~Constants.ACC_ABSTRACT
                       & ~Constants.ACC_NATIVE
                       & ~Constants.ACC_SYNCHRONIZED);
                if (forcePublic.contains(MethodWrapper.create(method))) {
                    modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC;
                }
                return ReflectUtils.getMethodInfo(method, modifiers);
            }
        });

        ClassEmitter e = new ClassEmitter(v);
        if (currentData == null) {
        e.begin_class(Constants.V1_2,
                      Constants.ACC_PUBLIC,
                      getClassName(),
                      Type.getType(sc),
                      (useFactory ?
                       TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) :
                       TypeUtils.getTypes(interfaces)),
                      Constants.SOURCE_FILE);
        } else {
            e.begin_class(Constants.V1_2,
                    Constants.ACC_PUBLIC,
                    getClassName(),
                    null,
                    new Type[]{FACTORY},
                    Constants.SOURCE_FILE);
        }
        List constructorInfo = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance());

        e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null);
        e.declare_field(Constants.ACC_PUBLIC | Constants.ACC_STATIC, FACTORY_DATA_FIELD, OBJECT_TYPE, null);
        if (!interceptDuringConstruction) {
            e.declare_field(Constants.ACC_PRIVATE, CONSTRUCTED_FIELD, Type.BOOLEAN_TYPE, null);
        }
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null);
        e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null);
        if (serialVersionUID != null) {
            e.declare_field(Constants.PRIVATE_FINAL_STATIC, Constants.SUID_FIELD_NAME, Type.LONG_TYPE, serialVersionUID);
        }

        for (int i = 0; i < callbackTypes.length; i++) {
            e.declare_field(Constants.ACC_PRIVATE, getCallbackField(i), callbackTypes[i], null);
        }
        // This is declared private to avoid "public field" pollution
        e.declare_field(Constants.ACC_PRIVATE | Constants.ACC_STATIC, CALLBACK_FILTER_FIELD, OBJECT_TYPE, null);

        if (currentData == null) {
            emitMethods(e, methods, actualMethods);
            emitConstructors(e, constructorInfo);
        } else {
            emitDefaultConstructor(e);
        }
        emitSetThreadCallbacks(e);
        emitSetStaticCallbacks(e);
        emitBindCallbacks(e);

        if (useFactory || currentData != null) {
            int[] keys = getCallbackKeys();
            emitNewInstanceCallbacks(e);
            emitNewInstanceCallback(e);
            emitNewInstanceMultiarg(e, constructorInfo);
            emitGetCallback(e, keys);
            emitSetCallback(e, keys);
            emitGetCallbacks(e);
            emitSetCallbacks(e);
        }

        e.end_class();
    }

    /**
     * Filter the list of constructors from the superclass. The
     * constructors which remain will be included in the generated
     * class. The default implementation is to filter out all private
     * constructors, but subclasses may extend Enhancer to override this
     * behavior.
     * @param sc the superclass
     * @param constructors the list of all declared constructors from the superclass
     * @throws IllegalArgumentException if there are no non-private constructors
     */
    protected void filterConstructors(Class sc, List constructors) {
        CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true));
        if (constructors.size() == 0)
            throw new IllegalArgumentException("No visible constructors in " + sc);
    }

    /**
     * This method should not be called in regular flow.
     * Technically speaking {@link #wrapCachedClass(Class)} uses {@link EnhancerFactoryData} as a cache value,
     * and the latter enables faster instantiation than plain old reflection lookup and invoke.
     * This method is left intact for backward compatibility reasons: just in case it was ever used.
     *
     * @param type class to instantiate
     * @return newly created proxy instance
     * @throws Exception if something goes wrong
     */
    protected Object firstInstance(Class type) throws Exception {
        if (classOnly) {
            return type;
        } else {
            return createUsingReflection(type);
        }
    }

    protected Object nextInstance(Object instance) {
        EnhancerFactoryData data = (EnhancerFactoryData) instance;

        if (classOnly) {
            return data.generatedClass;
        }

        Class[] argumentTypes = this.argumentTypes;
        Object[] arguments = this.arguments;
        if (argumentTypes == null) {
            argumentTypes = Constants.EMPTY_CLASS_ARRAY;
            arguments = null;
        }
        return data.newInstance(argumentTypes, arguments, callbacks);
    }

    @Override
    protected Object wrapCachedClass(Class klass) {
        Class[] argumentTypes = this.argumentTypes;
        if (argumentTypes == null) {
            argumentTypes = Constants.EMPTY_CLASS_ARRAY;
        }
        EnhancerFactoryData factoryData = new EnhancerFactoryData(klass, argumentTypes, classOnly);
        Field factoryDataField = null;
        try {
            // The subsequent dance is performed just once for each class,
            // so it does not matter much how fast it goes
            factoryDataField = klass.getField(FACTORY_DATA_FIELD);
            factoryDataField.set(null, factoryData);
            Field callbackFilterField = klass.getDeclaredField(CALLBACK_FILTER_FIELD);
            callbackFilterField.setAccessible(true);
            callbackFilterField.set(null, this.filter);
        } catch (NoSuchFieldException e) {
            throw new CodeGenerationException(e);
        } catch (IllegalAccessException e) {
            throw new CodeGenerationException(e);
        }
        return new WeakReference<EnhancerFactoryData>(factoryData);
    }

    @Override
    protected Object unwrapCachedValue(Object cached) {
        if (currentKey instanceof EnhancerKey) {
            EnhancerFactoryData data = ((WeakReference<EnhancerFactoryData>) cached).get();
            return data;
        }
        return super.unwrapCachedValue(cached);
    }

    /**
     * Call this method to register the {@link Callback} array to use before
     * creating a new instance of the generated class via reflection. If you are using
     * an instance of Enhancer or the {@link Factory} interface to create
     * new instances, this method is unnecessary. Its primary use is for when you want to
     * cache and reuse a generated class yourself, and the generated class does
     * not implement the {@link Factory} interface.
     *

     * Note that this method only registers the callbacks on the current thread.
     * If you want to register callbacks for instances created by multiple threads,
     * use {@link #registerStaticCallbacks}.
     *

     * The registered callbacks are overwritten and subsequently cleared
     * when calling any of the create methods (such as
     * {@link #create}), or any {@link Factory} newInstance method.
     * Otherwise they are not cleared, and you should be careful to set them
     * back to null after creating new instances via reflection if
     * memory leakage is a concern.
     * @param generatedClass a class previously created by {@link Enhancer}
     * @param callbacks the array of callbacks to use when instances of the generated
     * class are created
     * @see #setUseFactory
     */
    public static void registerCallbacks(Class generatedClass, Callback[] callbacks) {
        setThreadCallbacks(generatedClass, callbacks);
    }

    /**
     * Similar to {@link #registerCallbacks}, but suitable for use
     * when multiple threads will be creating instances of the generated class.
     * The thread-level callbacks will always override the static callbacks.
     * Static callbacks are never cleared.
     * @param generatedClass a class previously created by {@link Enhancer}
     * @param callbacks the array of callbacks to use when instances of the generated
     * class are created
     */
    public static void registerStaticCallbacks(Class generatedClass, Callback[] callbacks) {
        setCallbacksHelper(generatedClass, callbacks, SET_STATIC_CALLBACKS_NAME);
    }

    /**
     * Determine if a class was generated using Enhancer.
     * @param type any class
     * @return whether the class was generated  using Enhancer
     */
    public static boolean isEnhanced(Class type) {
        try {
            getCallbacksSetter(type, SET_THREAD_CALLBACKS_NAME);
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private static void setThreadCallbacks(Class type, Callback[] callbacks) {
        setCallbacksHelper(type, callbacks, SET_THREAD_CALLBACKS_NAME);
    }

    private static void setCallbacksHelper(Class type, Callback[] callbacks, String methodName) {
        // TODO: optimize
        try {
            Method setter = getCallbacksSetter(type, methodName);
            setter.invoke(null, new Object[]{ callbacks });
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(type + " is not an enhanced class");
        } catch (IllegalAccessException e) {
            throw new CodeGenerationException(e);
        } catch (InvocationTargetException e) {
            throw new CodeGenerationException(e);
        }
    }

    private static Method getCallbacksSetter(Class type, String methodName) throws NoSuchMethodException {
        return type.getDeclaredMethod(methodName, new Class[]{ Callback[].class });
    }

    /**
     * Instantiates a proxy instance and assigns callback values.
     * Implementation detail: java.lang.reflect instances are not cached, so this method should not
     * be used on a hot path.
     * This method is used when {@link #setUseCache(boolean)} is set to {@code false}.
     *
     * @param type class to instantiate
     * @return newly created instance
     */
    private Object createUsingReflection(Class type) {
        setThreadCallbacks(type, callbacks);
        try{

        if (argumentTypes != null) {

             return ReflectUtils.newInstance(type, argumentTypes, arguments);

        } else {

            return ReflectUtils.newInstance(type);

        }
        }finally{
         // clear thread callbacks to allow them to be gc'd
         setThreadCallbacks(type, null);
        }
    }

    /**
     * Helper method to create an intercepted object.
     * For finer control over the generated instance, use a new instance of Enhancer
     * instead of this static method.
     * @param type class to extend or interface to implement
     * @param callback the callback to use for all methods
     */
    public static Object create(Class type, Callback callback) {
        Enhancer e = new Enhancer();
        e.setSuperclass(type);
        e.setCallback(callback);
        return e.create();
    }

    /**
     * Helper method to create an intercepted object.
     * For finer control over the generated instance, use a new instance of Enhancer
     * instead of this static method.
     * @param superclass class to extend or interface to implement
     * @param interfaces array of interfaces to implement, or null
     * @param callback the callback to use for all methods
     */
    public static Object create(Class superclass, Class interfaces[], Callback callback) {
        Enhancer e = new Enhancer();
        e.setSuperclass(superclass);
        e.setInterfaces(interfaces);
        e.setCallback(callback);
        return e.create();
    }

    /**
     * Helper method to create an intercepted object.
     * For finer control over the generated instance, use a new instance of Enhancer
     * instead of this static method.
     * @param superclass class to extend or interface to implement
     * @param interfaces array of interfaces to implement, or null
     * @param filter the callback filter to use when generating a new class
     * @param callbacks callback implementations to use for the enhanced object
     */
    public static Object create(Class superclass, Class[] interfaces, CallbackFilter filter, Callback[] callbacks) {
        Enhancer e = new Enhancer();
        e.setSuperclass(superclass);
        e.setInterfaces(interfaces);
        e.setCallbackFilter(filter);
        e.setCallbacks(callbacks);
        return e.create();
    }

    private void emitDefaultConstructor(ClassEmitter ce) {
        Constructor<Object> declaredConstructor;
        try {
            declaredConstructor = Object.class.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Object should have default constructor ", e);
        }
        MethodInfo constructor = (MethodInfo) MethodInfoTransformer.getInstance().transform(declaredConstructor);
        CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC);
        e.load_this();
        e.dup();
        Signature sig = constructor.getSignature();
        e.super_invoke_constructor(sig);
        e.return_value();
        e.end_method();
    }

    private void emitConstructors(ClassEmitter ce, List constructors) {
        boolean seenNull = false;
        for (Iterator it = constructors.iterator(); it.hasNext();) {
            MethodInfo constructor = (MethodInfo)it.next();
            if (currentData != null && !"()V".equals(constructor.getSignature().getDescriptor())) {
                continue;
            }
            CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC);
            e.load_this();
            e.dup();
            e.load_args();
            Signature sig = constructor.getSignature();
            seenNull = seenNull || sig.getDescriptor().equals("()V");
            e.super_invoke_constructor(sig);
            if (currentData == null) {
                e.invoke_static_this(BIND_CALLBACKS);
                if (!interceptDuringConstruction) {
                    e.load_this();
                    e.push(1);
                    e.putfield(CONSTRUCTED_FIELD);
                }
            }
            e.return_value();
            e.end_method();
        }
        if (!classOnly && !seenNull && arguments == null)
            throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given");
    }

    private int[] getCallbackKeys() {
        int[] keys = new int[callbackTypes.length];
        for (int i = 0; i < callbackTypes.length; i++) {
            keys[i] = i;
        }
        return keys;
    }

    private void emitGetCallback(ClassEmitter ce, int[] keys) {
        final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACK, null);
        e.load_this();
        e.invoke_static_this(BIND_CALLBACKS);
        e.load_this();
        e.load_arg(0);
        e.process_switch(keys, new ProcessSwitchCallback() {
            public void processCase(int key, Label end) {
                e.getfield(getCallbackField(key));
                e.goTo(end);
            }
            public void processDefault() {
                e.pop(); // stack height
                e.aconst_null();
            }
        });
        e.return_value();
        e.end_method();
    }

    private void emitSetCallback(ClassEmitter ce, int[] keys) {
        final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACK, null);
        e.load_arg(0);
        e.process_switch(keys, new ProcessSwitchCallback() {
            public void processCase(int key, Label end) {
                e.load_this();
                e.load_arg(1);
                e.checkcast(callbackTypes[key]);
                e.putfield(getCallbackField(key));
                e.goTo(end);
            }
            public void processDefault() {
                // TODO: error?
            }
        });
        e.return_value();
        e.end_method();
    }

    private void emitSetCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACKS, null);
        e.load_this();
        e.load_arg(0);
        for (int i = 0; i < callbackTypes.length; i++) {
            e.dup2();
            e.aaload(i);
            e.checkcast(callbackTypes[i]);
            e.putfield(getCallbackField(i));
        }
        e.return_value();
        e.end_method();
    }

    private void emitGetCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACKS, null);
        e.load_this();
        e.invoke_static_this(BIND_CALLBACKS);
        e.load_this();
        e.push(callbackTypes.length);
        e.newarray(CALLBACK);
        for (int i = 0; i < callbackTypes.length; i++) {
            e.dup();
            e.push(i);
            e.load_this();
            e.getfield(getCallbackField(i));
            e.aastore();
        }
        e.return_value();
        e.end_method();
    }

    private void emitNewInstanceCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null);
        Type thisType = getThisType(e);
        e.load_arg(0);
        e.invoke_static(thisType, SET_THREAD_CALLBACKS);
        emitCommonNewInstance(e);
    }

    private Type getThisType(CodeEmitter e) {
        if (currentData == null) {
            return e.getClassEmitter().getClassType();
        } else {
            return Type.getType(currentData.generatedClass);
        }
    }

    private void emitCommonNewInstance(CodeEmitter e) {
        Type thisType = getThisType(e);
        e.new_instance(thisType);
        e.dup();
        e.invoke_constructor(thisType);
        e.aconst_null();
        e.invoke_static(thisType, SET_THREAD_CALLBACKS);
        e.return_value();
        e.end_method();
    }

    private void emitNewInstanceCallback(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SINGLE_NEW_INSTANCE, null);
        switch (callbackTypes.length) {
        case 0:
            // TODO: make sure Callback is null
            break;
        case 1:
            // for now just make a new array; TODO: optimize
            e.push(1);
            e.newarray(CALLBACK);
            e.dup();
            e.push(0);
            e.load_arg(0);
            e.aastore();
            e.invoke_static(getThisType(e), SET_THREAD_CALLBACKS);
            break;
        default:
            e.throw_exception(ILLEGAL_STATE_EXCEPTION, "More than one callback object required");
        }
        emitCommonNewInstance(e);
    }

    private void emitNewInstanceMultiarg(ClassEmitter ce, List constructors) {
        final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, MULTIARG_NEW_INSTANCE, null);
        final Type thisType = getThisType(e);
        e.load_arg(2);
        e.invoke_static(thisType, SET_THREAD_CALLBACKS);
        e.new_instance(thisType);
        e.dup();
        e.load_arg(0);
        EmitUtils.constructor_switch(e, constructors, new ObjectSwitchCallback() {
            public void processCase(Object key, Label end) {
                MethodInfo constructor = (MethodInfo)key;
                Type types[] = constructor.getSignature().getArgumentTypes();
                for (int i = 0; i < types.length; i++) {
                    e.load_arg(1);
                    e.push(i);
                    e.aaload();
                    e.unbox(types[i]);
                }
                e.invoke_constructor(thisType, constructor.getSignature());
                e.goTo(end);
            }
            public void processDefault() {
                e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Constructor not found");
            }
        });
        e.aconst_null();
        e.invoke_static(thisType, SET_THREAD_CALLBACKS);
        e.return_value();
        e.end_method();
    }

    private void emitMethods(final ClassEmitter ce, List methods, List actualMethods) {
        CallbackGenerator[] generators = CallbackInfo.getGenerators(callbackTypes);

        Map groups = new HashMap();
        final Map indexes = new HashMap();
        final Map originalModifiers = new HashMap();
        final Map positions = CollectionUtils.getIndexMap(methods);
        final Map declToBridge = new HashMap();

        Iterator it1 = methods.iterator();
        Iterator it2 = (actualMethods != null) ? actualMethods.iterator() : null;

        while (it1.hasNext()) {
            MethodInfo method = (MethodInfo)it1.next();
            Method actualMethod = (it2 != null) ? (Method)it2.next() : null;
            int index = filter.accept(actualMethod);
            if (index >= callbackTypes.length) {
                throw new IllegalArgumentException("Callback filter returned an index that is too large: " + index);
            }
            originalModifiers.put(method, new Integer((actualMethod != null) ? actualMethod.getModifiers() : method.getModifiers()));
            indexes.put(method, new Integer(index));
            List group = (List)groups.get(generators[index]);
            if (group == null) {
                groups.put(generators[index], group = new ArrayList(methods.size()));
            }
            group.add(method);

            // Optimization: build up a map of Class -> bridge methods in class
            // so that we can look up all the bridge methods in one pass for a class.
            if (TypeUtils.isBridge(actualMethod.getModifiers())) {
            	Set bridges = (Set)declToBridge.get(actualMethod.getDeclaringClass());
            	if (bridges == null) {
            	    bridges = new HashSet();
            	    declToBridge.put(actualMethod.getDeclaringClass(), bridges);
            	}
            	bridges.add(method.getSignature());
            }
        }

        final Map bridgeToTarget = new BridgeMethodResolver(declToBridge, getClassLoader()).resolveAll();

        Set seenGen = new HashSet();
        CodeEmitter se = ce.getStaticHook();
        se.new_instance(THREAD_LOCAL);
        se.dup();
        se.invoke_constructor(THREAD_LOCAL, CSTRUCT_NULL);
        se.putfield(THREAD_CALLBACKS_FIELD);

        final Object[] state = new Object[1];
        CallbackGenerator.Context context = new CallbackGenerator.Context() {
            public ClassLoader getClassLoader() {
                return Enhancer.this.getClassLoader();
            }
            public int getOriginalModifiers(MethodInfo method) {
                return ((Integer)originalModifiers.get(method)).intValue();
            }
            public int getIndex(MethodInfo method) {
                return ((Integer)indexes.get(method)).intValue();
            }
            public void emitCallback(CodeEmitter e, int index) {
                emitCurrentCallback(e, index);
            }
            public Signature getImplSignature(MethodInfo method) {
                return rename(method.getSignature(), ((Integer)positions.get(method)).intValue());
            }
            public void emitLoadArgsAndInvoke(CodeEmitter e, MethodInfo method) {
                // If this is a bridge and we know the target was called from invokespecial,
                // then we need to invoke_virtual w/ the bridge target instead of doing
                // a super, because super may itself be using super, which would bypass
                // any proxies on the target.
                Signature bridgeTarget = (Signature)bridgeToTarget.get(method.getSignature());
                if (bridgeTarget != null) {
                    // checkcast each argument against the target's argument types
                    for (int i = 0; i < bridgeTarget.getArgumentTypes().length; i++) {
                        e.load_arg(i);
                        Type target = bridgeTarget.getArgumentTypes()[i];
                        if (!target.equals(method.getSignature().getArgumentTypes()[i])) {
                            e.checkcast(target);
                        }
                    }

                    e.invoke_virtual_this(bridgeTarget);

                    Type retType = method.getSignature().getReturnType();
                    // Not necessary to cast if the target & bridge have
                    // the same return type.
                    // (This conveniently includes void and primitive types,
                    // which would fail if casted.  It's not possible to
                    // covariant from boxed to unbox (or vice versa), so no having
                    // to box/unbox for bridges).
                    // TODO: It also isn't necessary to checkcast if the return is
                    // assignable from the target.  (This would happen if a subclass
                    // used covariant returns to narrow the return type within a bridge
                    // method.)
                    if (!retType.equals(bridgeTarget.getReturnType())) {
                        e.checkcast(retType);
                    }
                } else {
                    e.load_args();
                    e.super_invoke(method.getSignature());
                }
            }
            public CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method) {
                CodeEmitter e = EmitUtils.begin_method(ce, method);
                if (!interceptDuringConstruction &&
                    !TypeUtils.isAbstract(method.getModifiers())) {
                    Label constructed = e.make_label();
                    e.load_this();
                    e.getfield(CONSTRUCTED_FIELD);
                    e.if_jump(e.NE, constructed);
                    e.load_this();
                    e.load_args();
                    e.super_invoke();
                    e.return_value();
                    e.mark(constructed);
                }
                return e;
            }
        };
        for (int i = 0; i < callbackTypes.length; i++) {
            CallbackGenerator gen = generators[i];
            if (!seenGen.contains(gen)) {
                seenGen.add(gen);
                final List fmethods = (List)groups.get(gen);
                if (fmethods != null) {
                    try {
                        gen.generate(ce, context, fmethods);
                        gen.generateStatic(se, context, fmethods);
                    } catch (RuntimeException x) {
                        throw x;
                    } catch (Exception x) {
                        throw new CodeGenerationException(x);
                    }
                }
            }
        }
        se.return_value();
        se.end_method();
    }

    private void emitSetThreadCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC,
                                        SET_THREAD_CALLBACKS,
                                        null);
        e.getfield(THREAD_CALLBACKS_FIELD);
        e.load_arg(0);
        e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_SET);
        e.return_value();
        e.end_method();
    }

    private void emitSetStaticCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC,
                                        SET_STATIC_CALLBACKS,
                                        null);
        e.load_arg(0);
        e.putfield(STATIC_CALLBACKS_FIELD);
        e.return_value();
        e.end_method();
    }

    private void emitCurrentCallback(CodeEmitter e, int index) {
        e.load_this();
        e.getfield(getCallbackField(index));
        e.dup();
        Label end = e.make_label();
        e.ifnonnull(end);
        e.pop(); // stack height
        e.load_this();
        e.invoke_static_this(BIND_CALLBACKS);
        e.load_this();
        e.getfield(getCallbackField(index));
        e.mark(end);
    }

    private void emitBindCallbacks(ClassEmitter ce) {
        CodeEmitter e = ce.begin_method(Constants.PRIVATE_FINAL_STATIC,
                                        BIND_CALLBACKS,
                                        null);
        Local me = e.make_local();
        e.load_arg(0);
        e.checkcast_this();
        e.store_local(me);

        Label end = e.make_label();
        e.load_local(me);
        e.getfield(BOUND_FIELD);
        e.if_jump(e.NE, end);
        e.load_local(me);
        e.push(1);
        e.putfield(BOUND_FIELD);

        e.getfield(THREAD_CALLBACKS_FIELD);
        e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_GET);
        e.dup();
        Label found_callback = e.make_label();
        e.ifnonnull(found_callback);
        e.pop();

        e.getfield(STATIC_CALLBACKS_FIELD);
        e.dup();
        e.ifnonnull(found_callback);
        e.pop();
        e.goTo(end);

        e.mark(found_callback);
        e.checkcast(CALLBACK_ARRAY);
        e.load_local(me);
        e.swap();
        for (int i = callbackTypes.length - 1; i >= 0; i--) {
            if (i != 0) {
                e.dup2();
            }
            e.aaload(i);
            e.checkcast(callbackTypes[i]);
            e.putfield(getCallbackField(i));
        }

        e.mark(end);
        e.return_value();
        e.end_method();
    }

    private static String getCallbackField(int index) {
        return "CGLIB$CALLBACK_" + index;
    }
}