package com.googlecode.d2j.dex;

import com.googlecode.d2j.*;
import com.googlecode.d2j.converter.Dex2IRConverter;
import com.googlecode.d2j.converter.IR2JConverter;
import com.googlecode.d2j.node.*;
import com.googlecode.dex2jar.ir.IrMethod;
import com.googlecode.dex2jar.ir.ts.*;
import com.googlecode.dex2jar.ir.ts.array.FillArrayTransformer;
import org.objectweb.asm.*;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureWriter;
import org.objectweb.asm.tree.InnerClassNode;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class Dex2Asm {
    public static class ClzCtx {
        public String classDescriptor;
        public String hexDecodeMethodNamePrefix;

        public String buildHexDecodeMethodName(String x) {
            if (hexDecodeMethodNamePrefix == null) {
                byte[] d = new byte[4];
                new Random().nextBytes(d);
                hexDecodeMethodNamePrefix = "$d2j$hex$" + IR2JConverter.hexEncode(d);
            }
            return hexDecodeMethodNamePrefix + "$decode_" + x;
        }
    }
    protected static class Clz {
        public int access;
        public Clz enclosingClass;
        public Method enclosingMethod;
        public String innerName;
        public Set<Clz> inners = null;
        public final String name;

        public Clz(String name) {
            super();
            this.name = name;
        }

        void addInner(Clz clz) {
            if (inners == null) {
                inners = new HashSet<Clz>();
            }
            inners.add(clz);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Clz other = (Clz) obj;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;
            return true;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((name == null) ? 0 : name.hashCode());
            return result;
        }

        public String toString() {
            return "" + name;
        }
    }

    protected static final int ACC_INTERFACE_ABSTRACT = (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT);

    private static final int NO_CODE_MASK = DexConstants.ACC_ABSTRACT | DexConstants.ACC_NATIVE
            | DexConstants.ACC_ANNOTATION;

    protected static final CleanLabel T_cleanLabel = new CleanLabel();
    protected static final EndRemover T_endRemove = new EndRemover();
    protected static final Ir2JRegAssignTransformer T_ir2jRegAssign = new Ir2JRegAssignTransformer();
    protected static final NewTransformer T_new = new NewTransformer();
    protected static final RemoveConstantFromSSA T_removeConst = new RemoveConstantFromSSA();
    protected static final RemoveLocalFromSSA T_removeLocal = new RemoveLocalFromSSA();
    protected static final ExceptionHandlerTrim T_trimEx = new ExceptionHandlerTrim();
    protected static final TypeTransformer T_type = new TypeTransformer();
    // protected static final TopologicalSort T_topologicalSort = new TopologicalSort();
    protected static final DeadCodeTransformer T_deadCode = new DeadCodeTransformer();
    protected static final FillArrayTransformer T_fillArray = new FillArrayTransformer();
    protected static final AggTransformer T_agg = new AggTransformer();
    protected static final UnSSATransformer T_unssa = new UnSSATransformer();
    protected static final ZeroTransformer T_zero = new ZeroTransformer();
    protected static final VoidInvokeTransformer T_voidInvoke = new VoidInvokeTransformer();
    protected static final NpeTransformer T_npe = new NpeTransformer();
    protected static final MultiArrayTransformer T_multiArray = new MultiArrayTransformer();

    static private int clearClassAccess(boolean isInner, int access) {
        if ((access & Opcodes.ACC_INTERFACE) == 0) { // issue 55
            access |= Opcodes.ACC_SUPER;// 解决生成的class文件使用dx重新转换时使用的指令与原始指令不同的问题
        }
        // access in class has no acc_static or acc_private
        access &= ~(Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE);
        if (isInner && (access & Opcodes.ACC_PROTECTED) != 0) {// protected inner class are public
            access &= ~Opcodes.ACC_PROTECTED;
            access |= Opcodes.ACC_PUBLIC;
        }
        access &= ~DexConstants.ACC_DECLARED_SYNCHRONIZED; // clean ACC_DECLARED_SYNCHRONIZED
        return access;
    }

    static private int clearInnerAccess(int access) {
        access &= (~Opcodes.ACC_SUPER);// inner class attr has no acc_super
        if (0 != (access & Opcodes.ACC_PRIVATE)) {// clear public/protected if it is private
            access &= ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
        } else if (0 != (access & Opcodes.ACC_PROTECTED)) {// clear public if it is protected
            access &= ~(Opcodes.ACC_PUBLIC);
        }
        return access;
    }

    protected static String toInternalName(DexType type) {
        return toInternalName(type.desc);
    }

    protected static String toInternalName(String desc) {
        // TODO without creating object
        return Type.getType(desc).getInternalName();
    }

    public static void accept(DexAnnotationNode ann, ClassVisitor v) {
        AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
        if (av != null) {
            accept(ann.items, av);
            av.visitEnd();
        }
    }

    public static void accept(List<DexAnnotationNode> anns, ClassVisitor cv) {
        if (anns != null) {
            for (DexAnnotationNode ann : anns) {
                if (ann.visibility != Visibility.SYSTEM) {
                    accept(ann, cv);
                }
            }
        }
    }

    public static void accept(List<DexAnnotationNode> anns, FieldVisitor fv) {
        if (anns != null) {
            for (DexAnnotationNode ann : anns) {
                if (ann.visibility != Visibility.SYSTEM) {
                    accept(ann, fv);
                }
            }
        }
    }

    public static void accept(List<DexAnnotationNode> anns, MethodVisitor mv) {
        if (anns != null) {
            for (DexAnnotationNode ann : anns) {
                if (ann.visibility != Visibility.SYSTEM) {
                    accept(ann, mv);
                }
            }
        }
    }

    public static void accept(DexAnnotationNode ann, MethodVisitor v) {
        AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
        if (av != null) {
            accept(ann.items, av);
            av.visitEnd();
        }
    }

    public static void acceptParameter(DexAnnotationNode ann, int index, MethodVisitor v) {
        AnnotationVisitor av = v.visitParameterAnnotation(index, ann.type, ann.visibility != Visibility.BUILD);
        if (av != null) {
            accept(ann.items, av);
            av.visitEnd();
        }
    }

    public static void accept(DexAnnotationNode ann, FieldVisitor v) {
        AnnotationVisitor av = v.visitAnnotation(ann.type, ann.visibility != Visibility.BUILD);
        if (av != null) {
            accept(ann.items, av);
            av.visitEnd();
        }
    }

    public static void accept(List<DexAnnotationNode.Item> items, AnnotationVisitor av) {
        for (DexAnnotationNode.Item item : items) {
            accept(av, item.name, item.value);
        }
    }

    private static void accept(AnnotationVisitor dav, String name, Object o) {
        if (o instanceof Object[]) {
            AnnotationVisitor arrayVisitor = dav.visitArray(name);
            if (arrayVisitor != null) {
                Object[] array = (Object[]) o;
                for (Object e : array) {
                    accept(arrayVisitor, null, e);
                }
                arrayVisitor.visitEnd();
            }
        } else if (o instanceof DexAnnotationNode) {
            DexAnnotationNode ann = (DexAnnotationNode) o;
            AnnotationVisitor av = dav.visitAnnotation(name, ann.type);
            if (av != null) {
                for (DexAnnotationNode.Item item : ann.items) {
                    accept(av, item.name, item.value);
                }
                av.visitEnd();
            }
        } else if (o instanceof Field) {
            Field f = (Field) o;
            dav.visitEnum(name, f.getType(), f.getName());
        } else if (o instanceof DexType) {
            dav.visit(name, Type.getType(((DexType) o).desc));
        } else if (o instanceof Method) {
            System.err.println("WARN: ignored method annotation value");
        } else {
            if (o == null) {
                System.err.println("WARN: ignored null annotation value");
            } else {
                dav.visit(name, o);
            }
        }
    }

    private static MethodVisitor collectBasicMethodInfo(DexMethodNode methodNode, ClassVisitor cv) {
        String xthrows[] = null;
        String signature = null;
        if (methodNode.anns != null) {
            for (DexAnnotationNode ann : methodNode.anns) {
                if (ann.visibility == Visibility.SYSTEM) {
                    switch (ann.type) {
                    case DexConstants.ANNOTATION_THROWS_TYPE: {
                        Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
                        if (strs != null) {
                            xthrows = new String[strs.length];
                            for (int i = 0; i < strs.length; i++) {
                                DexType type = (DexType) strs[i];
                                xthrows[i] = toInternalName(type);
                            }
                        }
                    }
                        break;
                    case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
                        Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
                        if (strs != null) {
                            StringBuilder sb = new StringBuilder();
                            for (Object str : strs) {
                                sb.append(str);
                            }
                            signature = sb.toString();
                        }
                    }
                        break;
                    }
                }
            }
        }
        if (isSignatureNotValid(signature, false)) {
            System.err.println("Applying workaround to method "
                    + methodNode.method
                    + " with original signature " + signature
                    + " by changing its signature to null");
            signature = null;
        }
        int access = methodNode.access;
        // clear ACC_DECLARED_SYNCHRONIZED and ACC_CONSTRUCTOR from method flags
        final int cleanFlag = ~((DexConstants.ACC_DECLARED_SYNCHRONIZED | DexConstants.ACC_CONSTRUCTOR));
        access &= cleanFlag;
        return cv.visitMethod(access, methodNode.method.getName(), methodNode.method.getDesc(), signature, xthrows);
    }

    protected static Map<String, Clz> collectClzInfo(DexFileNode fileNode) {
        Map<String, Clz> classes = new HashMap<>();
        for (DexClassNode classNode : fileNode.clzs) {
            Clz clz = get(classes, classNode.className);
            clz.access = (clz.access & ~ACC_INTERFACE_ABSTRACT) | classNode.access;
            if (classNode.anns != null) {
                for (DexAnnotationNode ann : classNode.anns) {
                    if (ann.visibility == Visibility.SYSTEM) {
                        switch (ann.type) {
                        case DexConstants.ANNOTATION_ENCLOSING_CLASS_TYPE: {
                            DexType type = (DexType) findAnnotationAttribute(ann, "value");
                            Clz enclosingClass = get(classes, type.desc);
                            clz.enclosingClass = enclosingClass;

                            // apply patch from ChaeHoon Lim,
                            // obfuscated code may declare itself as enclosing class
                            // which cause dex2jar to endless loop
                            //if(!clz.name.equals(clz.enclosingClass.name)) {
                            //    enclosingClass.addInner(clz);
                            //}
                            enclosingClass.addInner(clz);

                        }
                            break;
                        case DexConstants.ANNOTATION_ENCLOSING_METHOD_TYPE: {
                            Method m = (Method) findAnnotationAttribute(ann, "value");
                            Clz enclosingClass = get(classes, m.getOwner());
                            clz.enclosingClass = enclosingClass;
                            clz.enclosingMethod = m;
                            enclosingClass.addInner(clz);
                        }
                            break;
                        case DexConstants.ANNOTATION_INNER_CLASS_TYPE: {
                            for (DexAnnotationNode.Item it : ann.items) {
                                if ("accessFlags".equals(it.name)) {
                                    clz.access |= (Integer) it.value & ~ACC_INTERFACE_ABSTRACT;
                                } else if ("name".equals(it.name)) {
                                    clz.innerName = (String) it.value;
                                }
                            }
                        }
                            break;
                        case DexConstants.ANNOTATION_MEMBER_CLASSES_TYPE: {
                            Object ts[] = (Object[]) findAnnotationAttribute(ann, "value");
                            for (Object v : ts) {
                                DexType type = (DexType) v;
                                Clz inner = get(classes, type.desc);
                                clz.addInner(inner);
                                inner.enclosingClass = clz;
                            }
                        }
                            break;
                        }
                    }
                }
            }
        }
        return classes;
    }

    public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf, DexFileNode fileNode) {
        convertClass(fileNode.dexVersion, classNode, cvf, collectClzInfo(fileNode));
    }

    public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf) {
        convertClass(DexConstants.DEX_035, classNode, cvf);
    }
    public void convertClass(int dexVersion, DexClassNode classNode, ClassVisitorFactory cvf) {
        convertClass(dexVersion, classNode, cvf, new HashMap<String, Clz>());
    }

    private static boolean isJavaIdentifier(String str) {
        if (str.length() < 1) {
            return false;
        }
        if (!Character.isJavaIdentifierStart(str.charAt(0))) {
            return false;
        }
        for (int i = 1; i < str.length(); i++) {
            if (!Character.isJavaIdentifierPart(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    public void convertClass(DexClassNode classNode, ClassVisitorFactory cvf, Map<String, Clz> classes) {
        convertClass(DexConstants.DEX_035, classNode, cvf, classes);
    }
    public void convertClass(DexFileNode dfn, DexClassNode classNode, ClassVisitorFactory cvf, Map<String, Clz> classes) {
        convertClass(dfn.dexVersion, classNode, cvf, classes);
    }
    public void convertClass(int dexVersion, DexClassNode classNode, ClassVisitorFactory cvf, Map<String, Clz> classes) {
        ClassVisitor cv = cvf.create(toInternalName(classNode.className));
        if (cv == null) {
            return;
        }
        // the default value of static-final field are omitted by dex, fix it
        DexFix.fixStaticFinalFieldValue(classNode);

        String signature = null;
        if (classNode.anns != null) {
            for (DexAnnotationNode ann : classNode.anns) {
                if (ann.visibility == Visibility.SYSTEM) {
                    switch (ann.type) {
                    case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
                        Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
                        if (strs != null) {
                            StringBuilder sb = new StringBuilder();
                            for (Object str : strs) {
                                sb.append(str);
                            }
                            signature = sb.toString();
                        }
                    }
                        break;
                    }
                }
            }
        }
        String interfaceInterNames[] = null;
        if (classNode.interfaceNames != null) {
            interfaceInterNames = new String[classNode.interfaceNames.length];
            for (int i = 0; i < classNode.interfaceNames.length; i++) {
                interfaceInterNames[i] = toInternalName(classNode.interfaceNames[i]);
            }
        }

        Clz clzInfo = classes.get(classNode.className);
        int access = classNode.access;
        boolean isInnerClass = false;
        if (clzInfo != null) {
            isInnerClass = clzInfo.enclosingClass != null || clzInfo.enclosingMethod != null;
        }
        access = clearClassAccess(isInnerClass, access);

        if (isSignatureNotValid(signature, false)) {
            System.err.println("Applying workaround to class "
                    + classNode.className
                    + " with original signature " + signature
                    + " by changing its signature to null.");
            signature = null;
        }

        int version = dexVersion >= DexConstants.DEX_037 ? Opcodes.V1_8 : Opcodes.V1_6;
        cv.visit(version, access, toInternalName(classNode.className), signature,
                classNode.superClass == null ? null : toInternalName(classNode.superClass), interfaceInterNames);

        List<InnerClassNode> innerClassNodes = new ArrayList<InnerClassNode>(5);
        if (clzInfo != null) {
            searchInnerClass(clzInfo, innerClassNodes, classNode.className);
        }
        if (isInnerClass) {
            // build Outer Clz
            if (clzInfo.innerName == null) {// anonymous Innerclass
                Method enclosingMethod = clzInfo.enclosingMethod;
                if (enclosingMethod != null) {
                    cv.visitOuterClass(toInternalName(enclosingMethod.getOwner()), enclosingMethod.getName(),
                            enclosingMethod.getDesc());
                } else {
                    Clz enclosingClass = clzInfo.enclosingClass;
                    cv.visitOuterClass(toInternalName(enclosingClass.name), null, null);
                }
            }
            searchEnclosing(clzInfo, innerClassNodes);
        }
        Collections.sort(innerClassNodes, INNER_CLASS_NODE_COMPARATOR);
        for (InnerClassNode icn : innerClassNodes) {
            if (icn.innerName != null && !isJavaIdentifier(icn.innerName)) {
                System.err.println("WARN: ignored invalid inner class name " + ", treat as anonymous inner class.");
                icn.innerName = null;
                icn.outerName = null;
            }
            icn.accept(cv);
        }

        accept(classNode.anns, cv);

        if (classNode.fields != null) {
            for (DexFieldNode fieldNode : classNode.fields) {
                convertField(classNode, fieldNode, cv);
            }
        }
        if (classNode.methods != null) {
            ClzCtx clzCtx = new ClzCtx();
            clzCtx.classDescriptor = classNode.className;
            for (DexMethodNode methodNode : classNode.methods) {
                convertMethod(classNode, methodNode, cv, clzCtx);
            }
            if (clzCtx.hexDecodeMethodNamePrefix != null) {
                addHexDecodeMethod(cv, clzCtx.hexDecodeMethodNamePrefix);
            }
        }
        cv.visitEnd();
    }
    private void addHexDecodeMethod(ClassVisitor outCV, String hexDecodeMethodNameBase) {
        // the .data is a class file compiled from res.Hex
        try (InputStream is = Dex2Asm.class.getResourceAsStream("/d2j_hex_decode_stub.data")) {
            ClassReader cr = new ClassReader(is);
            cr.accept(new ClassVisitor(Opcodes.ASM5) {
                @Override
                public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                    if (name.startsWith("decode")) {
                        return outCV.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
                                hexDecodeMethodNameBase + "$" + name,
                                desc, signature, exceptions
                        );
                    } else {
                        return super.visitMethod(access, name, desc, signature, exceptions);
                    }
                }
            }, ClassReader.EXPAND_FRAMES);
        } catch (IOException e) {
            throw new RuntimeException("fail to add hex.decode", e);
        }
    }
    public void convertCode(DexMethodNode methodNode, MethodVisitor mv, ClzCtx clzCtx) {
        IrMethod irMethod = dex2ir(methodNode);
        optimize(irMethod);
        ir2j(irMethod, mv, clzCtx);
    }

    public void convertDex(DexFileNode fileNode, ClassVisitorFactory cvf) {
        if (fileNode.clzs != null) {
            Map<String, Clz> classes = collectClzInfo(fileNode);
            for (DexClassNode classNode : fileNode.clzs) {
                convertClass(fileNode, classNode, cvf, classes);
            }
        }
    }

    public void convertField(DexClassNode classNode, DexFieldNode fieldNode, ClassVisitor cv) {
        String signature = null;
        if (fieldNode.anns != null) {
            for (DexAnnotationNode ann : fieldNode.anns) {
                if (ann.visibility == Visibility.SYSTEM) {
                    switch (ann.type) {
                    case DexConstants.ANNOTATION_SIGNATURE_TYPE: {
                        Object[] strs = (Object[]) findAnnotationAttribute(ann, "value");
                        if (strs != null) {
                            StringBuilder sb = new StringBuilder();
                            for (Object str : strs) {
                                sb.append(str);
                            }
                            signature = sb.toString();
                        }
                    }
                        break;
                    }
                }
            }
        }
        Object value = convertConstantValue(fieldNode.cst);

        // https://github.com/pxb1988/dex2jar/issues/455
        // try validate signature before call visitField
        if (isSignatureNotValid(signature, true)) {
            System.err.println("Applying workaround to field "
                    + fieldNode.field
                    + " with original signature " + signature
                    + " by changing its signature to null.");
            signature = null;
        }


        final int fieldCleanFlag = ~((DexConstants.ACC_DECLARED_SYNCHRONIZED | Opcodes.ACC_SYNTHETIC));
        FieldVisitor fv = cv.visitField(fieldNode.access & fieldCleanFlag, fieldNode.field.getName(),
                    fieldNode.field.getType(), signature, value);

        if (fv == null) {
            return;
        }
        accept(fieldNode.anns, fv);
        fv.visitEnd();
    }

    /**
     * @see org.objectweb.asm.commons.Remapper#mapSignature(String, boolean)
     */
    private static boolean isSignatureNotValid(String signature, boolean typeSignature) {
        if (signature == null) {
            return false;
        }
        try {
            SignatureReader r = new SignatureReader(signature);
            SignatureWriter a = new SignatureWriter();

            if (typeSignature) {
                r.acceptType(a);
            } else {
                r.accept(a);
            }
        } catch (Exception ignore) {
            return true;
        }
        return false;
    }

    public static Object[] convertConstantValues(Object[] v) {
        Object[] copy = Arrays.copyOf(v, v.length);
        for (int i = 0; i < copy.length; i++) {
            Object ele = copy[i];
            ele = convertConstantValue(ele);
            copy[i] = ele;
        }
        return copy;
    }

    public static Object convertConstantValue(Object ele) {
        if (ele instanceof DexType) {
            ele = Type.getType(((DexType) ele).desc);
        } else if (ele instanceof MethodHandle) {
            Handle h = null;
            MethodHandle mh = (MethodHandle) ele;
            switch (mh.getType()) {
                case MethodHandle.INSTANCE_GET:
                    h = new Handle(Opcodes.H_GETFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
                    break;
                case MethodHandle.INSTANCE_PUT:
                    h = new Handle(Opcodes.H_PUTFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
                    break;
                case MethodHandle.STATIC_GET:
                    h = new Handle(Opcodes.H_GETFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
                    break;
                case MethodHandle.STATIC_PUT:
                    h = new Handle(Opcodes.H_PUTFIELD, toInternalName(mh.getField().getOwner()), mh.getField().getName(), mh.getField().getType());
                    break;
                case MethodHandle.INVOKE_INSTANCE:
                    h = new Handle(Opcodes.H_INVOKEVIRTUAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
                    break;
                case MethodHandle.INVOKE_STATIC:
                    h = new Handle(Opcodes.H_INVOKESTATIC, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
                    break;
                case MethodHandle.INVOKE_CONSTRUCTOR:
                    h = new Handle(Opcodes.H_NEWINVOKESPECIAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
                    break;
                case MethodHandle.INVOKE_DIRECT:
                    h = new Handle(Opcodes.H_INVOKESPECIAL, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
                    break;
                case MethodHandle.INVOKE_INTERFACE:
                    h = new Handle(Opcodes.H_INVOKEINTERFACE, toInternalName(mh.getMethod().getOwner()), mh.getMethod().getName(), mh.getMethod().getDesc());
                    break;
            }
            ele = h;
        } else if (ele instanceof Proto) {
            ele = Type.getMethodType(((Proto) ele).getDesc());
        }
        return ele;
    }

    public void convertMethod(DexClassNode classNode, DexMethodNode methodNode, ClassVisitor cv, ClzCtx clzCtx) {

        MethodVisitor mv = collectBasicMethodInfo(methodNode, cv);

        if (mv == null) {
            return;
        }
        if (0 != (classNode.access & DexConstants.ACC_ANNOTATION)) { // its inside an annotation
            Object defaultValue = null;
            if (classNode.anns != null) {
                for (DexAnnotationNode ann : classNode.anns) {
                    if (ann.visibility == Visibility.SYSTEM && ann.type.equals(DexConstants.ANNOTATION_DEFAULT_TYPE)) {
                        DexAnnotationNode node = (DexAnnotationNode) findAnnotationAttribute(ann, "value");
                        if (node != null) {
                            defaultValue = findAnnotationAttribute(node, methodNode.method.getName());
                        }
                        break;
                    }
                }
            }
            if (defaultValue != null) {
                AnnotationVisitor av = mv.visitAnnotationDefault();
                if (av != null) {
                    accept(av, null, defaultValue);
                    av.visitEnd();
                }
            }
        }

        accept(methodNode.anns, mv);

        if (methodNode.parameterAnns != null) {
            for (int i = 0; i < methodNode.parameterAnns.length; i++) {
                List<DexAnnotationNode> anns = methodNode.parameterAnns[i];
                if (anns != null) {
                    for (DexAnnotationNode ann : anns) {
                        if (ann.visibility != Visibility.SYSTEM) {
                            acceptParameter(ann, i, mv);
                        }
                    }
                }
            }
        }

        if ((NO_CODE_MASK & methodNode.access) == 0) { // has code
            if (methodNode.codeNode != null) {
                mv.visitCode();
                convertCode(methodNode, mv, clzCtx);
            }
        }

        mv.visitEnd();

    }

    public IrMethod dex2ir(DexMethodNode methodNode) {
        return new Dex2IRConverter()
                .convert(0 != (methodNode.access & DexConstants.ACC_STATIC), methodNode.method, methodNode.codeNode);
    }

    protected static Object findAnnotationAttribute(DexAnnotationNode ann, String name) {
        for (DexAnnotationNode.Item item : ann.items) {
            if (item.name.equals(name)) {
                return item.value;
            }
        }
        return null;
    }

    private static Clz get(Map<String, Clz> classes, String name) {
        Clz clz = classes.get(name);
        if (clz == null) {
            clz = new Clz(name);
            classes.put(name, clz);
        }
        return clz;
    }

    public void ir2j(IrMethod irMethod, MethodVisitor mv, ClzCtx clzCtx) {
        new IR2JConverter()
                .optimizeSynchronized(false)
                .clzCtx(clzCtx)
                .ir(irMethod)
                .asm(mv)
                .convert();
        mv.visitMaxs(-1, -1);
    }

    public void optimize(IrMethod irMethod) {
        T_cleanLabel.transform(irMethod);
        T_deadCode.transform(irMethod);
        T_removeLocal.transform(irMethod);
        T_removeConst.transform(irMethod);
        T_zero.transform(irMethod);
        if (T_npe.transformReportChanged(irMethod)) {
            T_deadCode.transform(irMethod);
            T_removeLocal.transform(irMethod);
            T_removeConst.transform(irMethod);
        }
        T_new.transform(irMethod);
        T_fillArray.transform(irMethod);
        T_agg.transform(irMethod);
        T_multiArray.transform(irMethod);
        T_voidInvoke.transform(irMethod);

        {
            // https://github.com/pxb1988/dex2jar/issues/477
            // dead code found in unssa, clean up
            T_deadCode.transform(irMethod);
            T_removeLocal.transform(irMethod);
            T_removeConst.transform(irMethod);
        }
        T_type.transform(irMethod);
        T_unssa.transform(irMethod);
        T_trimEx.transform(irMethod);
        T_ir2jRegAssign.transform(irMethod);
    }

    /**
     * For structure
     * 
     * <pre>
     * class A {
     *     class B {
     *         class WeAreHere {
     *         }
     *     }
     * }
     * </pre>
     * 
     * this method will add
     * 
     * <pre>
     * InnerClass  Outter
     * A$B$WeAreHere A$B
     * A$B           A
     * </pre>
     * 
     * to WeAreHere.class
     * 
     */
    private static void searchEnclosing(Clz clz, List<InnerClassNode> innerClassNodes) {
        Set<Clz> visitedClz = new HashSet<>();
        for (Clz p = clz; p != null; p = p.enclosingClass) {
            if (!visitedClz.add(p)) { // prevent endless loop
                break;
            }
            Clz enclosingClass = p.enclosingClass;
            if (enclosingClass == null) {
                break;
            }
            if (enclosingClass == clz) {
                // enclosing itself, that is impossible
                break;
            }
            int accessInInner = clearInnerAccess(p.access);
            if (p.innerName != null) {// non-anonymous Innerclass
                innerClassNodes.add(new InnerClassNode(toInternalName(p.name),
                        toInternalName(enclosingClass.name), p.innerName, accessInInner));
            } else {// anonymous Innerclass
                innerClassNodes.add(new InnerClassNode(toInternalName(p.name), null, null, accessInInner));
            }
        }
    }

    /**
     * For structure
     * 
     * <pre>
     * class WeAreHere {
     *     class A {
     *         class B {
     * 
     *         }
     *     }
     * }
     * </pre>
     * 
     * this method will add
     * 
     * <pre>
     * InnerClass      Outter
     * WeAreHere$A$B   WeAreHere$A
     * WeAreHere$A     WeAreHere
     * </pre>
     * 
     * to WeAreHere.class
     * 
     * @param clz
     */
    private static void searchInnerClass(Clz clz, List<InnerClassNode> innerClassNodes, String className) {
        Set<Clz> visited = new HashSet<>();
        Stack<Clz> stack = new Stack<>();
        stack.push(clz);
        while (!stack.empty()) {
            clz = stack.pop();
            if (visited.contains(clz)) {
                continue;
            } else {
                visited.add(clz);
            }
            if (clz.inners != null) {
                for (Clz inner : clz.inners) {
                    if (inner.innerName == null) {// anonymous Innerclass
                        innerClassNodes.add(new InnerClassNode(toInternalName(inner.name), null, null,
                                clearInnerAccess(inner.access)));
                    } else {// non-anonymous Innerclass
                        innerClassNodes.add(new InnerClassNode(toInternalName(inner.name), toInternalName(className),
                                inner.innerName, clearInnerAccess(inner.access)));
                    }
                    stack.push(inner);
                }
            }
        }
    }

    private static final Comparator<InnerClassNode> INNER_CLASS_NODE_COMPARATOR = new Comparator<InnerClassNode>() {
        @Override
        public int compare(InnerClassNode o1, InnerClassNode o2) {
            return o1.name.compareTo(o2.name);
        }
    };

}
