/*
 * Copyright (C) 2010 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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/*
 * Entry point and tests that are expected to succeed.
 */
public class Main {
    /**
     * Drives tests.
     */
    public static void main(String[] args) {
        System.loadLibrary(args[0]);
        if (!hasOatFile() || runtimeIsSoftFail() || isInterpreted()) {
            // Some tests ensure that the verifier was able to guarantee balanced locking by
            // asserting that the test function is running as compiled code. But skip this now,
            // as this seems to be a non-compiled code test configuration.
            disableStackFrameAsserts();
        }

        Main m = new Main();

        m.recursiveSync(0);

        m.nestedMayThrow(false);
        try {
            m.nestedMayThrow(true);
            System.err.println("nestedThrow(true) did not throw");
        } catch (MyException me) {}
        System.out.println("nestedMayThrow ok");

        m.constantLock();
        System.out.println("constantLock ok");

        m.notExcessiveNesting();

        m.notNested();
        System.out.println("notNested ok");

        Object obj1 = new Object();
        Object obj2 = new Object();

        TwoPath.twoPath(obj1, obj2, 0);
        System.out.println("twoPath ok");

        m.triplet(obj1, obj2, 0);
        System.out.println("triplet ok");

        runSmaliTests();
    }

    /**
     * Recursive synchronized method.
     */
    synchronized void recursiveSync(int iter) {
        assertIsManaged();
        if (iter < 40) {
            recursiveSync(iter+1);
        } else {
            System.out.println("recursiveSync ok");
        }
    }

    /**
     * Tests simple nesting, with and without a throw.
     */
    void nestedMayThrow(boolean doThrow) {
        assertIsManaged();
        synchronized (this) {
            synchronized (Main.class) {
                synchronized (new Object()) {
                    synchronized(Class.class) {
                        if (doThrow) {
                            throw new MyException();
                        }
                    }
                }
            }
        }
    }

    /**
     * Exercises bug 3215458.
     */
    void constantLock() {
        assertIsManaged();
        Class thing = Thread.class;
        synchronized (Thread.class) {}
    }

    /**
     * Confirms that we can have 32 nested monitors on one method.
     */
    void notExcessiveNesting() {
        assertIsManaged();
        synchronized (this) {   // 1
        synchronized (this) {   // 2
        synchronized (this) {   // 3
        synchronized (this) {   // 4
        synchronized (this) {   // 5
        synchronized (this) {   // 6
        synchronized (this) {   // 7
        synchronized (this) {   // 8
        synchronized (this) {   // 9
        synchronized (this) {   // 10
        synchronized (this) {   // 11
        synchronized (this) {   // 12
        synchronized (this) {   // 13
        synchronized (this) {   // 14
        synchronized (this) {   // 15
        synchronized (this) {   // 16
        synchronized (this) {   // 17
        synchronized (this) {   // 18
        synchronized (this) {   // 19
        synchronized (this) {   // 20
        synchronized (this) {   // 21
        synchronized (this) {   // 22
        synchronized (this) {   // 23
        synchronized (this) {   // 24
        synchronized (this) {   // 25
        synchronized (this) {   // 26
        synchronized (this) {   // 27
        synchronized (this) {   // 28
        synchronized (this) {   // 29
        synchronized (this) {   // 30
        synchronized (this) {   // 31
        synchronized (this) {   // 32
        }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
    }

    /**
     * Confirms that we can have more than 32 non-nested monitors in one
     * method.
     */
    void notNested() {
        assertIsManaged();
        synchronized (this) {}  // 1
        synchronized (this) {}  // 2
        synchronized (this) {}  // 3
        synchronized (this) {}  // 4
        synchronized (this) {}  // 5
        synchronized (this) {}  // 6
        synchronized (this) {}  // 7
        synchronized (this) {}  // 8
        synchronized (this) {}  // 9
        synchronized (this) {}  // 10
        synchronized (this) {}  // 11
        synchronized (this) {}  // 12
        synchronized (this) {}  // 13
        synchronized (this) {}  // 14
        synchronized (this) {}  // 15
        synchronized (this) {}  // 16
        synchronized (this) {}  // 17
        synchronized (this) {}  // 18
        synchronized (this) {}  // 19
        synchronized (this) {}  // 20
        synchronized (this) {}  // 21
        synchronized (this) {}  // 22
        synchronized (this) {}  // 23
        synchronized (this) {}  // 24
        synchronized (this) {}  // 25
        synchronized (this) {}  // 26
        synchronized (this) {}  // 27
        synchronized (this) {}  // 28
        synchronized (this) {}  // 29
        synchronized (this) {}  // 30
        synchronized (this) {}  // 31
        synchronized (this) {}  // 32
        synchronized (this) {}  // 33
        synchronized (this) {}  // 34
    }

    /* does nothing but ensure that the compiler doesn't discard an object */
    private void doNothing(Object obj) {}

    /**
     * Lock the monitor two or three times, and make use of the locked or
     * unlocked object.
     */
    public void triplet(Object obj1, Object obj2, int x) {
        Object localObj;

        synchronized (obj1) {
            synchronized(obj1) {
                if (x == 0) {
                    synchronized(obj1) {
                        localObj = obj2;
                    }
                } else {
                    localObj = obj1;
                }
            }
        }

        doNothing(localObj);
    }

    // Smali testing code.
    private static void runSmaliTests() {
        runTest("OK", new Object[] { new Object(), new Object() }, null);
        runTest("TooDeep", new Object[] { new Object() }, null);
        runTest("NotStructuredOverUnlock", new Object[] { new Object() },
                IllegalMonitorStateException.class);
        runTest("NotStructuredUnderUnlock", new Object[] { new Object() },
                IllegalMonitorStateException.class);
        runTest("UnbalancedJoin", new Object[] { new Object(), new Object() }, null);
        runTest("UnbalancedStraight", new Object[] { new Object(), new Object() }, null);
        runTest("NullLocks", new Object[] { false }, null);
        runTest("NullLocks", new Object[] { true }, NullPointerException.class);
    }

    private static void runTest(String className, Object[] parameters, Class<?> excType) {
        try {
            Class<?> c = Class.forName(className);

            Method[] methods = c.getDeclaredMethods();

            // For simplicity we assume that test methods are not overloaded. So searching by name
            // will give us the method we need to run.
            Method method = null;
            for (Method m : methods) {
                if (m.getName().equals("run")) {
                    method = m;
                    break;
                }
            }

            if (method == null) {
                System.out.println("Could not find test method for " + className);
            } else if (!Modifier.isStatic(method.getModifiers())) {
                System.out.println("Test method for " + className + " is not static.");
            } else {
                method.invoke(null, parameters);
                if (excType != null) {
                    System.out.println("Expected an exception in " + className);
                }
            }
        } catch (Throwable exc) {
            if (excType == null) {
                System.out.println("Did not expect exception " + exc + " for " + className);
                exc.printStackTrace(System.out);
            } else if (exc instanceof InvocationTargetException && exc.getCause() != null &&
                       exc.getCause().getClass().equals(excType)) {
                // Expected exception is wrapped in InvocationTargetException.
            } else if (!excType.equals(exc.getClass())) {
                System.out.println("Expected " + excType.getName() + ", but got " + exc.getClass());
            } else {
              // Expected exception, do nothing.
            }
        }
    }

    // Helpers for the smali code.
    public static native void assertIsInterpreted();
    public static native void assertIsManaged();
    public static native boolean hasOatFile();
    public static native boolean runtimeIsSoftFail();
    public static native boolean isInterpreted();
    public static native void disableStackFrameAsserts();
}