/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.bcel;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.ArrayElementValue;
import org.apache.bcel.classfile.ElementValue;
import org.apache.bcel.classfile.ElementValuePair;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.classfile.ParameterAnnotationEntry;
import org.apache.bcel.classfile.SimpleElementValue;
import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.ASTORE;
import org.apache.bcel.generic.AnnotationElementValueGen;
import org.apache.bcel.generic.AnnotationEntryGen;
import org.apache.bcel.generic.ArrayElementValueGen;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.ElementValueGen;
import org.apache.bcel.generic.ElementValuePairGen;
import org.apache.bcel.generic.GOTO;
import org.apache.bcel.generic.InstructionConstants;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableGen;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.SimpleElementValueGen;
import org.apache.bcel.generic.Type;
import org.apache.bcel.util.SyntheticRepository;

/**
 * The program that some of the tests generate looks like this:
 *
 * <pre>
 * public class HelloWorld
 * {
 *  public static void main(String[] argv)
 *  {
 *      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
 *      String name = null;
 *
 *      try
 *      {
 *          name = &quot;Andy&quot;;
 *      }
 *      catch (IOException e)
 *      {
 *          return;
 *      }
 *      System.out.println(&quot;Hello, &quot; + name);
 *  }
 * }
 * </pre>
 */
public class GeneratingAnnotatedClassesTestCase extends AbstractTestCase
{
    /**
     * Steps in the test:
     * <ol>
     * <li>Programmatically construct the HelloWorld program</li>
     * <li>Add two simple annotations at the class level</li>
     * <li>Save the class to disk</li>
     * <li>Reload the class using the 'static' variant of the BCEL classes</li>
     * <li>Check the attributes are OK</li>
     * </ol>
     */
    public void testGenerateClassLevelAnnotations()
            throws ClassNotFoundException
    {
        // Create HelloWorld
        ClassGen cg = createClassGen("HelloWorld");
        cg.setMajor(49);
        cg.setMinor(0);
        ConstantPoolGen cp = cg.getConstantPool();
        InstructionList il = new InstructionList();
        cg.addAnnotationEntry(createSimpleVisibleAnnotation(cp));
        cg.addAnnotationEntry(createSimpleInvisibleAnnotation(cp));
        buildClassContents(cg, cp, il);
        //System.out.println(cg.getJavaClass().toString());
        dumpClass(cg, "HelloWorld.class");
        JavaClass jc = getClassFrom(".", "HelloWorld");
        AnnotationEntry[] as = jc.getAnnotationEntries();
        assertTrue("Should be two AnnotationEntries but found " + as.length,
                as.length == 2);
        // TODO L??;
        assertTrue(
                "Name of annotation 1 should be LSimpleAnnotation; but it is "
                        + as[0].getAnnotationType(), as[0].getAnnotationType()
                        .equals("LSimpleAnnotation;"));
        assertTrue(
                "Name of annotation 2 should be LSimpleAnnotation; but it is "
                        + as[1].getAnnotationType(), as[1].getAnnotationType()
                        .equals("LSimpleAnnotation;"));
        ElementValuePair[] vals = as[0].getElementValuePairs();
        ElementValuePair nvp = vals[0];
        assertTrue(
                "Name of element in SimpleAnnotation should be 'id' but it is "
                        + nvp.getNameString(), nvp.getNameString().equals("id"));
        ElementValue ev = nvp.getValue();
        assertTrue("Type of element value should be int but it is "
                + ev.getElementValueType(),
                ev.getElementValueType() == ElementValue.PRIMITIVE_INT);
        assertTrue("Value of element should be 4 but it is "
                + ev.stringifyValue(), ev.stringifyValue().equals("4"));
        assertTrue(createTestdataFile("HelloWorld.class").delete());
    }

    /**
     * Just check that we can dump a class that has a method annotation on it
     * and it is still there when we read it back in
     */
    public void testGenerateMethodLevelAnnotations1()
            throws ClassNotFoundException
    {
        // Create HelloWorld
        ClassGen cg = createClassGen("HelloWorld");
        ConstantPoolGen cp = cg.getConstantPool();
        InstructionList il = new InstructionList();
        buildClassContentsWithAnnotatedMethods(cg, cp, il);
        // Check annotation is OK
        int i = cg.getMethods()[0].getAnnotationEntries().length;
        assertTrue(
                "Prior to dumping, main method should have 1 annotation but has "
                        + i, i == 1);
        dumpClass(cg, "temp1" + File.separator + "HelloWorld.class");
        JavaClass jc2 = getClassFrom("temp1", "HelloWorld");
        // Check annotation is OK
        i = jc2.getMethods()[0].getAnnotationEntries().length;
        assertTrue("JavaClass should say 1 annotation on main method but says "
                + i, i == 1);
        ClassGen cg2 = new ClassGen(jc2);
        // Check it now it is a ClassGen
        Method[] m = cg2.getMethods();
        i = m[0].getAnnotationEntries().length;
        assertTrue("The main 'Method' should have one annotation but has " + i,
                i == 1);
        MethodGen mg = new MethodGen(m[0], cg2.getClassName(), cg2
                .getConstantPool());
        // Check it finally when the Method is changed to a MethodGen
        i = mg.getAnnotationEntries().length;
        assertTrue("The main 'MethodGen' should have one annotation but has "
                + i, i == 1);
        assertTrue(wipe("temp1" + File.separator + "HelloWorld.class"));
    }

    /**
     * Going further than the last test - when we reload the method back in,
     * let's change it (adding a new annotation) and then store that, read it
     * back in and verify both annotations are there !
     */
    public void testGenerateMethodLevelAnnotations2()
            throws ClassNotFoundException
    {
        // Create HelloWorld
        ClassGen cg = createClassGen("HelloWorld");
        ConstantPoolGen cp = cg.getConstantPool();
        InstructionList il = new InstructionList();
        buildClassContentsWithAnnotatedMethods(cg, cp, il);
        dumpClass(cg, "temp2", "HelloWorld.class");
        JavaClass jc2 = getClassFrom("temp2", "HelloWorld");
        ClassGen cg2 = new ClassGen(jc2);
        // Main method after reading the class back in
        Method mainMethod1 = jc2.getMethods()[0];
        assertTrue("The 'Method' should have one annotations but has "
                + mainMethod1.getAnnotationEntries().length, mainMethod1
                .getAnnotationEntries().length == 1);
        MethodGen mainMethod2 = new MethodGen(mainMethod1, cg2.getClassName(),
                cg2.getConstantPool());
        assertTrue("The 'MethodGen' should have one annotations but has "
                + mainMethod2.getAnnotationEntries().length, mainMethod2
                .getAnnotationEntries().length == 1);
        mainMethod2.addAnnotationEntry(createFruitAnnotation(cg2
                .getConstantPool(), "Pear"));
        cg2.removeMethod(mainMethod1);
        cg2.addMethod(mainMethod2.getMethod());
        dumpClass(cg2, "temp3", "HelloWorld.class");
        JavaClass jc3 = getClassFrom("temp3", "HelloWorld");
        ClassGen cg3 = new ClassGen(jc3);
        Method mainMethod3 = cg3.getMethods()[1];
        int i = mainMethod3.getAnnotationEntries().length;
        assertTrue("The 'Method' should now have two annotations but has " + i,
                i == 2);
        assertTrue(wipe("temp2", "HelloWorld.class"));
        assertTrue(wipe("temp3", "HelloWorld.class"));
    }

    // J5TODO: Need to add deleteFile calls to many of these tests
    /**
     * Transform simple class from an immutable to a mutable object.
     */
    public void testTransformClassToClassGen_SimpleTypes()
            throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.SimpleAnnotatedClass");
        ClassGen cgen = new ClassGen(jc);
        // Check annotations are correctly preserved
        AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
        assertTrue("Expected one annotation but found " + annotations.length,
                annotations.length == 1);
    }

    /**
     * Transform simple class from an immutable to a mutable object. The class
     * is annotated with an annotation that uses an enum.
     */
    public void testTransformClassToClassGen_EnumType()
            throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.AnnotatedWithEnumClass");
        ClassGen cgen = new ClassGen(jc);
        // Check annotations are correctly preserved
        AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
        assertTrue("Expected one annotation but found " + annotations.length,
                annotations.length == 1);
    }

    /**
     * Transform simple class from an immutable to a mutable object. The class
     * is annotated with an annotation that uses an array of SimpleAnnotations.
     */
    public void testTransformClassToClassGen_ArrayAndAnnotationTypes()
            throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.AnnotatedWithCombinedAnnotation");
        ClassGen cgen = new ClassGen(jc);
        // Check annotations are correctly preserved
        AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
        assertTrue("Expected one annotation but found " + annotations.length,
                annotations.length == 1);
        AnnotationEntryGen a = annotations[0];
        assertTrue("That annotation should only have one value but has "
                + a.getValues().size(), a.getValues().size() == 1);
        ElementValuePairGen nvp = a.getValues().get(0);
        ElementValueGen value = nvp.getValue();
        assertTrue("Value should be ArrayElementValueGen but is " + value,
                value instanceof ArrayElementValueGen);
        ArrayElementValueGen arrayValue = (ArrayElementValueGen) value;
        assertTrue("Array value should be size one but is "
                + arrayValue.getElementValuesSize(), arrayValue
                .getElementValuesSize() == 1);
        ElementValueGen innerValue = arrayValue.getElementValues().get(0);
        assertTrue(
                "Value in the array should be AnnotationElementValueGen but is "
                        + innerValue,
                innerValue instanceof AnnotationElementValueGen);
        AnnotationElementValueGen innerAnnotationValue = (AnnotationElementValueGen) innerValue;
        assertTrue("Should be called Lorg/apache/bcel/data/SimpleAnnotation; but is called: "
                + innerAnnotationValue.getAnnotation().getTypeName(),
                innerAnnotationValue.getAnnotation().getTypeSignature().equals(
                        "Lorg/apache/bcel/data/SimpleAnnotation;"));

        // check the three methods
        Method[] methods = cgen.getMethods();
        assertEquals(3, methods.length);
        for(Method method : methods)
        {
            String methodName= method.getName();
            if(methodName.equals("<init>"))
            {
                assertMethodAnnotations(method, 0, 1);
                assertParameterAnnotations(method, 0, 1);
            }
            else if(methodName.equals("methodWithArrayOfZeroAnnotations"))
            {
                assertMethodAnnotations(method, 1, 0);
            }
            else if(methodName.equals("methodWithArrayOfTwoAnnotations"))
            {
                assertMethodAnnotations(method, 1, 2);
            }
            else
            {
                fail("unexpected method "+method.getName());
            }
        }
    }

    private void assertMethodAnnotations(Method method, int expectedNumberAnnotations, int nExpectedArrayValues)
    {
        String methodName= method.getName();
        AnnotationEntry[] annos= method.getAnnotationEntries();
        assertEquals("For "+methodName, expectedNumberAnnotations, annos.length);
        if(expectedNumberAnnotations!=0)
        {
            assertArrayElementValue(nExpectedArrayValues, annos[0]);
        }
    }

    private void assertArrayElementValue(int nExpectedArrayValues, AnnotationEntry anno)
    {
        ElementValuePair elementValuePair = anno.getElementValuePairs()[0];
        assertEquals("value", elementValuePair.getNameString());
        ArrayElementValue ev = (ArrayElementValue) elementValuePair.getValue();
        ElementValue[] eva = ev.getElementValuesArray();
        assertEquals(nExpectedArrayValues, eva.length);
    }

    private void assertParameterAnnotations(Method method, int... expectedNumberOfParmeterAnnotations)
    {
        String methodName= "For "+method.getName();
        ParameterAnnotationEntry[] parameterAnnotations= method.getParameterAnnotationEntries();
        assertEquals(methodName, expectedNumberOfParmeterAnnotations.length, parameterAnnotations.length);

        int i= 0;
        for(ParameterAnnotationEntry parameterAnnotation : parameterAnnotations)
        {
            AnnotationEntry[] annos= parameterAnnotation.getAnnotationEntries();
            int expectedLength = expectedNumberOfParmeterAnnotations[i++];
            assertEquals(methodName+" parameter "+i, expectedLength, annos.length);
            if(expectedLength!=0)
            {
                assertSimpleElementValue(annos[0]);
            }
        }
    }

    private void assertSimpleElementValue(AnnotationEntry anno)
    {
        ElementValuePair elementValuePair = anno.getElementValuePairs()[0];
        assertEquals("id", elementValuePair.getNameString());
        SimpleElementValue ev = (SimpleElementValue)elementValuePair.getValue();
        assertEquals(42, ev.getValueInt());
    }

    /**
     * Transform complex class from an immutable to a mutable object.
     */
    public void testTransformComplexClassToClassGen()
            throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.ComplexAnnotatedClass");
        ClassGen cgen = new ClassGen(jc);
        // Check annotations are correctly preserved
        AnnotationEntryGen[] annotations = cgen.getAnnotationEntries();
        assertTrue("Expected one annotation but found " + annotations.length,
                annotations.length == 1);
        List<?> l = annotations[0].getValues();
        boolean found = false;
        for (Object name : l) {
            ElementValuePairGen element = (ElementValuePairGen) name;
            if (element.getNameString().equals("dval"))
            {
                if (((SimpleElementValueGen) element.getValue())
                        .stringifyValue().equals("33.4")) {
                    found = true;
                }
            }
        }
        assertTrue("Did not find double annotation value with value 33.4",
                found);
    }

    /**
     * Load a class in and modify it with a new attribute - A SimpleAnnotation
     * annotation
     */
    public void testModifyingClasses1() throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.SimpleAnnotatedClass");
        ClassGen cgen = new ClassGen(jc);
        ConstantPoolGen cp = cgen.getConstantPool();
        cgen.addAnnotationEntry(createFruitAnnotation(cp, "Pineapple"));
        assertTrue("Should now have two annotations but has "
                + cgen.getAnnotationEntries().length, cgen
                .getAnnotationEntries().length == 2);
        dumpClass(cgen, "SimpleAnnotatedClass.class");
        assertTrue(wipe("SimpleAnnotatedClass.class"));
    }

    /**
     * Load a class in and modify it with a new attribute - A ComplexAnnotation
     * annotation
     */
    public void testModifyingClasses2() throws ClassNotFoundException
    {
        JavaClass jc = getTestClass("org.apache.bcel.data.SimpleAnnotatedClass");
        ClassGen cgen = new ClassGen(jc);
        ConstantPoolGen cp = cgen.getConstantPool();
        cgen.addAnnotationEntry(createCombinedAnnotation(cp));
        assertTrue("Should now have two annotations but has "
                + cgen.getAnnotationEntries().length, cgen
                .getAnnotationEntries().length == 2);
        dumpClass(cgen, "SimpleAnnotatedClass.class");
        JavaClass jc2 = getClassFrom(".", "SimpleAnnotatedClass");
        jc2.getAnnotationEntries();
        assertTrue(wipe("SimpleAnnotatedClass.class"));
        // System.err.println(jc2.toString());
    }

    private void dumpClass(ClassGen cg, String fname)
    {
        try
        {
            File f = createTestdataFile(fname);
            cg.getJavaClass().dump(f);
        }
        catch (java.io.IOException e)
        {
            System.err.println(e);
        }
    }

    private void dumpClass(ClassGen cg, String dir, String fname)
    {
        dumpClass(cg, dir + File.separator + fname);
    }

    private void buildClassContentsWithAnnotatedMethods(ClassGen cg,
            ConstantPoolGen cp, InstructionList il)
    {
        // Create method 'public static void main(String[]argv)'
        MethodGen mg = createMethodGen("main", il, cp);
        InstructionFactory factory = new InstructionFactory(cg);
        mg.addAnnotationEntry(createSimpleVisibleAnnotation(mg
                .getConstantPool()));
        // We now define some often used types:
        ObjectType i_stream = new ObjectType("java.io.InputStream");
        ObjectType p_stream = new ObjectType("java.io.PrintStream");
        // Create variables in and name : We call the constructors, i.e.,
        // execute BufferedReader(InputStreamReader(System.in)) . The reference
        // to the BufferedReader object stays on top of the stack and is stored
        // in the newly allocated in variable.
        il.append(factory.createNew("java.io.BufferedReader"));
        il.append(InstructionConstants.DUP); // Use predefined constant
        il.append(factory.createNew("java.io.InputStreamReader"));
        il.append(InstructionConstants.DUP);
        il.append(factory.createFieldAccess("java.lang.System", "in", i_stream,
                Constants.GETSTATIC));
        il.append(factory.createInvoke("java.io.InputStreamReader", "<init>",
                Type.VOID, new Type[] { i_stream }, Constants.INVOKESPECIAL));
        il.append(factory.createInvoke("java.io.BufferedReader", "<init>",
                Type.VOID, new Type[] { new ObjectType("java.io.Reader") },
                Constants.INVOKESPECIAL));
        LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType(
                "java.io.BufferedReader"), null, null);
        int in = lg.getIndex();
        lg.setStart(il.append(new ASTORE(in))); // "in" valid from here
        // Create local variable name and initialize it to null
        lg = mg.addLocalVariable("name", Type.STRING, null, null);
        int name = lg.getIndex();
        il.append(InstructionConstants.ACONST_NULL);
        lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
        // Create try-catch block: We remember the start of the block, read a
        // line from the standard input and store it into the variable name .
        // InstructionHandle try_start = il.append(factory.createFieldAccess(
        // "java.lang.System", "out", p_stream, Constants.GETSTATIC));
        // il.append(new PUSH(cp, "Please enter your name> "));
        // il.append(factory.createInvoke("java.io.PrintStream", "print",
        // Type.VOID, new Type[] { Type.STRING },
        // Constants.INVOKEVIRTUAL));
        // il.append(new ALOAD(in));
        // il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
        // Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
        InstructionHandle try_start = il.append(new PUSH(cp, "Andy"));
        il.append(new ASTORE(name));
        // Upon normal execution we jump behind exception handler, the target
        // address is not known yet.
        GOTO g = new GOTO(null);
        InstructionHandle try_end = il.append(g);
        // We add the exception handler which simply returns from the method.
        LocalVariableGen var_ex = mg.addLocalVariable("ex", Type
                .getType("Ljava.io.IOException;"), null, null);
        int var_ex_slot = var_ex.getIndex();
        InstructionHandle handler = il.append(new ASTORE(var_ex_slot));
        var_ex.setStart(handler);
        var_ex.setEnd(il.append(InstructionConstants.RETURN));
        mg.addExceptionHandler(try_start, try_end, handler, new ObjectType(
                "java.io.IOException"));
        // "Normal" code continues, now we can set the branch target of the GOTO
        // .
        InstructionHandle ih = il.append(factory.createFieldAccess(
                "java.lang.System", "out", p_stream, Constants.GETSTATIC));
        g.setTarget(ih);
        // Printing "Hello": String concatenation compiles to StringBuffer
        // operations.
        il.append(factory.createNew(Type.STRINGBUFFER));
        il.append(InstructionConstants.DUP);
        il.append(new PUSH(cp, "Hello, "));
        il
                .append(factory.createInvoke("java.lang.StringBuffer",
                        "<init>", Type.VOID, new Type[] { Type.STRING },
                        Constants.INVOKESPECIAL));
        il.append(new ALOAD(name));
        il.append(factory.createInvoke("java.lang.StringBuffer", "append",
                Type.STRINGBUFFER, new Type[] { Type.STRING },
                Constants.INVOKEVIRTUAL));
        il.append(factory.createInvoke("java.lang.StringBuffer", "toString",
                Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
        il
                .append(factory.createInvoke("java.io.PrintStream", "println",
                        Type.VOID, new Type[] { Type.STRING },
                        Constants.INVOKEVIRTUAL));
        il.append(InstructionConstants.RETURN);
        // Finalization: Finally, we have to set the stack size, which normally
        // would have to be computed on the fly and add a default constructor
        // method to the class, which is empty in this case.
        mg.setMaxStack();
        mg.setMaxLocals();
        cg.addMethod(mg.getMethod());
        il.dispose(); // Allow instruction handles to be reused
        cg.addEmptyConstructor(Constants.ACC_PUBLIC);
    }

    private void buildClassContents(ClassGen cg, ConstantPoolGen cp,
            InstructionList il)
    {
        // Create method 'public static void main(String[]argv)'
        MethodGen mg = createMethodGen("main", il, cp);
        InstructionFactory factory = new InstructionFactory(cg);
        // We now define some often used types:
        ObjectType i_stream = new ObjectType("java.io.InputStream");
        ObjectType p_stream = new ObjectType("java.io.PrintStream");
        // Create variables in and name : We call the constructors, i.e.,
        // execute BufferedReader(InputStreamReader(System.in)) . The reference
        // to the BufferedReader object stays on top of the stack and is stored
        // in the newly allocated in variable.
        il.append(factory.createNew("java.io.BufferedReader"));
        il.append(InstructionConstants.DUP); // Use predefined constant
        il.append(factory.createNew("java.io.InputStreamReader"));
        il.append(InstructionConstants.DUP);
        il.append(factory.createFieldAccess("java.lang.System", "in", i_stream,
                Constants.GETSTATIC));
        il.append(factory.createInvoke("java.io.InputStreamReader", "<init>",
                Type.VOID, new Type[] { i_stream }, Constants.INVOKESPECIAL));
        il.append(factory.createInvoke("java.io.BufferedReader", "<init>",
                Type.VOID, new Type[] { new ObjectType("java.io.Reader") },
                Constants.INVOKESPECIAL));
        LocalVariableGen lg = mg.addLocalVariable("in", new ObjectType(
                "java.io.BufferedReader"), null, null);
        int in = lg.getIndex();
        lg.setStart(il.append(new ASTORE(in))); // "in" valid from here
        // Create local variable name and initialize it to null
        lg = mg.addLocalVariable("name", Type.STRING, null, null);
        int name = lg.getIndex();
        il.append(InstructionConstants.ACONST_NULL);
        lg.setStart(il.append(new ASTORE(name))); // "name" valid from here
        // Create try-catch block: We remember the start of the block, read a
        // line from the standard input and store it into the variable name .
        // InstructionHandle try_start = il.append(factory.createFieldAccess(
        // "java.lang.System", "out", p_stream, Constants.GETSTATIC));
        // il.append(new PUSH(cp, "Please enter your name> "));
        // il.append(factory.createInvoke("java.io.PrintStream", "print",
        // Type.VOID, new Type[] { Type.STRING },
        // Constants.INVOKEVIRTUAL));
        // il.append(new ALOAD(in));
        // il.append(factory.createInvoke("java.io.BufferedReader", "readLine",
        // Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
        InstructionHandle try_start = il.append(new PUSH(cp, "Andy"));
        il.append(new ASTORE(name));
        // Upon normal execution we jump behind exception handler, the target
        // address is not known yet.
        GOTO g = new GOTO(null);
        InstructionHandle try_end = il.append(g);
        // We add the exception handler which simply returns from the method.
        LocalVariableGen var_ex = mg.addLocalVariable("ex", Type
                .getType("Ljava.io.IOException;"), null, null);
        int var_ex_slot = var_ex.getIndex();
        InstructionHandle handler = il.append(new ASTORE(var_ex_slot));
        var_ex.setStart(handler);
        var_ex.setEnd(il.append(InstructionConstants.RETURN));
        mg.addExceptionHandler(try_start, try_end, handler, new ObjectType(
                "java.io.IOException"));
        // "Normal" code continues, now we can set the branch target of the GOTO
        // .
        InstructionHandle ih = il.append(factory.createFieldAccess(
                "java.lang.System", "out", p_stream, Constants.GETSTATIC));
        g.setTarget(ih);
        // Printing "Hello": String concatenation compiles to StringBuffer
        // operations.
        il.append(factory.createNew(Type.STRINGBUFFER));
        il.append(InstructionConstants.DUP);
        il.append(new PUSH(cp, "Hello, "));
        il
                .append(factory.createInvoke("java.lang.StringBuffer",
                        "<init>", Type.VOID, new Type[] { Type.STRING },
                        Constants.INVOKESPECIAL));
        il.append(new ALOAD(name));
        il.append(factory.createInvoke("java.lang.StringBuffer", "append",
                Type.STRINGBUFFER, new Type[] { Type.STRING },
                Constants.INVOKEVIRTUAL));
        il.append(factory.createInvoke("java.lang.StringBuffer", "toString",
                Type.STRING, Type.NO_ARGS, Constants.INVOKEVIRTUAL));
        il
                .append(factory.createInvoke("java.io.PrintStream", "println",
                        Type.VOID, new Type[] { Type.STRING },
                        Constants.INVOKEVIRTUAL));
        il.append(InstructionConstants.RETURN);
        // Finalization: Finally, we have to set the stack size, which normally
        // would have to be computed on the fly and add a default constructor
        // method to the class, which is empty in this case.
        mg.setMaxStack();
        mg.setMaxLocals();
        cg.addMethod(mg.getMethod());
        il.dispose(); // Allow instruction handles to be reused
        cg.addEmptyConstructor(Constants.ACC_PUBLIC);
    }

    private JavaClass getClassFrom(String where, String clazzname)
            throws ClassNotFoundException
    {
        // System.out.println(where);
        SyntheticRepository repos = createRepos(where);
        return repos.loadClass(clazzname);
    }

    // helper methods
    private ClassGen createClassGen(String classname)
    {
        return new ClassGen(classname, "java.lang.Object", "<generated>",
                Constants.ACC_PUBLIC | Constants.ACC_SUPER, null);
    }

    private MethodGen createMethodGen(String methodname, InstructionList il,
            ConstantPoolGen cp)
    {
        return new MethodGen(Constants.ACC_STATIC | Constants.ACC_PUBLIC, // access
                // flags
                Type.VOID, // return type
                new Type[] { new ArrayType(Type.STRING, 1) }, // argument
                // types
                new String[] { "argv" }, // arg names
                methodname, "HelloWorld", // method, class
                il, cp);
    }

    public AnnotationEntryGen createSimpleVisibleAnnotation(ConstantPoolGen cp)
    {
        SimpleElementValueGen evg = new SimpleElementValueGen(
                ElementValueGen.PRIMITIVE_INT, cp, 4);
        ElementValuePairGen nvGen = new ElementValuePairGen("id", evg, cp);
        ObjectType t = new ObjectType("SimpleAnnotation");
        List<ElementValuePairGen> elements = new ArrayList<ElementValuePairGen>();
        elements.add(nvGen);
        AnnotationEntryGen a = new AnnotationEntryGen(t, elements, true, cp);
        return a;
    }

    public AnnotationEntryGen createFruitAnnotation(ConstantPoolGen cp,
            String aFruit)
    {
        SimpleElementValueGen evg = new SimpleElementValueGen(
                ElementValueGen.STRING, cp, aFruit);
        ElementValuePairGen nvGen = new ElementValuePairGen("fruit", evg, cp);
        ObjectType t = new ObjectType("SimpleStringAnnotation");
        List<ElementValuePairGen> elements = new ArrayList<ElementValuePairGen>();
        elements.add(nvGen);
        return new AnnotationEntryGen(t, elements, true, cp);
    }

    public AnnotationEntryGen createCombinedAnnotation(ConstantPoolGen cp)
    {
        // Create an annotation instance
        AnnotationEntryGen a = createSimpleVisibleAnnotation(cp);
        ArrayElementValueGen array = new ArrayElementValueGen(cp);
        array.addElement(new AnnotationElementValueGen(a, cp));
        ElementValuePairGen nvp = new ElementValuePairGen("value", array, cp);
        List<ElementValuePairGen> elements = new ArrayList<ElementValuePairGen>();
        elements.add(nvp);
        return new AnnotationEntryGen(new ObjectType("CombinedAnnotation"),
                elements, true, cp);
    }

    public AnnotationEntryGen createSimpleInvisibleAnnotation(ConstantPoolGen cp)
    {
        SimpleElementValueGen evg = new SimpleElementValueGen(
                ElementValueGen.PRIMITIVE_INT, cp, 4);
        ElementValuePairGen nvGen = new ElementValuePairGen("id", evg, cp);
        ObjectType t = new ObjectType("SimpleAnnotation");
        List<ElementValuePairGen> elements = new ArrayList<ElementValuePairGen>();
        elements.add(nvGen);
        AnnotationEntryGen a = new AnnotationEntryGen(t, elements, false, cp);
        return a;
    }
}