/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed 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.
 */

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

/*
 * Create a Proxy class that blah.
 */
public class WrappedThrow {
    public static void main(String[] args) {
        WTMix mix = new WTMix();
        InvocationHandler handler = new WTInvocationHandler(mix);
        Object proxy;

        try {
            proxy = Proxy.newProxyInstance(WrappedThrow.class.getClassLoader(),
                new Class[] { InterfaceW1.class, InterfaceW2.class },
                handler);
        } catch (IllegalArgumentException iae) {
            System.out.println("WT init failed");
            return;
        }

        InterfaceW1 if1 = (InterfaceW1) proxy;
        InterfaceW2 if2 = (InterfaceW2) proxy;
        try {
            if1.throwFunky();
            System.err.println("No exception thrown");
        } catch (UndeclaredThrowableException ute) {
            System.out.println("Got expected UTE");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        try {
            if1.throwFunky2();
            System.err.println("No exception thrown");
        } catch (IOException ioe) {
            System.out.println("Got expected IOE");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        try {
            if2.throwFunky2();
            System.err.println("No exception thrown");
        } catch (IOException ioe) {
            System.out.println("Got expected IOE");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        /*
         * Throw exceptions, walking down the hierarchy.
         */
        try {
            if1.throwException();
            System.err.println("No exception thrown");
        } catch (UndeclaredThrowableException ute) {
            System.out.println("Got expected UTE");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        try {
            if1.throwBase();
            System.err.println("No exception thrown");
        } catch (UndeclaredThrowableException ute) {
            System.out.println("Got expected UTE");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        try {
            if2.throwSub();
            System.err.println("No exception thrown");
        } catch (SubException se) {
            System.out.println("Got expected exception");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        try {
            if2.throwSubSub();
            System.err.println("No exception thrown");
        } catch (SubException se) {
            System.out.println("Got expected exception");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }

        /*
         * Make sure that, if the class explicitly allows the base
         * class of an exception, that we still allow it.
         */
        try {
            if1.bothThrowBase();
            System.err.println("No exception thrown");
        } catch (BaseException se) {
            System.out.println("Got expected exception");
        } catch (Throwable t) {
            System.err.println("Got unexpected exception: " + t);
        }
    }
}

class BaseException extends Exception {}
class SubException extends BaseException {}
class SubSubException extends SubException {}

interface InterfaceW1 {
    public void throwFunky();

    public void throwFunky2() throws BaseException,
           NoSuchMethodException, IOException;

    public void throwException() throws BaseException;
    public void throwBase() throws BaseException;
    public void throwSub() throws BaseException;
    public void throwSubSub() throws BaseException;

    public void bothThrowBase() throws BaseException, SubException, SubSubException;
}

interface InterfaceW2 {
    public void throwFunky2() throws InterruptedException,
           NoSuchMethodException, IOException;

    public void throwException() throws SubException;
    public void throwBase() throws SubException;
    public void throwSub() throws SubException;
    public void throwSubSub() throws SubException;

    public void bothThrowBase() throws SubException, BaseException, SubSubException;
}

/**
 * Implement all of the proxied interfaces.
 */
class WTMix implements InterfaceW1, InterfaceW2 {
    public int dastardlyDeed() throws SubException {
        System.out.println("Throwing SubException");
        throw new SubException();
    }

    /* these don't actually get called; they just cause exceptions */
    public void throwFunky() {}
    public void throwFunky2() {}
    public void throwException() throws SubException {}
    public void throwBase() throws SubException {}
    public void throwSub() throws SubException {}
    public void throwSubSub() throws SubException {}

    public void bothThrowBase() throws BaseException, SubException {}
}

/**
 * Invocation handler for our proxy class.
 */
class WTInvocationHandler implements InvocationHandler {
    private Object mObj;

    public WTInvocationHandler(Object obj) {
        mObj = obj;
    }

    /*
     * This is called when anything gets invoked in the proxy object.
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {

        Object result = null;

        // Trap Object calls.  This is important here to avoid a recursive
        // invocation of toString() in the print statements below.
        if (method.getDeclaringClass() == java.lang.Object.class) {
            //System.out.println("!!! object " + method.getName());
            if (method.getName().equals("toString"))
                return super.toString();
            else if (method.getName().equals("hashCode"))
                return Integer.valueOf(super.hashCode());
            else if (method.getName().equals("equals"))
                return Boolean.valueOf(super.equals(args[0]));
            else
                throw new RuntimeException("huh?");
        }

        System.out.println("Invoke " + method);
        if (args == null || args.length == 0) {
            System.out.println(" (no args)");
        } else {
            for (int i = 0; i < args.length; i++)
                System.out.println(" " + i + ": " + args[i]);
        }

        try {
            if (method.getName().equals("throwFunky"))
                throw new InterruptedException("fake");
            if (method.getName().equals("throwFunky2"))
                throw new IOException("fake2");
            if (method.getName().equals("throwException"))
                throw new Exception();
            if (method.getName().equals("throwBase"))
                throw new BaseException();
            if (method.getName().equals("throwSub"))
                throw new SubException();
            if (method.getName().equals("throwSubSub"))
                throw new SubSubException();
            if (method.getName().equals("bothThrowBase"))
                throw new BaseException();

            if (true)
                result = method.invoke(mObj, args);
            else
                result = -1;
            System.out.println("Success: method " + method.getName()
                + " res=" + result);
        } catch (InvocationTargetException ite) {
            throw ite.getTargetException();
        } catch (IllegalAccessException iae) {
            throw new RuntimeException(iae);
        }
        return result;
    }
}