package test.javassist.bytecode.analysis;

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Bytecode;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import javassist.bytecode.analysis.Subroutine;
import javassist.bytecode.analysis.SubroutineScanner;
import junit.framework.TestCase;

/**
 * Tests Subroutine Scanner
 *
 * @author Jason T. Greene
 */
public class ScannerTest extends TestCase {

    public void testNestedFinally() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        generate(pool);
        CtClass clazz = pool.get("test.ScannerTest$GeneratedTest");
        CtMethod method = clazz.getDeclaredMethod("doit");

        SubroutineScanner scanner = new SubroutineScanner();
        Subroutine[] subs = scanner.scan(method.getMethodInfo2());

        verifySubroutine(subs, 31, 31, new int[]{125, 25});
        verifySubroutine(subs, 32, 31, new int[]{125, 25});
        verifySubroutine(subs, 33, 31, new int[]{125, 25});
        verifySubroutine(subs, 60, 31, new int[]{125, 25});
        verifySubroutine(subs, 61, 31, new int[]{125, 25});
        verifySubroutine(subs, 63, 31, new int[]{125, 25});
        verifySubroutine(subs, 66, 31, new int[]{125, 25});
        verifySubroutine(subs, 69, 31, new int[]{125, 25});
        verifySubroutine(subs, 71, 31, new int[]{125, 25});
        verifySubroutine(subs, 74, 31, new int[]{125, 25});
        verifySubroutine(subs, 76, 31, new int[]{125, 25});
        verifySubroutine(subs, 77, 77, new int[]{111, 71});
        verifySubroutine(subs, 79, 77, new int[]{111, 71});
        verifySubroutine(subs, 80, 77, new int[]{111, 71});
        verifySubroutine(subs, 82, 77, new int[]{111, 71});
        verifySubroutine(subs, 85, 77, new int[]{111, 71});
        verifySubroutine(subs, 88, 77, new int[]{111, 71});
        verifySubroutine(subs, 90, 77, new int[]{111, 71});
        verifySubroutine(subs, 93, 77, new int[]{111, 71});
        verifySubroutine(subs, 95, 77, new int[]{111, 71});
        verifySubroutine(subs, 96, 96, new int[]{106, 90});
        verifySubroutine(subs, 98, 96, new int[]{106, 90});
        verifySubroutine(subs, 99, 96, new int[]{106, 90});
        verifySubroutine(subs, 101, 96, new int[]{106, 90});
        verifySubroutine(subs, 104, 96, new int[]{106, 90});
        verifySubroutine(subs, 106, 77, new int[]{111, 71});
        verifySubroutine(subs, 109, 77, new int[]{111, 71});
        verifySubroutine(subs, 111, 31, new int[]{125, 25});
        verifySubroutine(subs, 114, 31, new int[]{125, 25});
        verifySubroutine(subs, 117, 31, new int[]{125, 25});
        verifySubroutine(subs, 118, 31, new int[]{125, 25});
        verifySubroutine(subs, 120, 31, new int[]{125, 25});
        verifySubroutine(subs, 123, 31, new int[]{125, 25});
    }

    private static void verifySubroutine(Subroutine[] subs, int pos, int start,
            int[] callers) {
        Subroutine sub = subs[pos];
        assertNotNull(sub);
        assertEquals(sub.start(), start);
        for (int i = 0; i < callers.length; i++)
            assertTrue(sub.callers().contains(Integer.valueOf(callers[i])));
    }

    private static void generate(ClassPool pool) throws CannotCompileException, IOException, NotFoundException {
        // Generated from eclipse JDK4 compiler:
        // public void doit(int x) {
        //    println("null");
        //    try {
        //        println("try");
        //    } catch (RuntimeException e) {
        //        e.printStackTrace();
        //    } finally {
        //        switch (x) {
        //        default:
        //        case 15:
        //        try {
        //            println("inner-try");
        //        } finally {
        //            try {
        //                println("inner-inner-try");
        //            } finally {
        //                println("inner-finally");
        //            }
        //        }
        //        break;
        //        case 1789:
        //        println("switch -17");
        //        }
        //    }
        //}

        CtClass clazz = pool.makeClass("test.ScannerTest$GeneratedTest");
        CtMethod method = new CtMethod(CtClass.voidType, "doit", new CtClass[] {CtClass.intType}, clazz);
        MethodInfo info = method.getMethodInfo2();
        info.setAccessFlags(AccessFlag.PUBLIC);
        CtClass stringClass = pool.get("java.lang.String");
        Bytecode code = new Bytecode(info.getConstPool(), 2, 9);
        /* 0   */ code.addAload(0);
        /* 1   */ code.addLdc("start");
        /* 3   */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 6   */ code.addAload(0);
        /* 7   */ code.addLdc("try");
        /* 9   */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 12  */ addJump(code, Opcode.GOTO, 125);
        /* 14  */ code.addAstore(2);
        /* 16  */ code.addAload(2);
        /* 17  */ code.addInvokevirtual("java.lang.Exception", "printStackTrace", "()V");
        /* 20  */ addJump(code, Opcode.GOTO, 125);
        /* 23  */ code.addAstore(4);
        /* 25  */ addJump(code, Opcode.JSR, 31);
        /* 28  */ code.addAload(4);
        /* 30  */ code.addOpcode(Opcode.ATHROW);
        /* 31  */ code.addAstore(3);
        /* 32  */ code.addIload(1);
        int spos = code.currentPc();
        /* 33  */ code.addOpcode(Opcode.LOOKUPSWITCH);
                  code.addIndex(0); // 2 bytes pad - gets us to 36
                  code.add32bit(60 - spos); // default
                  code.add32bit(2); // 2 pairs
                  code.add32bit(15); code.add32bit(60 - spos);
                  code.add32bit(1789); code.add32bit(117 - spos);
        /* 60  */ code.addAload(0);
        /* 61  */ code.addLdc("inner-try");
        /* 63  */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 66  */ addJump(code, Opcode.GOTO, 111);
        /* 69  */ code.addAstore(6);
        /* 71  */ addJump(code, Opcode.JSR, 77);
        /* 74  */ code.addAload(6);
        /* 76  */ code.add(Opcode.ATHROW);
        /* 77  */ code.addAstore(5);
        /* 79  */ code.addAload(0);
        /* 80  */ code.addLdc("inner-inner-try");
        /* 82  */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 85  */ addJump(code, Opcode.GOTO, 106);
        /* 88  */ code.addAstore(8);
        /* 90  */ addJump(code, Opcode.JSR, 96);
        /* 93  */ code.addAload(8);
        /* 95  */ code.add(Opcode.ATHROW);
        /* 96  */ code.addAstore(7);
        /* 98  */ code.addAload(0);
        /* 99  */ code.addLdc("inner-finally");
        /* 101 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 104 */ code.addRet(7);
        /* 106 */ addJump(code, Opcode.JSR, 96);
        /* 109 */ code.addRet(5);
        /* 111 */ addJump(code, Opcode.JSR, 77);
        /* 114 */ addJump(code, Opcode.GOTO, 123);
        /* 117 */ code.addAload(0);
        /* 118 */ code.addLdc("switch - 1789");
        /* 120 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass});
        /* 123 */ code.addRet(3);
        /* 125 */ addJump(code, Opcode.JSR, 31);
        /* 128 */ code.addOpcode(Opcode.RETURN);
        code.addExceptionHandler(6, 12, 15, "java.lang.RuntimeException");
        code.addExceptionHandler(6, 20, 23, 0);
        code.addExceptionHandler(125, 128, 23, 0);
        code.addExceptionHandler(60, 69, 69, 0);
        code.addExceptionHandler(111, 114, 69, 0);
        code.addExceptionHandler(79, 88, 88, 0);
        code.addExceptionHandler(106, 109, 88, 0);
        info.setCodeAttribute(code.toCodeAttribute());
        clazz.addMethod(method);
        clazz.writeFile("/tmp");
    }

    private static void addJump(Bytecode code, int opcode, int pos) {
        int current = code.currentPc();
        code.addOpcode(opcode);
        code.addIndex(pos - current);
    }
}
