package test.org.apache.bcel.generic;

import org.apache.bcel.Constants;
import org.apache.bcel.generic.*;
import org.apache.bcel.classfile.*;

import java.security.SecureClassLoader;

import junit.framework.TestCase;

public class CodeExceptionGenTest
    extends TestCase
{
    public CodeExceptionGenTest( String name ) {
	super( name );
    }
    
    private class MockClassLoader 
	extends SecureClassLoader
    {
	private CodeExceptionGenTest test = null;

	public MockClassLoader( CodeExceptionGenTest test ) {
	    super( TInterface.class.getClassLoader() ); 
	    this.test = test;
	}

	protected Class findClass( String className ) 
	    throws ClassNotFoundException
	{
	    ClassGen clazz = null;
	    if (className.equals( "test.NoCatch" )) {
		clazz = test.makeNoCatch();
	    }

	    if (className.equals( "test.Catch" )) {
		clazz = test.makeCatch();
	    }

	    if (clazz == null) {
		return super.findClass( className );
	    }
	    
	    JavaClass jClass = clazz.getJavaClass();
	    byte bytecode[] = jClass.getBytes();
	    Class RC = defineClass( className,
				    bytecode, 0,
				    bytecode.length );
	    return RC;
	}
    }

    /**
     * Generate a Constructor for the class.  Its a no-arg
     * constructor that just calls super.
     */
    public MethodGen makeConstructor( ClassGen clazz ) {
	InstructionFactory factory = 
	    new InstructionFactory( clazz );

	InstructionList instructions = new InstructionList();

	instructions.append( new ALOAD(0) );
	instructions.append( factory.createInvoke( "java.lang.Object", "<init>",
						   Type.VOID, new Type[0], 
						   Constants.INVOKESPECIAL ) );
	instructions.append( new RETURN() );

	MethodGen returnMethod =
	    new MethodGen( Constants.ACC_PUBLIC, Type.VOID, 
			   new Type[0], new String[0],
			   "<init>", clazz.getClassName(),
			   instructions, clazz.getConstantPool() );

	returnMethod.setMaxStack();
	return returnMethod;
    }

    public ClassGen makeNoCatch() 
    {
	ClassGen clazz = new ClassGen("test.NoCatch",
				      "java.lang.Object",
				      "test/NoCatch.java",
				      Constants.ACC_PUBLIC,
				      new String[] 
	    { "test.org.apache.bcel.generic.TInterface" } );
	MethodGen constructor = 
	    makeConstructor( clazz );

	InstructionList instructions = new InstructionList();
	InstructionFactory factory = new InstructionFactory( clazz );

	instructions.append( factory.ACONST_NULL ); 
	instructions.append( factory.ICONST_0 );
	instructions.append( factory.createInvoke( "test.NoCatch",
						   "runTest",
						   Type.INT,
						   new Type[] { Type.INT },
						   Constants.INVOKEVIRTUAL));
	instructions.append( factory.POP );
	instructions.append( factory.ICONST_1 );
	instructions.append( factory.IRETURN );

	MethodGen runTest = 
	    new MethodGen( Constants.ACC_PUBLIC,
			   Type.INT,
			   new Type[] { Type.INT },
			   new String[] { "x" },
			   "runTest", "test.NoCatch",
			   instructions, clazz.getConstantPool() );

	constructor.setMaxStack();
	runTest.setMaxStack();

	clazz.addMethod( constructor.getMethod() );
	clazz.addMethod( runTest.getMethod() );

	return clazz;
    }

    public ClassGen makeCatch() 
    {
	ClassGen clazz = new ClassGen("test.Catch",
				      "java.lang.Object",
				      "test/Catch.java",
				      Constants.ACC_PUBLIC,
				      new String[] 
	    { "test.org.apache.bcel.generic.TInterface" } );
	MethodGen constructor = 
	    makeConstructor( clazz );

	InstructionList instructions = new InstructionList();
	InstructionFactory factory = new InstructionFactory( clazz );

	instructions.append( factory.ACONST_NULL ); 
	instructions.append( factory.ICONST_0 );
	instructions.append( factory.createInvoke( "test.Catch",
						   "runTest",
						   Type.INT,
						   new Type[] { Type.INT },
						   Constants.INVOKEVIRTUAL));
	instructions.append( factory.POP );
	instructions.append( factory.ICONST_1 );
	instructions.append( factory.IRETURN );

	InstructionHandle beginTry = instructions.getStart();
	InstructionHandle endTry = instructions.getEnd();
	InstructionHandle catchHandle =
	    instructions.append( factory.POP );
	instructions.append( factory.ICONST_1 );
	instructions.append( factory.IRETURN );

	MethodGen runTest = 
	    new MethodGen( Constants.ACC_PUBLIC,
			   Type.INT,
			   new Type[] { Type.INT },
			   new String[] { "x" },
			   "runTest", "test.Catch",
			   instructions, clazz.getConstantPool() );
	ObjectType npe = new ObjectType( "java.lang.NullPointerException" );
	runTest.addExceptionHandler( beginTry, endTry, catchHandle, npe );

	constructor.setMaxStack();
	runTest.setMaxStack();

	clazz.addMethod( constructor.getMethod() );
	clazz.addMethod( runTest.getMethod() );
	
	return clazz;
    }

    public void testCatch()
	throws Throwable
    {
	MockClassLoader loader = new MockClassLoader(this);

	Class clazz = loader.loadClass("test.Catch");
	TInterface object = 
	    (TInterface) clazz.newInstance();

	assertEquals( "Expecting Return of 1 from Catch block.",
		      1, object.runTest( 3 ) );
    }

    public void testNoCatch()
	throws Throwable
    {
	MockClassLoader loader = new MockClassLoader(this);

	Class clazz = loader.loadClass("test.NoCatch");
	TInterface object = 
	    (TInterface) clazz.newInstance();
	
	try {
	    object.runTest( 5 );
	    fail("Expecting NullPointerException to be thrown.");
	} catch (NullPointerException npe) {}
    }
}
