package javassist;

import javassist.bytecode.*;
import javassist.bytecode.annotation.*;
import javassist.expr.*;
import test3.*;

public class JvstTest3 extends JvstTestRoot {
    public JvstTest3(String name) {
         super(name);
    }

    public void testAnnotation() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest");
        Object[] all = cc.getAnnotations();
        Anno a = (Anno)all[0];
        assertEquals('0', a.c());
        assertEquals(true, a.bool());
        assertEquals(1, a.b());
        assertEquals(2, a.s());
        assertEquals(3, a.i());
        assertEquals(4L, a.j());
        assertEquals(5.0F, a.f());
        assertEquals(6.0, a.d());
        assertEquals("7", a.str());
        assertEquals(AnnoTest.class, a.clazz());
        assertEquals(3, a.anno2().str().length);
    }

    public void testAnnotation2() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest2");
        Object[] all = cc.getAnnotations();
        Anno a = (Anno)all[0];
        assertEquals('a', a.c());
        assertEquals(false, a.bool());
        assertEquals(11, a.b());
        assertEquals(12, a.s());
        assertEquals(13, a.i());
        assertEquals(14L, a.j());
        assertEquals(15.0F, a.f());
        assertEquals(16.0, a.d());
        assertEquals("17", a.str());
        assertEquals(String.class, a.clazz());
        assertEquals(11, a.anno2().i()[0]);
    }

    public void testAnnotation3() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest3");
        Object[] all = cc.getAnnotations();
        assertEquals(2, all.length);
        int i;
        if (all[0] instanceof Anno2)
            i = 0;
        else
            i = 1;

        Anno2 a = (Anno2)all[i];
        assertEquals(1, a.i()[0]);
        assertEquals(test3.ColorName.RED, a.color());
        assertEquals(test3.ColorName.BLUE, a.color2()[0]);
    }

    public void testAnnotation4() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest4");
        Object[] all = cc.getAnnotations();
        Anno3 a = null;
        for (int i = 0; i < all.length; i++)
            if (all[i] instanceof Anno3)
                a = (Anno3)all[i];

        assertTrue(a != null);
        assertEquals('0', a.c()[0]);
        assertEquals(true, a.bool()[0]);
        assertEquals(1, a.b()[0]);
        assertEquals(2, a.s()[0]);
        assertEquals(3, a.i()[0]);
        assertEquals(4L, a.j()[0]);
        assertEquals(5.0F, a.f()[0]);
        assertEquals(6.0, a.d()[0]);
        assertEquals("7", a.str()[0]);
        assertEquals(AnnoTest.class, a.clazz()[0]);
        assertEquals(11, a.anno2()[0].i()[0]);
    }

    public void testAnnotation5() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest5");
        Object[] all = cc.getField("bar").getAnnotations();
        Anno2 a2 = (Anno2)all[0];
        assertEquals(test3.ColorName.RED, a2.color());

        all = cc.getDeclaredMethod("foo").getAnnotations();
        Anno a = (Anno)all[0];
        assertEquals("7", a.str());
    }

    public void testAnnotation6() throws Exception {
        CtClass cc = sloader.get("test3.AnnoTest6");
        Object[] all = cc.getAnnotations();
        Anno6 a = (Anno6)all[0];
        assertEquals(0, a.str1().length);
        assertEquals(0, a.str2().length);
    }

    public void testChainedException() throws Exception {
        try {
            throwChainedException();
        }
        catch (CannotCompileException e) {
            e.printStackTrace(System.out);
        }

        try {
            throwChainedException2();
        }
        catch (CannotCompileException e) {
            e.printStackTrace(System.out);
        }

        try {
            throwChainedException3();
        }
        catch (Exception e) {
            e.printStackTrace(System.out);
        }

    }

    public void throwChainedException() throws Exception {
        throw new CannotCompileException("test");
    }

    public void throwChainedException2() throws Exception {
        Throwable e = new CannotCompileException("test");
        throw new CannotCompileException("test2", e);
    }

    public void throwChainedException3() throws Exception {
        Throwable e = new CannotCompileException("testA");
        Throwable e2 = new CannotCompileException("testB", e);
        throw new Exception(e2);
    }

    // JIRA Javassist-12
    public void testInnerClassMethod() throws Exception {
        CtClass cc = sloader.get("test3.InnerMethod");
        CtMethod m1 = cc.getDeclaredMethod("test");
        m1.setBody("{inner.test();}");

        CtMethod m2 = CtNewMethod.make(
            "public int bar() {"
          + "  if (counter-- <= 0) return 3;"
          + "  else return bar();"
          + "}",
            cc);
        cc.addMethod(m2);

        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(1, invoke(obj, "foo"));
        assertEquals(3, invoke(obj, "bar"));
    }

    public void testCheckModifyAndPruned() throws Exception {
        CtClass cc = sloader.get("test3.CheckModify");
        cc.addField(new CtField(CtClass.intType, "j", cc));
        cc.stopPruning(false);
        cc.toBytecode();
        try {
            cc.getClassFile();
            fail();
        }
        catch (RuntimeException e) {
            // System.err.println(e.getMessage());
            assertTrue(e.getMessage().indexOf("prune") >= 0);
        }
    }

    public void testReplaceNew() throws Exception {
        CtClass cc = sloader.get("test3.ReplaceNew");
        CtMethod m1 = cc.getDeclaredMethod("run");
        m1.instrument(new ExprEditor() {
            public void edit(NewExpr n) throws CannotCompileException {
                n.replace("{ i++; $_ = null; }");
            }
        });

        CtMethod m2 = cc.getDeclaredMethod("run2");
        m2.instrument(new ExprEditor() {
            public void edit(NewExpr n) throws CannotCompileException {
                n.replace("{ j++; $_ = null; }");
            }
        });

        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(5, invoke(obj, "run"));
        assertEquals(2, invoke(obj, "run2"));
    }

    public void testPublicInner() throws Exception {
        CtClass cc0 = sloader.get("test3.PublicInner2");
        int mod = cc0.getClassFile2().getAccessFlags();
        System.out.println("testPublicInner: " + mod);

        CtClass cc = sloader.get("test3.PublicInner");
        CtClass jp = cc.makeNestedClass("Inner", true);
        assertEquals(Modifier.PUBLIC | Modifier.STATIC, jp.getModifiers());
        assertEquals(Modifier.PUBLIC | Modifier.STATIC,
                     getPublicInner(jp, "Inner"));
        assertEquals(Modifier.PUBLIC | Modifier.STATIC,
                     getPublicInner(cc, "Inner"));

        jp.setModifiers(Modifier.STATIC);
        assertEquals(Modifier.STATIC, jp.getModifiers());
        assertEquals(Modifier.STATIC, getPublicInner(jp, "Inner"));
        assertEquals(Modifier.STATIC, getPublicInner(cc, "Inner"));

        jp.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
        assertEquals(Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC, jp.getModifiers());
        assertEquals(Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC,
                     getPublicInner(jp, "Inner"));
        assertEquals(Modifier.PUBLIC | Modifier.ABSTRACT | Modifier.STATIC,
                     getPublicInner(cc, "Inner"));

        cc.writeFile();
        jp.writeFile();
    }

    private int getPublicInner(CtClass cc, String name) throws Exception {
        ClassFile cf = cc.getClassFile();
        InnerClassesAttribute ica
            = (InnerClassesAttribute)cf.getAttribute(
                                         InnerClassesAttribute.tag);
        assertEquals(name, ica.innerName(0));
        return ica.accessFlags(0);
    }

    public void testConstructorToMethod() throws Exception {
        CtClass cc = sloader.get("test3.Constructor");
        CtConstructor[] cons = cc.getConstructors();
        CtConstructor sinit = cc.getClassInitializer();

        for (int i = 0; i < cons.length; i++) {
            CtConstructor ccons = cons[i];
            String desc = ccons.getSignature();
            boolean result = false;
            if (desc.equals("()V"))
                result = false;
            else if (desc.equals("(I)V"))
                result = true;
            else if (desc.equals("(Ljava/lang/String;)V"))
                result = false;
            else if (desc.equals("(D)V"))
                result = true;
            else
                fail("unknonw constructor");

            assertEquals(result, ccons.callsSuper());
        }

        CtClass cc2 = sloader.get("test3.Constructor2");
        for (int i = 0; i < cons.length; i++)
            cc2.addMethod(cons[i].toMethod("m", cc2));

        cc2.addMethod(sinit.toMethod("sinit", cc2));
        cc2.addMethod(CtMethod.make(
            "public int run() { m(); m(5); m(\"s\"); m(0.0);" +
            "  sinit(); return i + str.length(); }",
            cc2));
        cc2.writeFile();

        Object obj = make(cc2.getName());
        assertEquals(119, invoke(obj, "run"));
    }

    public void testUnique() throws Exception {
        CtClass cc = sloader.get("test3.Unique");
        CtClass cc2 = sloader.get("test3.Unique3");
        assertEquals("poi", cc.makeUniqueName("poi"));
        assertEquals("foo102", cc.makeUniqueName("foo"));
        assertEquals("bar102", cc2.makeUniqueName("bar"));
        assertEquals("foo100", cc2.makeUniqueName("foo"));
    }

    public void testGetMethods() throws Exception {
        CtClass cc = sloader.get("test3.GetMethods");
        assertEquals(3, cc.getConstructors().length);
        assertEquals(6, cc.getFields().length);
        assertEquals(6 + Object.class.getMethods().length + 2,
                     cc.getMethods().length);
    }

    public void testVisiblity() throws Exception {
        CtClass cc = sloader.get("test3.Visible");
        CtClass cc2 = sloader.get("test3.Visible2");
        CtClass subcc = sloader.get("test3.sub.Visible");
        CtClass subcc2 = sloader.get("test3.sub.Visible2");
        CtClass top = sloader.get("VisibleTop");
        CtClass top2 = sloader.get("VisibleTop2");

        assertEquals(true, cc.getField("pub").visibleFrom(cc2));
        assertEquals(true, cc.getField("pub").visibleFrom(subcc));

        assertEquals(true, cc.getField("pri").visibleFrom(cc));
        assertEquals(false, cc.getField("pri").visibleFrom(cc2));

        assertEquals(true, cc.getField("pack").visibleFrom(cc));
        assertEquals(true, cc.getField("pack").visibleFrom(cc2));
        assertEquals(false, cc.getField("pack").visibleFrom(subcc));
        assertEquals(false, cc.getField("pack").visibleFrom(top));

        assertEquals(true, cc.getField("pro").visibleFrom(cc));
        assertEquals(true, cc.getField("pro").visibleFrom(cc2));
        assertEquals(true, cc.getField("pro").visibleFrom(subcc2));
        assertEquals(false, cc.getField("pro").visibleFrom(subcc));
        assertEquals(false, cc.getField("pro").visibleFrom(top));

        assertEquals(true, top.getField("pack").visibleFrom(top2));
        assertEquals(false, top.getField("pack").visibleFrom(cc));
    }

    public void testNewAnnotation() throws Exception {
        CtClass c = sloader.makeClass("test3.NewClass");
        ClassFile cf = c.getClassFile();
        ConstPool cp = cf.getConstPool();
        AnnotationsAttribute attr
            = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag);
        javassist.bytecode.annotation.Annotation a
            = new Annotation("test3.ChibaAnnotation", cp);
        a.addMemberValue("name", new StringMemberValue("Chiba", cp));
        a.addMemberValue("version", new StringMemberValue("Chiba", cp));
        a.addMemberValue("description", new StringMemberValue("Chiba", cp));
        a.addMemberValue("interfaceName", new StringMemberValue("Chiba", cp));
        attr.setAnnotation(a);
        System.out.println(attr);
        cf.addAttribute(attr);
        cf.setVersionToJava5();
        Object [] ans = c.getAnnotations() ;
        System.out.println("Num Annotation : " +ans.length);

        // c.debugWriteFile();
        Class newclass = c.toClass();
        java.lang.annotation.Annotation[] anns = newclass.getAnnotations();
        System.out.println("Num NewClass Annotation : " +anns.length);
        assertEquals(ans.length, anns.length);
    }

    public void testRecursiveReplace() throws Exception {
        CtClass cc = sloader.get("test3.RecReplace");
        CtMethod m1 = cc.getDeclaredMethod("run");
        final ExprEditor e2 = new ExprEditor() {
            public void edit(MethodCall mc) throws CannotCompileException {
                if (mc.getMethodName().equals("bar")) {
                    mc.replace("{ double k = 10.0; $_ = $proceed($1 + k); }");
                }
            }
        };
        m1.instrument(new ExprEditor() {
            public void edit(MethodCall mc) throws CannotCompileException {
                if (mc.getMethodName().equals("foo")) {
                    mc.replace("{ int k = bar($$); $_ = k + $proceed(7.0); }",
                    e2);
                }
            }
        });

        CtMethod m2 = cc.getDeclaredMethod("run2");
        m2.instrument(new ExprEditor() {
            public void edit(MethodCall mc) throws CannotCompileException {
                if (mc.getMethodName().equals("foo")) {
                    mc.replace("{ int k = bar($$); $_ = k + $proceed(7.0); }");
                }
            }
        });

        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(26, invoke(obj, "run"));
        assertEquals(16, invoke(obj, "run2"));
    }

    public void testRecursiveReplace2() throws Exception {
        final ExprEditor[] ref = new ExprEditor[1];
        ExprEditor e2 = new ExprEditor() {
            public void edit(FieldAccess fa) throws CannotCompileException {
                if (fa.getFieldName().equals("value2")
                    && fa.isWriter()) {
                    fa.replace("{ $_ = $proceed($1 + 2); }");
                }
            }
        };
        ExprEditor e1 = new ExprEditor() {
            public void edit(FieldAccess fa) throws CannotCompileException {
                if (fa.getFieldName().equals("value")
                    && fa.isWriter()) {
                    fa.replace("{ value2 = $1; value = value2; }",
                               ref[0]);
                }
            }
        };

        CtClass cc = sloader.get("test3.RecReplace2");
        CtMethod m1 = cc.getDeclaredMethod("run");
        ref[0] = e2;
        m1.instrument(e1);
        CtMethod m2 = cc.getDeclaredMethod("run2");
        ref[0] = null;
        m2.instrument(e1);

        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(28, invoke(obj, "run"));
        assertEquals(24, invoke(obj, "run2"));
    }

    public void testInnerModifier() throws Exception {
        CtClass cc = sloader.get("test3.InnerClass$Inner");
        assertEquals(Modifier.PUBLIC | Modifier.STATIC, cc.getModifiers());
        CtClass cc2 = sloader.get("test3.InnerClass$Inner2");
        assertEquals(Modifier.PUBLIC, cc2.getModifiers());
    }

    public void testMethodLookup() throws Exception {
        CtClass cc = sloader.get("test3.SubValue");
        CtMethod m1 = CtNewMethod.make(
                "public int run() {" +
                " test3.SuperValue sup = new test3.SuperValue();" +
                " test3.SubValue sub = new test3.SubValue();" +
                " return this.after(sup, sub, sub) == null ? 0 : 1;" +
                "}",
                cc);
        cc.addMethod(m1);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(1, invoke(obj, "run"));
    }

    public void testFieldAccessType() throws Exception {
        CtClass cc = sloader.get("test3.FieldAccessType");
        CtMethod m1 = cc.getDeclaredMethod("access");
        final boolean[] result = new boolean[1];
        result[0] = true;
        ExprEditor e = new ExprEditor() {
            public void edit(FieldAccess fa) throws CannotCompileException {
                if (!fa.getSignature().equals("[I"))
                    result[0] = false;
            }
        };
        m1.instrument(e);
        assertTrue(result[0]);
    }

    public void testGetNestedClasses() throws Exception {
        CtClass cc = sloader.get("test3.NestedClass");
        CtClass[] nested = cc.getNestedClasses();
        assertEquals(4, nested.length);
        testGetNestedClasses("test3.NestedClass$Inner", nested);
        testGetNestedClasses("test3.NestedClass$StaticNested", nested);
        testGetNestedClasses("test3.NestedClass$1Local", nested);
        testGetNestedClasses("test3.NestedClass$1", nested);
    }

    private void testGetNestedClasses(String name, CtClass[] classes) {
        for (int i = 0; i < classes.length; i++)
            if (classes[i].getName().equals(name))
                return;

        fail("no class: " + name);
    }

    public void testGetParmeterAnnotations() throws Exception {
        CtClass cc = sloader.get("test3.ParamAnno");
        Object[][] anno = cc.getDeclaredMethod("foo").getParameterAnnotations();
        assertEquals(4, anno.length);
        assertEquals(0, anno[0].length);
        assertEquals(0, anno[1].length);
        assertEquals(0, anno[2].length);
        assertEquals(0, anno[3].length);

        Object[][] anno2 = cc.getDeclaredMethod("bar").getParameterAnnotations();
        assertEquals(0, anno2.length);

        Class rc = Class.forName("test3.ParamAnno");
        java.lang.reflect.Method[] ms = rc.getDeclaredMethods();
        java.lang.reflect.Method m1, m2;
        if (ms[0].getName().equals("foo")) {
            m1 = ms[0];
            m2 = ms[1];
        }
        else {
            m1 = ms[1];
            m2 = ms[0];
        }

        java.lang.annotation.Annotation[][] ja;
        ja = m1.getParameterAnnotations();
        assertEquals(4, ja.length);
        assertEquals(0, ja[0].length);
        assertEquals(0, ja[1].length);
        assertEquals(0, ja[2].length);
        assertEquals(0, ja[3].length);

        assertEquals(0, m2.getParameterAnnotations().length);
    }

    public void testSetModifiers() throws Exception {
        CtClass cc = sloader.get("test3.SetModifiers");
        try {
            cc.setModifiers(Modifier.STATIC | Modifier.PUBLIC);
            fail("static public class SetModifiers");
        }
        catch (RuntimeException e) {
            assertEquals("cannot change test3.SetModifiers into a static class", e.getMessage());
        }

        cc = sloader.get("test3.SetModifiers$A");
        cc.setModifiers(Modifier.STATIC | Modifier.PUBLIC);
        assertTrue(Modifier.isStatic(cc.getModifiers()));
        assertTrue((cc.getClassFile2().getAccessFlags() & AccessFlag.STATIC) == 0);
    }

    public void testFieldCopy() throws Exception {
        CtClass cc = sloader.get("test3.FieldCopy");
        CtClass cc2 = sloader.get("test3.FieldCopy2");
        CtField f = cc.getDeclaredField("foo");
        cc2.addField(new CtField(f, cc2));
        CtField f2 = cc2.getDeclaredField("foo");
        Object[] anno = f2.getAnnotations();
        assertTrue(anno[0] instanceof test3.FieldCopy.Test);
        assertEquals(Modifier.PRIVATE | Modifier.STATIC,
                     f2.getModifiers());
    }

    public void testMethodRedirect() throws Exception {
        CtClass cc = sloader.get("test3.MethodRedirect");
        CtClass cc2 = sloader.get("test3.MethodRedirectIntf");
        CtMethod foo = cc.getDeclaredMethod("foo");
        CtMethod poi = cc.getDeclaredMethod("poi");
        CtMethod bar = cc.getDeclaredMethod("bar");
        CtMethod afo = cc2.getDeclaredMethod("afo");
        CodeConverter conv = new CodeConverter();

        try {
            conv.redirectMethodCall(foo, bar);
            fail("foo");
        }
        catch (CannotCompileException e) {}

        try {
            conv.redirectMethodCall(poi, bar);
            fail("bar");
        }
        catch (CannotCompileException e) {}

        try {
            conv.redirectMethodCall(bar, afo);
            fail("afo");
        }
        catch (CannotCompileException e) {}
        bar.setName("bar2");
        conv.redirectMethodCall("bar", bar);
        cc.instrument(conv);
        // cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(2, invoke(obj, "test"));
    }

    public void testMethodRedirect2() throws Exception {
        CtClass cc = sloader.get("test3.MethodRedirect2");
        CtClass sup = sloader.get("test3.MethodRedirect2Sup");
        CtClass supsup = sloader.get("test3.MethodRedirect2SupSup");
        CtClass intf = sloader.get("test3.MethodRedirect2SupIntf");
        CtMethod bfo2 = supsup.getDeclaredMethod("bfo2");
        CtMethod afo2 = sup.getDeclaredMethod("afo2");
        CtMethod foo = intf.getDeclaredMethod("foo");
        CodeConverter conv = new CodeConverter();

        conv.redirectMethodCall("bfo", bfo2);
        conv.redirectMethodCall("afo", afo2);
        conv.redirectMethodCall("bar", foo);
        conv.redirectMethodCall("bar2", foo);
        cc.instrument(conv);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(524, invoke(obj, "test"));
    }

    public void testClassMap() throws Exception {
        ClassMap map = new ClassMap();
        map.put("aa", "AA");
        map.put("xx", "XX");
        assertEquals("AA", map.get("aa"));
        assertEquals(null, map.get("bb"));
        ClassMap map2 = new ClassMap(map);
        map2.put("aa", "A1");
        map2.put("cc", "CC");
        assertEquals("A1", map2.get("aa"));
        assertEquals("CC", map2.get("cc"));
        assertEquals("XX", map2.get("xx"));
        assertEquals(null, map2.get("bb"));
    }

    public void testEmptyConstructor() throws Exception {
        CtClass cc = sloader.get("test3.EmptyConstructor");
        CtConstructor[] cons = cc.getDeclaredConstructors();
        for (int i = 0; i < cons.length; i++)
            assertTrue("index: " + i, cons[i].isEmpty());

        cc = sloader.get("test3.EmptyConstructor2");
        cons = cc.getDeclaredConstructors();
        for (int i = 0; i < cons.length; i++)
            assertTrue("index: " + i, cons[i].isEmpty());

        cc = sloader.get("test3.EmptyConstructor3");
        cons = cc.getDeclaredConstructors();
        for (int i = 0; i < cons.length; i++)
            assertTrue("index: " + i, cons[i].isEmpty());

        cc = sloader.get("test3.EmptyConstructor4");
        cons = cc.getDeclaredConstructors();
        for (int i = 0; i < cons.length; i++)
            assertFalse("index: " + i, cons[i].isEmpty());
    }

    public void testTransformRead() throws Exception {
        CtClass cc = sloader.get("test3.TransformRead");
        CtClass parent = cc.getSuperclass();
        CtMethod m = cc.getDeclaredMethod("foo");
        CodeConverter conv = new CodeConverter();
        conv.replaceFieldRead(parent.getField("value"), cc, "getValue");
        conv.replaceFieldRead(parent.getField("value2"), cc, "getValue2");
        m.instrument(conv);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(11100, invoke(obj, "foo"));
    }

    public void testDescriptor() throws Exception {
        assertEquals("int", Descriptor.toString("I")); 
        assertEquals("long[][]", Descriptor.toString("[[J")); 
        assertEquals("java.lang.Object", Descriptor.toString("Ljava/lang/Object;"));
        assertEquals("()", Descriptor.toString("()V")); 
        assertEquals("(int)", Descriptor.toString("(I)V")); 
        assertEquals("(int,int[])", Descriptor.toString("(I[I)V"));
        assertEquals("(java.lang.String,Foo[][])", Descriptor.toString("(Ljava/lang/String;[[LFoo;)V"));
    }

    public void testLongName() throws Exception {
        CtClass cc = sloader.get("test3.Name");
        assertEquals("test3.Name.<clinit>()", cc.getClassInitializer().getLongName());
        assertEquals("test3.Name()", cc.getConstructor("()V").getLongName());
        assertEquals("test3.Name(int)", cc.getConstructor("(I)V").getLongName());
        assertEquals("test3.Name(test3.Name)", cc.getConstructor("(Ltest3/Name;)V").getLongName());
        assertEquals("test3.Name(test3.Name,java.lang.String)",
                     cc.getConstructor("(Ltest3/Name;Ljava/lang/String;)V").getLongName());

        assertEquals("test3.Name.foo()", cc.getDeclaredMethod("foo").getLongName());
        assertEquals("test3.Name.foo2(int)", cc.getDeclaredMethod("foo2").getLongName());
        assertEquals("test3.Name.foo3(java.lang.String)", cc.getDeclaredMethod("foo3").getLongName());
        assertEquals("test3.Name.foo4(java.lang.String[])", cc.getDeclaredMethod("foo4").getLongName());
        assertEquals("test3.Name.foo5(int,java.lang.String)", cc.getDeclaredMethod("foo5").getLongName());
    }

    public void testPackageName() throws Exception {
        CtClass cc = sloader.get("test3.PackName");
        CtMethod m1 = CtNewMethod.make(
                "public int run() {" +
                " return test3.PackName.get() + test3.sub.SubPackName.get(); }",
                cc);
        cc.addMethod(m1);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(8, invoke(obj, "run"));
    }

    public void testErasure() throws Exception {
        CtClass cc = sloader.get("test3.Erasure");
        cc.addInterface(sloader.get("test3.ErasureGet"));
        CtMethod m1 = CtNewMethod.make(
                "public Object get() { return value; }",
                cc);
        cc.addMethod(m1);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(4, invoke(obj, "run"));
    }

    /* Check the result of JvstTest#testFieldInit()
     * This tests CtClassType#saveClassFile().
     */
    public void testFieldInitAgain() throws Exception {
        System.gc();
        CtClass cc = sloader.get("test1.FieldInit");
        CtField f = cc.getDeclaredField("f1");
        assertEquals(CtClass.intType, f.getType());
        assertTrue("f.hashCode() doesn't change!", f.hashCode() != JvstTest.testFieldInitHash);
    }

    /* This tests CtClassType#saveClassFile().
     * A CtMethod is not garbage collected, its CtClass is never
     * compressed.
     */
    public void testCalleeBeforeAgain() throws Exception {
        CtClass cc = sloader.get("test1.CalleeBefore");
        assertEquals(JvstTest.testCalleeBeforeMethod,
                     cc.getDeclaredMethod("m1"));
        assertEquals(JvstTest.testCalleeBeforeMethod2,
                     cc.getDeclaredMethod("m2").getMethodInfo2().hashCode());
    }

    public void testSetSuper() throws Exception {
        CtClass cc = sloader.get("test3.Superclass");
        CtClass cc3 = sloader.get("test3.Superclass3");
        cc3.setModifiers(Modifier.setPublic(cc3.getModifiers()));
        cc.setSuperclass(sloader.get("test3.Superclass3"));
        cc.writeFile();
        cc3.writeFile();
        Object obj = make(cc.getName());
        assertEquals(21, invoke(obj, "test"));
    }

    public void testFrozen2() throws Exception {
        CtClass cc = sloader.get("test3.Frozen");
        cc.addField(new CtField(CtClass.intType, "k", cc));
        cc.toBytecode();
        cc.toBytecode();
        cc = sloader.makeClass("test3.Frozen2");
        cc.toBytecode();
        cc.toBytecode();
    }

    public void testCopyAnnotation() throws Exception {
        CtClass cc1 = sloader.get("test3.CopyAnnoBase");
        CtMethod m1 = cc1.getDeclaredMethod("getX");
        CtClass cc2 = sloader.get("test3.CopyAnno");
        CtMethod m2 = cc2.getDeclaredMethod("getX");
        copyAnnotations(m1, m2);
        cc2.getClassFile();
        Class clazz = cc2.toClass();
        java.lang.reflect.Method m = clazz.getDeclaredMethod("getX", new Class[0]);
        assertEquals(1, m.getAnnotations().length);
        test3.VisibleAnno a = m.getAnnotation(test3.VisibleAnno.class);
        assertNotNull(a);
    }

    private void copyAnnotations(CtMethod src, CtMethod dest)
        throws NotFoundException
    {
       MethodInfo srcInfo = src.getMethodInfo2();
       MethodInfo destInfo = dest.getMethodInfo2();
       copyAnnotations(srcInfo, destInfo, AnnotationsAttribute.invisibleTag);
       copyAnnotations(srcInfo, destInfo, AnnotationsAttribute.visibleTag);
    }

    private void copyAnnotations(MethodInfo src, MethodInfo dest, String annotationTag) {
       AnnotationsAttribute attribute = (AnnotationsAttribute)src.getAttribute(annotationTag);
       if (attribute != null)
          dest.addAttribute(attribute.copy(dest.getConstPool(), new java.util.HashMap()));
    }

    public void testStaticFinalField() throws Exception {
        CtClass cc = sloader.makeClass("test3.StaticFinalField");
        CtField fj = new CtField(CtClass.longType, "j", cc);
        fj.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fj, CtField.Initializer.constant(2L));
        CtField fi = new CtField(CtClass.intType, "i", cc);
        fi.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fi, CtField.Initializer.constant(3));
        CtField fs = new CtField(CtClass.shortType, "s", cc);
        fs.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fs, CtField.Initializer.constant(4));
        CtField fc = new CtField(CtClass.charType, "c", cc);
        fc.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fc, CtField.Initializer.constant('5'));
        CtField fby = new CtField(CtClass.byteType, "by", cc);
        fby.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fby, CtField.Initializer.constant(6));
        CtField fb = new CtField(CtClass.booleanType, "b", cc);
        fb.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fb, CtField.Initializer.constant(true));
        CtField ff = new CtField(CtClass.floatType, "f", cc);
        ff.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(ff, CtField.Initializer.constant(7.0F));
        CtField fstr = new CtField(sloader.get("java.lang.String"), "str", cc);
        fstr.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fstr, CtField.Initializer.constant("foo"));
        CtField fobj = new CtField(sloader.get("java.lang.Object"), "obj", cc);
        fobj.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
        cc.addField(fobj, CtField.Initializer.constant("bar"));

        cc.writeFile();
        Class clazz = cc.toClass();
        assertEquals(2L, clazz.getField("j").getLong(null));
        assertEquals(3, clazz.getField("i").getInt(null));
        assertEquals(4, clazz.getField("s").getShort(null));
        assertEquals('5', clazz.getField("c").getChar(null));
        assertEquals(6, clazz.getField("by").getByte(null));
        assertEquals(true, clazz.getField("b").getBoolean(null));
        assertEquals(7.0F, clazz.getField("f").getFloat(null));
        assertEquals("foo", clazz.getField("str").get(null));
        assertEquals("bar", clazz.getField("obj").get(null));
    }

    /*
    public void testClassPath() throws Exception {
        ClassPool cp = new ClassPool(null);
        cp.appendClassPath("./test-classpath.JaR");
        assertNotNull(cp.get("test.Point"));
        cp = new ClassPool(null);
        cp.appendClassPath("./*");
        assertNotNull(cp.get("javassist.bytecode.Gap0Example"));
    }*/

    public void testVoidReturn() throws Exception {
        CtClass cc = sloader.get("test3.VoidReturn");
        CtMethod m1 = cc.getDeclaredMethod("foo");
        m1.insertAfter("System.out.println(\"return value: \" + $_);", true);
        cc.writeFile();
        make(cc.getName());
    }

    public void testInsertParam0() throws Exception {
        assertEquals("(I)V", Descriptor.insertParameter(CtClass.intType, "()V"));
        assertEquals("(ILjava/lang/Object;)V", Descriptor.insertParameter(CtClass.intType, "(Ljava/lang/Object;)V"));
        assertEquals("(IJ)V", Descriptor.appendParameter(CtClass.longType, "(I)V"));
        assertEquals("(Ljava/lang/String;I)V", Descriptor.insertParameter(sloader.get("java.lang.String"), "(I)V"));
    }

    public void testInsertParam() throws Exception {
        CtClass cc = sloader.get("test3.InsParam");
        CtMethod m1 = cc.getDeclaredMethod("foo");
        m1.insertParameter(CtClass.longType);
        m1.insertBefore("$2 += (int)$1;");

        CtMethod m2 = cc.getDeclaredMethod("bar");
        m2.addParameter(CtClass.doubleType);
        m2.insertBefore("$1 += (int)$2;");

        CtMethod m3 = cc.getDeclaredMethod("poi");
        m3.addParameter(sloader.get("java.lang.Object"));
        m3.insertBefore("$2 = (String)$3;");

        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(11, invoke(obj, "foo", 10L, 1));
        assertEquals(11, invoke(obj, "bar", 1, 10.0));
        assertEquals(3, invoke(obj, "poi", 1, "x", "xx"));
    }

    private int invoke(Object target, String method, long arg1, int arg2)
        throws Exception
    {
        java.lang.reflect.Method m =
            target.getClass().getMethod(method, new Class[] { long.class, int.class });
        Object res = m.invoke(target, new Object[] { new Long(arg1), new Integer(arg2)});
        return ((Integer)res).intValue();
    }

    private int invoke(Object target, String method, int arg1, double arg2)
        throws Exception
    {
        java.lang.reflect.Method m =
            target.getClass().getMethod(method, new Class[] { int.class, double.class });
        Object res = m.invoke(target, new Object[] { new Integer(arg1), new Double(arg2)});
        return ((Integer)res).intValue();
    }

    private int invoke(Object target, String method, int arg1, String arg2, Object arg3)
        throws Exception
    {
        java.lang.reflect.Method m =
            target.getClass().getMethod(method, new Class[] { int.class, String.class, Object.class });
        Object res = m.invoke(target, new Object[] { new Integer(arg1), arg2, arg3});
        return ((Integer)res).intValue();
    }

    public void testInvokeinterface() throws Exception {
        // JIRA JASSIST-60
        CtClass cc = sloader.get("test3.InvokeIntf");
        CtMethod mth = cc.getDeclaredMethod("doit");
        ExprEditor e = new ExprEditor() {
            public void edit(MethodCall m) throws CannotCompileException {
                String block = "{$_=$proceed($$);}";
                m.replace(block);
            }
        };
        mth.instrument(e);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(7, invoke(obj, "test"));
    }

    public void testInvokeArrayObj() throws Exception {
        // JIRA JASSIST-61
        CtClass cc = sloader.get("test3.InvokeArray");
        CtMethod mth = cc.getDeclaredMethod("doit");
        ExprEditor e = new ExprEditor() {
            public void edit(MethodCall m) throws CannotCompileException {
                String block = "{$_=$proceed($$);}";
                m.replace(block);
            }
        };
        mth.instrument(e);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(3, invoke(obj, "test"));
    }

    public void testNewExprTry() throws Exception {
        CtClass cc = sloader.get("test3.NewExprTryCatch");
        CtMethod mth = cc.getDeclaredMethod("instrumentMe");
        ExprEditor e = new ExprEditor() {
            public void edit(NewExpr m) throws CannotCompileException {
                String block = "{$_=$proceed($$);}";
                m.replace(block);
            }
        };
        mth.instrument(e);

        // JIRA JASSIST-52
        CtMethod mth2 = cc.getDeclaredMethod("me2");
        ExprEditor e2 = new ExprEditor() {
            public void edit(MethodCall m) throws CannotCompileException {
                String block = "{try{$_=$proceed($$);} catch(Throwable t) {}}";
                m.replace(block);
            }
        };
        //mth2.instrument(e2);

        /*
        // JIRA JASSIST-53
        CtClass cc2 = sloader.get("test3.NewExprTryCatch2");
        CtBehavior mth3 = cc2.getDeclaredConstructors()[0];
        ExprEditor e3 = new ExprEditor() {
            public void edit(ConstructorCall m) throws CannotCompileException {
                String block = "{try {$_=$proceed($$);} catch (Throwable t) {}}";
                m.replace(block);
            }
        };
        mth3.instrument(e3);
        */

        cc.writeFile();
        // cc2.writeFile();
        Object obj = make(cc.getName());
        assertEquals(0, invoke(obj, "test"));
        // Object obj2 = make(cc2.getName());
    }

    public void testJIRA63() throws Exception {
        frameTypeTest(2);
        frameTypeTest(7);
    }

    private void frameTypeTest(final int initializerRepeatCount) throws Exception {
        // Get a class
        final CtClass cTst = sloader.get("test3.JIRA63");
        cTst.getClassFile().setMajorVersion(ClassFile.JAVA_6);
        try {
            // Create an initializer for the fields
            String initializer = "test3.JIRA63Helper";
            for(int i=0; i < initializerRepeatCount; i++)
                initializer += ".getAnObject(new Integer(1))";

            initializer += ";";

            // Add some fields
            final CtClass cObj = sloader.get("java.lang.Object");
            for(int i = 0; i < 7; i++)
                cTst.addField(new CtField(cObj, "a" + i, cTst), initializer);

            // Modify the constructors
            for(final CtConstructor m : cTst.getConstructors()) {
                m.insertAfter(initializer, true);
                m.insertBefore(initializer);
            }

            // Get the byte code.
            // cTst.toBytecode();
            cTst.writeFile();
            //make(cTst.getName());
        }
        finally {
            cTst.detach();
        }
    }

    public void testInsertBeforeType() throws Exception {
        CtClass cc = sloader.get("test3.InsertBeforeType");
        CtMethod m1 = cc.getDeclaredMethod("foo");
        m1.insertBefore("value = $type.getName();");
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(5, invoke(obj, "test")); 
    }

    public void testTransformNewClass() throws Exception {
        CodeConverter conv = new CodeConverter();
        conv.replaceNew(sloader.get("test3.TransNewClassOld"),
                        sloader.get("test3.TransNewClassNew"));
        CtClass cc = sloader.get("test3.TransNewClass");
        cc.instrument(conv);
        cc.writeFile();

        CtClass cc2 = sloader.get("test3.TransNewClass$TransNewClass2");
        cc2.instrument(conv);
        cc2.writeFile();

        Object obj = make(cc.getName());
        assertEquals(170, invoke(obj, "test")); 
        Object obj2 = make(cc2.getName());
        assertEquals(50, invoke(obj2, "test")); 
    }

    public void testInsertAfter() throws Exception {
        CtClass cc = sloader.get("test3.InsertAfter");
        CtMethod m1 = cc.getDeclaredMethod("foo");
        m1.insertAfter("k++;", true);
        CtConstructor cons = cc.getConstructor("()V");
        cons.insertAfter("k++;", true);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(6, invoke(obj, "test")); 
    }

    public void testInsertSwitch() throws Exception {
        CtClass cc = sloader.get("test3.Switch");
        CtMethod m1 = cc.getDeclaredMethod("foo");
        String sourceCode = "{"
            + "System.out.println(\"Bla!\");"
            + "}";
        String toInsert =
            " try " +
            " {   " +
            sourceCode +
            " }   " +
            " catch(Throwable e) " +
            " {   " +
            "   e.printStackTrace(); " +
            " }   ";

        m1.insertBefore(toInsert);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals(0, invoke(obj, "test"));
    }

    public void testStringBuilder() throws Exception {
        CtClass cc = sloader.get("test3.StrBuild");
        CtMethod mth = cc.getDeclaredMethod("test");
        ExprEditor ed = new ExprEditor() {
            public void edit(MethodCall m) throws CannotCompileException {
                String block = "$_=$proceed($$);";
                m.replace(block);
            }
        };
        mth.instrument(ed);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals('t', invoke(obj, "test"));
    }

    public void testInheritCons() throws Exception {
        CtClass s = sloader.get("test3.InheritCons");
        CtClass cc = sloader.makeClass("test3.InheritCons2", s);
        cc.writeFile();
        Object obj = make(cc.getName());
        assertEquals("test3.InheritCons2", obj.getClass().getName());
        assertEquals(3, obj.getClass().getDeclaredConstructors().length);

        cc = sloader.makeClass("InheritCons3", s);
        cc.writeFile();
        obj = make(cc.getName());
        assertEquals("InheritCons3", obj.getClass().getName());
        assertEquals(2, obj.getClass().getDeclaredConstructors().length);
    }

    public void testAddInterfaceMethod() throws Exception {
        CtClass cc = sloader.makeInterface("test3.AddIntfMth");
        CtMethod m = CtMethod.make("void foo();", cc);
        cc.addMethod(m);
        assertTrue(Modifier.isPublic(m.getModifiers()));
        CtMethod m2 = CtMethod.make("public void foo2();", cc);
        cc.addMethod(m2);
        assertTrue(Modifier.isPublic(m2.getModifiers()));
        CtMethod m3 = CtMethod.make("public void foo3() {}", cc);
        try {
            cc.addMethod(m3);
            if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_8)
                fail();
        }
        catch (CannotCompileException e) {
            // System.out.println(e);
        }
    }

    // JIRA-67
    public void test67() throws Exception {
        ClassPool pool = new ClassPool(true); 
        CtClass ctc = pool.makeClass("test3.Clazz67"); 
        StringBuilder sb = new StringBuilder("public void test() {"); 
        for (int i = 0; i < 1000; i++) { 
            sb.append("for(int i=0; i<10; i++) {}"); // line 1 
            // sb.append("for(int i=0; i<10; i++) {int j=i;}"); // line 2 
        } 

        sb.append("}"); 
        ctc.addMethod(CtNewMethod.make(sb.toString(), ctc));
        ctc.debugWriteFile();
        ctc.toClass().newInstance(); 
    }

    // JIRA-83
    public void testEmptyCatch() throws Exception {
        CtClass cc = sloader.get("test3.EmptyCatch");
        CtMethod mth = cc.getDeclaredMethod("test");
        mth.instrument(new ExprEditor() {
            public void edit(Handler h) throws CannotCompileException {
                try {
                    assertEquals(null, h.getType());
                    assertTrue(h.isFinally());
                } catch (NotFoundException e) {}
            }
        });
    }
}
