/*
 * Copyright (c) 2006, 2018, 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.
 */
package nsk.share.jdi;

import java.util.*;
import nsk.share.TestBug;
import nsk.share.locks.MonitorLockingThread;

/*
 * This class generates MonitorWaitEvent and MonitorWaitedEvent
 */
class MonitorWaitExecutor extends EventActionsExecutor {
    // MonitorWaited event may occurs when waiting thread was interrupted,
    // notified by notify or notifyAll, or when timeout expired
    enum ExitFromWaitType {
        EXIT_WITH_TIMEOUT,
        INTERRUPT,
        NOTIFY,
        NOTIFY_ALL
    }

    // this thread forces MonitorWaitExecutor to exit from wait()
    class AuxiliaryThread extends Thread {
        private Thread threadToInterrupt;

        private Object monitor;

        public AuxiliaryThread(Thread threadToInterrupt, Object monitor) {
            this.threadToInterrupt = threadToInterrupt;
            this.monitor = monitor;
        }

        public void run() {
            // wait when interrupted thread switches state to 'TIMED_WAITING'
            while ((threadToInterrupt.getState() != Thread.State.WAITING) && !exitedFromWait) {
                yield();
            }

            // threadToInterrupt 'spuriously' exited from wait()
            if(exitedFromWait)
                return;

            if (exitFromWaitType == ExitFromWaitType.INTERRUPT) {
                threadToInterrupt.interrupt();
            } else if ((exitFromWaitType == ExitFromWaitType.NOTIFY)
                    || (exitFromWaitType == ExitFromWaitType.NOTIFY_ALL)) {
                /*
                 * NOTE: thread's state WAITING doesn't guarantee that thread released
                 * monitor, and entering to the next synchronized block may cause MonitorContentedEnterEvent
                 * (if corresponding request was created).
                 *
                 * Debugger should take in account this issue.
                 */
                synchronized (monitor) {
                    if (exitFromWaitType == ExitFromWaitType.NOTIFY)
                        monitor.notify();
                    else if (exitFromWaitType == ExitFromWaitType.NOTIFY_ALL)
                        monitor.notifyAll();
                }
            }
        }
    }

    // thread may 'spuriously' exit from wait(), in this case AuxiliaryThread shouldn't wait 'WAITING' state
    private volatile boolean exitedFromWait;

    // save data about MonitorWait events
    private boolean monitorWait;

    // save data about MonitorWaited events
    private boolean monitorWaited;

    public MonitorWaitExecutor(boolean monitorWait, boolean monitorWaited) {
        this.monitorWait = monitorWait;
        this.monitorWaited = monitorWaited;
    }

    protected ExitFromWaitType exitFromWaitType;

    public void doEventAction() {
        for (ExitFromWaitType exitType : ExitFromWaitType.values()) {
            exitFromWaitType = exitType;

            monitorWait();
        }
    }

    protected void monitorWait() {

        exitedFromWait = false;

        long timeout;

        if (exitFromWaitType == ExitFromWaitType.EXIT_WITH_TIMEOUT)
            timeout = 100;
        else
            timeout = 0;

        if (monitorWait) {
            DebuggeeEventData.DebugMonitorWaitEventData eventData = new DebuggeeEventData.DebugMonitorWaitEventData(
                    this, Thread.currentThread(), timeout, this);
            addEventData(eventData);
        }

        if (monitorWaited) {
            DebuggeeEventData.DebugMonitorWaitedEventData eventData = new DebuggeeEventData.DebugMonitorWaitedEventData(
                    this, Thread.currentThread(),
                    (exitFromWaitType == ExitFromWaitType.EXIT_WITH_TIMEOUT),
                    this);
            addEventData(eventData);
        }

        AuxiliaryThread auxiliaryThread = null;

        if (exitFromWaitType != ExitFromWaitType.EXIT_WITH_TIMEOUT) {
            auxiliaryThread = new AuxiliaryThread(Thread.currentThread(), this);
            auxiliaryThread.start();
        }

        try {
            eventMethod(timeout);
        } catch (InterruptedException e) {
            // expected exception
        }

        exitedFromWait = true;

        if (auxiliaryThread != null) {
            // don't using join, because of join is realized with using of
            // wait(), and this method can generate unexpected events
            while (auxiliaryThread.getState() != Thread.State.TERMINATED)
                Thread.yield();
        }
    }

    // move event actions to the separate method to simplify method redifinition
    // in subclasses
    synchronized protected void eventMethod(long timeout)
            throws InterruptedException {
        wait(timeout);
    }

}

/*
 * Subclass of MonitorWaitExecutor, define its own event method(copy of parent
 * method), intended for event filters test
 */
class MonitorWaitExecutor_1Subclass extends MonitorWaitExecutor {
    public MonitorWaitExecutor_1Subclass(boolean monitorWait,
            boolean monitorWaited) {
        super(monitorWait, monitorWaited);
    }

    synchronized protected void eventMethod(long timeout)
            throws InterruptedException {
        wait(timeout);
    }
}

/*
 * Subclass of MonitorWaitExecutor, define its own event method(copy of parent
 * method), intended for event filters test
 */
class MonitorWaitExecutor_2Subclass extends MonitorWaitExecutor_1Subclass {
    public MonitorWaitExecutor_2Subclass(boolean monitorWait,
            boolean monitorWaited) {
        super(monitorWait, monitorWaited);
    }

    synchronized protected void eventMethod(long timeout)
            throws InterruptedException {
        wait(timeout);
    }
}

/*
 * This class generates MonitorContendedEnterEvent and
 * MonitorContendedEnteredEvent
 */
class MonitorEnterExecutor extends EventActionsExecutor {
    /*
     * Types of monitor acquiring:
     * - through synchronized block
     * - through synchronized method
     * - through JNI MonitorEnter
     */
    static enum MonitorAcquireType {
        SYNCHRONIZED_BLOCK,
        SYNCHRONIZED_METHOD,
        JNI_MONITOR_ENTER
    }

    // native method uses this class
    private static final Class<?> jniError = nsk.share.TestJNIError.class;


    static final String nativeLibararyName = "MonitorEnterExecutor";

    static {
        System.loadLibrary(nativeLibararyName);
    }

    // this thread forces MonitorLockingThread to release holding lock
    static class LockFreeThread extends Thread {
        private Thread blockedThread;

        private MonitorLockingThread lockingThread;

        public LockFreeThread(Thread blockedThread,
                MonitorLockingThread lockingThread) {
            this.blockedThread = blockedThread;
            this.lockingThread = lockingThread;
        }

        public void run() {
            // wait when blocked thread switches state to 'BLOCKED'
            while (blockedThread.getState() != Thread.State.BLOCKED)
                yield();

            lockingThread.releaseLock();
        }
    }

    private boolean monitorEnter;

    private boolean monitorEntered;

    public MonitorEnterExecutor(boolean monitorEnter, boolean monitorEntered) {
        this.monitorEnter = monitorEnter;
        this.monitorEntered = monitorEntered;
    }

    protected void monitorEnter() {
        // locking thread acquires 'this' lock
        MonitorLockingThread lockingThread = new MonitorLockingThread(this);
        lockingThread.acquireLock();

        if (monitorEnter) {
            DebuggeeEventData.DebugMonitorEnterEventData eventData = new DebuggeeEventData.DebugMonitorEnterEventData(
                    this, Thread.currentThread(), this);
            addEventData(eventData);
        }
        if (monitorEntered) {
            DebuggeeEventData.DebugMonitorEnteredEventData eventData = new DebuggeeEventData.DebugMonitorEnteredEventData(
                    this, Thread.currentThread(), this);
            addEventData(eventData);
        }

        /*
         * This thread forces lockingThread to free 'this' lock when current thread's state will change to 'BLOCKED'
         *
         * NOTE: this method to provoke MonitorContended events isn't 100% robust
         * Tests should take in account this issue.
         *
         */
        LockFreeThread lockFreeThread = new LockFreeThread(Thread.currentThread(), lockingThread);
        lockFreeThread.start();

        // try to acquire 'this' lock
        eventMethod();

        while(lockingThread.getState() != Thread.State.TERMINATED)
            Thread.yield();

        while(lockFreeThread.getState() != Thread.State.TERMINATED)
            Thread.yield();
    }

    private MonitorAcquireType monitorAcquireType;

    // generate events in different ways
    public void doEventAction() {
        for (MonitorAcquireType monitorAcquireType : MonitorAcquireType.values()) {
            this.monitorAcquireType = monitorAcquireType;
            monitorEnter();
        }
    }

    protected void eventMethod() {
        switch (monitorAcquireType) {
        case SYNCHRONIZED_BLOCK:
            synchronizedBlock();
            break;
        case SYNCHRONIZED_METHOD:
            synchronizedMethod();
            break;
        case JNI_MONITOR_ENTER:
            nativeJNIMonitorEnter();
            break;

        default:
            throw new TestBug("Invalid monitorAcquireType: "
                    + monitorAcquireType);
        }
    }

    // move event actions to the separate methods to simplify method
    // redifinition in subclasses:

    protected void synchronizedBlock() {
        // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
        synchronized (this) {
            // don't expect events for following synchronized block
            synchronized (this) {
            }
        }
    }

    // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
    synchronized protected void synchronizedMethod() {
        // don't expect events for following synchronized block
        synchronized (this) {
        }
    }

    // call JNI methods MonitorEnter and MonitorExit for 'this' object
    protected native void nativeJNIMonitorEnter();

}

/*
 * Subclass of MonitorEnterExecutor, defines its own event methods(copy of parent
 * method), intended for event filters test
 */
class MonitorEnterExecutor_1Subclass extends MonitorEnterExecutor {
    static {
        System.loadLibrary(nativeLibararyName);
    }

    public MonitorEnterExecutor_1Subclass(boolean monitorEnter,
            boolean monitorEntered) {
        super(monitorEnter, monitorEntered);
    }

    protected void synchronizedBlock() {
        // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
        synchronized (this) {
        }
    }

    // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
    synchronized protected void synchronizedMethod() {
    }

    // call JNI methods MonitorEnter and MonitorExit for 'this' object
    protected native void nativeJNIMonitorEnter();
}

/*
 * Subclass of MonitorEnterExecutor, defines its own event methods(copy of parent
 * method), intended for event filters test
 */
class MonitorEnterExecutor_2Subclass extends MonitorEnterExecutor_1Subclass {
    static {
        System.loadLibrary(nativeLibararyName);
    }

    public MonitorEnterExecutor_2Subclass(boolean monitorEnter,
            boolean monitorEntered) {
        super(monitorEnter, monitorEntered);
    }

    protected void synchronizedBlock() {
        // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
        synchronized (this) {
        }
    }

    // MonitorContendedEnterEvent and MonitorContendedEnteredEvent should occur here
    synchronized protected void synchronizedMethod() {
    }

    // call JNI methods MonitorEnter and MonitorExit for 'this' object
    protected native void nativeJNIMonitorEnter();
}

/*
 * Class is used as base debuggee in tests for following events and event requests:
 * - MonitorContendedEnterRequest / MonitorContendedEnterEvent
 * - MonitorContendedEnteredRequest / MonitorContendedEnteredEvent
 * - MonitorWaitRequest / MonitorWaitEvent
 * - MonitorWaitedRequest / MonitorWaitedEvent
 */
public class MonitorEventsDebuggee extends JDIEventsDebuggee {
    public static void main(String[] args) {
        MonitorEventsDebuggee debuggee = new MonitorEventsDebuggee();
        debuggee.doTest(args);
    }

    protected void createActionsExecutors(String description, int eventsCount) {
        boolean monitorEnter = description
                .contains(JDIEventsDebugger.EventType.MONITOR_CONTENTED_ENTER
                        .name());
        boolean monitorEntered = description
                .contains(JDIEventsDebugger.EventType.MONITOR_CONTENTED_ENTERED
                        .name());
        boolean monitorWait = description
                .contains(JDIEventsDebugger.EventType.MONITOR_WAIT.name());
        boolean monitorWaited = description
                .contains(JDIEventsDebugger.EventType.MONITOR_WAITED.name());

        if (monitorEnter || monitorEntered || monitorWait || monitorWaited) {
            createActionsExecutors(monitorEnter, monitorEntered, monitorWait,
                    monitorWaited, eventsCount);
        } else
            throw new TestBug(
                    "Invalid command format (required event not specified)");
    }

    private List<DebuggeeEventData.DebugMonitorEventData> eventsData = new ArrayList<DebuggeeEventData.DebugMonitorEventData>();

    protected void clearResults() {
        super.clearResults();
        eventsData.clear();
    }

    private void createActionsExecutors(boolean monitorEnter,
            boolean monitorEntered, boolean monitorWait, boolean monitorWaited,
            int actionsCount) {
        EventActionsThread thread;

        // create 3 instances of generating objects of 3 different classes (for
        // event filters tests)
        if (monitorEnter || monitorEntered) {
            thread = new EventActionsThread(new MonitorEnterExecutor(
                    monitorEnter, monitorEntered), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);

            thread = new EventActionsThread(new MonitorEnterExecutor_1Subclass(
                    monitorEnter, monitorEntered), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);

            thread = new EventActionsThread(new MonitorEnterExecutor_2Subclass(
                    monitorEnter, monitorEntered), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);
        }

        // create 3 instances of generating objects of 3 different classes (for
        // event filters tests)
        if (monitorWait || monitorWaited) {
            thread = new EventActionsThread(new MonitorWaitExecutor(
                    monitorWait, monitorWaited), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);

            thread = new EventActionsThread(new MonitorWaitExecutor_1Subclass(
                    monitorWait, monitorWaited), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);

            thread = new EventActionsThread(new MonitorWaitExecutor_2Subclass(
                    monitorWait, monitorWaited), actionsCount);
            thread.start();
            eventActionsExecutorsPool.add(thread);
        }
    }

    // start event generating threads and wait when all this threads finish
    // execution
    // override parent method because of Thread.join() used in parent method
    // generates unexpected MonitorWait/MonitorWaited events
    protected void startExecution() {
        if (eventActionsExecutorsPool.size() == 0) {
            throw new TestBug("ActionsExecutors were not created");
        }

        for (EventActionsThread thread : eventActionsExecutorsPool) {
            thread.startExecution();
        }

        // wait completion of test threads in separate thread to free thread listening commands
        executionControllingThread = new Thread(
                new Runnable() {
                    public void run() {
                        boolean executionCompleted;
                        do {
                            try {
                                // give time to event generators
                                Thread.sleep(500);
                            } catch (InterruptedException e) {
                                unexpectedException(e);
                            }
                            executionCompleted = true;
                            for (EventActionsThread thread : eventActionsExecutorsPool) {
                                if (thread.isAlive()) {
                                    executionCompleted = false;
                                    break;
                                }
                            }
                        } while (!executionCompleted);

                        completeExecution();
                    }
                });

        executionControllingThread.start();
    }

}
