/*
 * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @bug     5086470 6358247
 * @summary SynchronizersLockingThread is used by LockedSynchronizers.
 *          It will create threads that acquire ReentrantLock and also object
 *          monitors.
 * @author  Mandy Chung
 *
 * @build ThreadDump Utils
 */

import java.lang.management.*;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public class SynchronizerLockingThread extends Thread {
    static ReentrantLock lock1 = new ReentrantLock();
    static ReentrantLock lock2 = new ReentrantLock();
    static ReentrantLock lock3 = new ReentrantLock();
    static ReentrantLock lock4 = new ReentrantLock();
    static Lock lock5 = new Lock("lock5");
    static Lock lock6 = new Lock("lock6");
    static Lock lock7 = new Lock("lock7");
    static ReentrantLock lock8 = new ReentrantLock();

    static SynchronizerLockingThread t1 = new Thread1();
    static SynchronizerLockingThread t2 = new Thread2();
    static int count = 2;
    static void startLockingThreads() {
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();

        // wait until t1 and t2 waits
        while (count != 0) {
           try {
               Thread.sleep(100);
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
        }

        Utils.waitForBlockWaitingState(t1);
        Utils.waitForBlockWaitingState(t2);
    }

    static long[] getThreadIds() {
        return new long[] {t1.getId(), t2.getId()};
    }

    static void checkLocks(ThreadInfo[] tinfos) throws Exception {
        int matches = 0;
        for (ThreadInfo info : tinfos) {
            if (info.getThreadId() == t1.getId()) {
                t1.checkLocks(info);
                matches++;
            }
            if (info.getThreadId() == t2.getId()) {
                t2.checkLocks(info);
                matches++;
            }
        }
        if (matches != 2) {
            throw new RuntimeException("MonitorInfo missing");
        }
    }

    static class Lock {
        String name;
        Lock(String name) {
            this.name = name;
        }
        public String toString() {
            return name;
        }
    }

    final String threadName;
    Lock   waitingLock;
    int    numOwnedMonitors;
    Map<String, Lock[]> ownedMonitors;
    Condition       waitingSync;
    int             numOwnedSyncs;
    Map<String, ReentrantLock[]> ownedSyncs;
    public SynchronizerLockingThread(String name) {
        this.threadName = name;
    }

    protected void setExpectedResult(Lock waitingLock,
                                     int numOwnedMonitors,
                                     Map<String, Lock[]> ownedMonitors,
                                     Condition waitingSync,
                                     int numOwnedSyncs,
                                     Map<String, ReentrantLock[]> ownedSyncs) {
        this.waitingLock = waitingLock;
        this.numOwnedMonitors = numOwnedMonitors;
        this.ownedMonitors = ownedMonitors;
        this.waitingSync = waitingSync;
        this.numOwnedSyncs = numOwnedSyncs;
        this.ownedSyncs = ownedSyncs;
    }

    void checkLocks(ThreadInfo info) throws Exception {
        checkThreadInfo(info);
        MonitorInfo[] monitors = info.getLockedMonitors();
        if (monitors.length != numOwnedMonitors) {
            ThreadDump.threadDump();
            throw new RuntimeException("Number of locked monitors = " +
                monitors.length +
                " not matched. Expected: " + numOwnedMonitors);
        }
        // check if each monitor returned in the list is the expected
        // one
        for (MonitorInfo m : monitors) {
            StackTraceElement ste = m.getLockedStackFrame();
            int depth = m.getLockedStackDepth();
            checkStackFrame(info, ste, depth);
            checkMonitor(m, ste.getMethodName());
        }
        // check if each expected monitor is included in the returned
        // list
        for (Map.Entry<String, Lock[]> e : ownedMonitors.entrySet()) {
            for (Lock l : e.getValue()) {
                checkMonitor(e.getKey(), l, monitors);
            }
        }

        // We can only check if the length matches since we have no
        // way to get the AbstractOwnableSynchronizer in ReentrantLock
        LockInfo[] syncs = info.getLockedSynchronizers();
        if (syncs.length != numOwnedSyncs) {
            ThreadDump.threadDump();
            throw new RuntimeException("Number of locked syncs = " +
                syncs.length +
                " not matched. Expected: " + numOwnedSyncs);
        }
    }

    void checkThreadInfo(ThreadInfo info) throws Exception {
        if (!getName().equals(info.getThreadName())) {
            throw new RuntimeException("Name: " + info.getThreadName() +
                " not matched. Expected: " + getName());
        }
        LockInfo l = info.getLockInfo();
        if ((waitingLock != null || waitingSync != null) && l == null) {
            throw new RuntimeException("LockInfo: " + l +
                " not matched. Expected: non-null");
        }
        if (waitingLock == null && waitingSync == null && l != null) {
            throw new RuntimeException("LockInfo: " + l +
                " not matched. Expected: null");
        }

        String waitingLockName;
        int hcode;
        if (waitingLock != null) {
            waitingLockName = waitingLock.getClass().getName();
            hcode = System.identityHashCode(waitingLock);
        } else {
            waitingLockName = waitingSync.getClass().getName();
            hcode = System.identityHashCode(waitingSync);
        }
        if (!waitingLockName.equals(l.getClassName())) {
            throw new RuntimeException("LockInfo : " + l +
                " class name not matched. Expected: " + waitingLockName);
        }
        if (hcode != l.getIdentityHashCode()) {
            throw new RuntimeException("LockInfo: " + l +
                " IdentityHashCode not matched. Expected: " + hcode);
        }

        String lockName = info.getLockName();
        String[] s = lockName.split("@");
        if (!waitingLockName.equals(s[0])) {
            throw new RuntimeException("LockName: " + lockName +
                " class name not matched. Expected: " + waitingLockName);
        }
        int i = Integer.parseInt(s[1], 16);
        if (hcode != i) {
            throw new RuntimeException("LockName: " + lockName +
                " IdentityHashCode not matched. Expected: " + hcode);
        }
    }

    void checkStackFrame(ThreadInfo info, StackTraceElement ste, int depth) {
        StackTraceElement[] stacktrace = info.getStackTrace();
        if (!ste.equals(stacktrace[depth])) {
            System.out.println("LockedStackFrame:- " + ste);
            System.out.println("StackTrace at " + depth + " :-" +
                stacktrace[depth]);
            throw new RuntimeException("LockedStackFrame does not match " +
                "stack frame in ThreadInfo.getStackTrace");
        }
    }
    void checkMonitor(MonitorInfo m, String methodName) {
        for (Map.Entry<String, Lock[]> e : ownedMonitors.entrySet()) {
            if (methodName.equals(e.getKey())) {
                for (Lock l : e.getValue()) {
                    String className = l.getClass().getName();
                    int hcode = System.identityHashCode(l);
                    if (className.equals(m.getClassName()) &&
                        hcode == m.getIdentityHashCode()) {
                        // monitor matched the expected
                        return;
                    }
                }
            }
        }
        throw new RuntimeException("Monitor not expected" + m);
    }
    void checkMonitor(String methodName, Lock l, MonitorInfo[] monitors) {
        String className = l.getClass().getName();
        int hcode = System.identityHashCode(l);
        for (MonitorInfo m : monitors) {
            if (className.equals(m.getClassName()) &&
                hcode == m.getIdentityHashCode() &&
                methodName.equals(m.getLockedStackFrame().getMethodName())) {
                return;
            }
        }
        throw new RuntimeException("Monitor not found in the returned list" +
            " Method: " + methodName + " Lock: " + l);

    }

    static class Thread1 extends SynchronizerLockingThread {
        public Thread1() {
            super("t1");
            initExpectedResult();
        }
        public void run() {
            A();
        }
        void A() {
            lock1.lock();
            try {
                lock2.lock();
                try {
                    lock3.lock();
                    try {
                        B();
                    } finally {
                        lock3.unlock();
                    }
                } finally {
                    lock2.unlock();
                }
            } finally {
                lock1.unlock();
            }
        }
        void B() {
            lock4.lock();
            try {
                synchronized(lock5) {
                    C();
                }
            } finally {
                lock4.unlock();
            }
        }
        void C() {
            synchronized(lock6) {
                D();
            }
        }
        void D() {
            synchronized(lock7) {
                try {
                    // signal to about to wait
                    count--;
                    lock7.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        Map<String, Lock[]> LOCKED_MONITORS;
        Map<String, ReentrantLock[]> LOCKED_SYNCS;
        Lock WAITING_LOCK = lock7;
        int OWNED_MONITORS = 2;
        int OWNED_SYNCS = 4;
        void initExpectedResult() {
            LOCKED_MONITORS = new HashMap<String, Lock[]>();
            LOCKED_MONITORS.put("D", new Lock[0]); // no monitored locked
            LOCKED_MONITORS.put("C", new Lock[] {lock6});
            LOCKED_MONITORS.put("B", new Lock[] {lock5});
            LOCKED_MONITORS.put("A", new Lock[0]);

            LOCKED_SYNCS = new HashMap<String, ReentrantLock[]>();
            LOCKED_SYNCS.put("D", new ReentrantLock[0]); // no sync locked
            LOCKED_SYNCS.put("C", new ReentrantLock[0]); // no sync locked
            LOCKED_SYNCS.put("B", new ReentrantLock[] {lock4});
            LOCKED_SYNCS.put("A", new ReentrantLock[] {lock3, lock2, lock1});
            this.setExpectedResult(WAITING_LOCK,
                                   OWNED_MONITORS, LOCKED_MONITORS,
                                   null,
                                   OWNED_SYNCS, LOCKED_SYNCS);
        }

    }

    static class Thread2 extends SynchronizerLockingThread {
        Map<String, Lock[]> LOCKED_MONITORS = new HashMap<String, Lock[]>();
        Map<String, ReentrantLock[]> LOCKED_SYNCS = new HashMap<String, ReentrantLock[]>();
        Condition c = lock8.newCondition();
        Condition WAITING_LOCK = c;
        int OWNED_MONITORS = 0;
        int OWNED_SYNCS = 0;
        public Thread2() {
            super("t2");
            this.setExpectedResult(null,
                                   OWNED_MONITORS, LOCKED_MONITORS,
                                   WAITING_LOCK,
                                   OWNED_SYNCS, LOCKED_SYNCS);
        }
        public void run() {
            lock8.lock();
            try {
                synchronized(lock7) {
                    count--;
                }
                c.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock8.unlock();
            }
            throw new RuntimeException("should not reach here");
        }
    }

}