/**
 **/
package test.common;

import ovm.core.execution.NativeInterface;
/**
 * Test exception handling
 * @author Christian Grothoff
 * @author Hiroshi Yamauchi
 **/
public class TestExceptions
    extends TestBase {

    boolean doOverflow;
    
    public TestExceptions(boolean doOverflow) {
	super("Exceptions");
	this.doOverflow = doOverflow;
    }

    public TestExceptions() {
	this(true);
    }

    public TestExceptions(long disable) {
	this((disable & TestSuite.DISABLE_STACK_OVERFLOW) == 0);
    }

    public void run() {
	testSimpleTryCatch();
	testSubtypedCatch();
	testMultiCatch();
	testCallerCatch();
	testCallerCatchWithNoUnreachable();
	testNestedTryCatch();
	testNullDereference();
	testArithmeticException();
	testArrayStore();
	testTrace();
	if (doOverflow)
	    testOverflow();
	testArrayIndex();
        testNPE();
    }

    private int id(int x) { return x; }

    public void testArithmeticException() {
	int divisor = id(0);
	int foo = 3;
	try {
	    foo /= divisor;
	    id(foo);
	    COREfail("arithmetic exception not thrown");
	} catch (ArithmeticException e) {
	}
    }

    private void doStore(Object [] arr, Object o, boolean expected)
    {
	try {
	    arr[0] = o;
	    check_condition(!expected, "missing array store exception");
	} catch (ArrayStoreException _) {
	    check_condition(expected, "unexpected array store exception");
	}
    }

    public void testArrayStore() {
	doStore(new String[1], new Object(), true);
	doStore(new Object[1], new String("a string"), false);
	doStore(new Object[1], new Object(), false);
    }

    static private Object returnNull() { return null; }
    static private void useObject(Object o) { o.toString(); }
    
    public void testNullDereference() {
	setModule("null dereference");
	try {
	    Object o = returnNull();
	    o.toString();	// j2c optimizes this to generateThrowable()
	    COREfail("null pointer exception not thrown");
	} catch (NullPointerException npe) {
	    //npe.printStackTrace();
	}
	try {
	    Object o = returnNull();
	    o.toString();	// j2c optimizes this to generateThrowable()
	    COREfail("null pointer exception not thrown");
	} catch (Throwable npe) {
	    //npe.printStackTrace();
	}
	try {
	    Object o = returnNull();
	    useObject(o);
	    COREfail("null pointer exception not thrown");
	} catch (NullPointerException npe) {
	    //npe.printStackTrace();
	}
    }

    public void testSimpleTryCatch() {
	setModule("simpleTryCatch");
	AException ae = new AException();
	try {	    
	    if (true)  /* make compiler happy! */
		throw ae;
	    COREfail("this code should be unreachable!");
	} catch (AException aeII) {
	    check_condition(aeII == ae,
		       "exception caught is not exception thrown!");
	}
    }

    public void testSubtypedCatch() {
	setModule("subtypedTryCatch");
	AException ae = new AException();
	try {	    
	    if (true)  /* make compiler happy! */
		throw ae;
	    COREfail("this code should be unreachable!");
	} catch (Exception aeII) {
	    check_condition(aeII == ae,
		       "exception caught is not exception thrown!");
	}
    }

    public void testMultiCatch() {
	setModule("multiCatch");
	BException be = new BException();
	try {	    
	    if (true)  /* make compiler happy! */
		throw be;
	    else
		if (false) 
		    throw new CException();
		else
		    COREfail("this code should be unreachable!");
	} catch (CException ce) {
	    COREfail("wrong catch clause!");
	} catch (BException beII) {
	    check_condition(beII == be,
		       "exception caught is not exception thrown!");
	}
    }

    public void testCallerCatch() {
	setModule("callerCatch");
	try {	    
	    m();
	    COREfail("this code should be unreachable!");
	} catch (CException ce) {
	    COREfail("wrong catch clause!");
	} catch (AException beII) {
	} catch (Exception e) {
	    COREfail("wrong catch clause!");
	}
    }

    public void testCallerCatchWithNoUnreachable() {
	setModule("callerCatchWithNoUnreachable");
	try {	    
	    m();
	    //  method call is last to test if PC updates are handled correctly
	} catch (CException ce) {
	    COREfail("wrong catch clause!");
	} catch (AException beII) {
	} catch (Exception e) {
	    COREfail("wrong catch clause!");
	}
    }



    public void testNestedTryCatch() {
	setModule("nestedTryCatch");
	try {	    
	    try {
		try {
		    m();
		    COREfail("this code should be unreachable!");
		} catch (CException ce) {
		    COREfail("wrong catch clause!");
		}
	    } catch (CException ce) {
		COREfail("wrong catch clause!");
	    }
	} catch (AException beII) {
	} catch (Exception e) {
	    COREfail("wrong catch clause!");
	}
    }

   
    public void testTrace() {
	try {
	    n();
	} catch (Exception e) {
	    StackTraceElement[] ste = e.getStackTrace();
	    for (int i = 0; i < ste.length; i++) {
		ste[i].toString(); // just test if it crashes ...
	    }
	}
    }

    private void overflow() {
	overflow();
    }

    public void testOverflow() {
	setModule("stack overflow");
	try {
	    overflow();
	} catch (StackOverflowError sof) {
	}
    }


    // methods to return null in such a way that j2c can't tell it
    // will always be a null and so optimise the code straight to a
    // generateThrowable call.
    Object[] getArray(boolean returnNull) {
        if (returnNull) return null;
        return new Object[0];
    }

    Object getObject(boolean returnNull) {
        if (returnNull) return null;
        return new Object();
    }

    public void testNPE() {
	setModule("null reference checks");
	Object o = getObject(true);
	Object[] oa = getArray(true);
	try {
	    synchronized(o) {
                COREfail("NPE check missing");
	    }
	    COREfail("NPE check missing");
	} catch (NullPointerException npe) {
	}
	try {
	    synchronized(oa) {
                COREfail("NPE check missing");
	    }
	    COREfail("NPE check missing");
	} catch (NullPointerException npe) {
	}
	try {
	    o.toString();
	    COREfail("NPE check missing");
	} catch (NullPointerException npe) {
	}
	try {
	    if (oa[1] == o)
                COREfail("NPE check missing");

	    COREfail("NPE check missing");
	} catch (NullPointerException npe) {
	}
    }

    public void testArrayIndex() {
        setModule("array range checks");
	int[] ia = new int[5];
	ia[0] = 1;
	try {
	    ia[5] = 1;
	    COREfail("ArrayIndex check bad - index too big");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
	try {
	    ia[-1] = 1;
	    COREfail("ArrayIndex check bad - negative index");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
	try {
	    ia[6] = 1;
	    COREfail("ArrayIndex check bad - index too big ");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
	Object[] oa = new Object[5];
	try {
	    oa[5] = null;
	    COREfail("ArrayIndex check bad - index too big");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
	try {
	    oa[-1] = null;
	    COREfail("ArrayIndex check bad - negative index");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
	try {
	    oa[6] = null;
	    COREfail("ArrayIndex check bad - index too big");
	} catch (ArrayIndexOutOfBoundsException a) {
	}
    }


    /* ************** helper methods ********************** */

    private void n() throws Exception {
	m();
    }


    private void m() throws Exception {
	if (true)
	    throw new BException();
	COREfail("this code should be unreachable!");
    }
    
    /* ************** helper classes ********************** */

    static class AException extends Exception {
	AException() {}
    }

    static class BException extends AException {
	BException() {}
    }

    static class CException extends Exception {
 	CException() {}
   }


} // end of TestException
