/*******************************************************************************
 * Copyright (c) 2010, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.tests.runtime.jobs;

import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.tests.harness.TestBarrier;

/**
 * Make sure that IProgressMonitor's blocked/unblocked is invoked.
 */
public class Bug_311756 extends AbstractJobManagerTest {

	int UNSET = -1;
	int CLEARED = 0;
	int BLOCKED = 1;

	/**
	 * Tests that the progress monitor blocked state is cleared in the normal case
	 * that the rule becomes available after being blocked.
	 */
	public void testBlockingAndUnblockingMonitor() throws Exception {
		final int[] blocked = new int[] {UNSET};
		ProgressMonitorWrapper wrapper = new ProgressMonitorWrapper(new NullProgressMonitor()) {
			@Override
			public void setBlocked(IStatus reason) {
				super.setBlocked(reason);
				blocked[0] = BLOCKED;
			}

			@Override
			public void clearBlocked() {
				super.clearBlocked();
				if (blocked[0] == BLOCKED)
					blocked[0] = CLEARED;
			}
		};
		final TestBarrier barrier = new TestBarrier(TestBarrier.STATUS_START);
		IdentityRule rule = new IdentityRule();
		Job conflicting = new Job("Conflicting") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				barrier.setStatus(TestBarrier.STATUS_RUNNING);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					fail("4.99", e);
				}
				return Status.OK_STATUS;
			}
		};
		conflicting.setRule(rule);
		conflicting.schedule();

		barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
		try {
			Job.getJobManager().beginRule(rule, wrapper);
		} finally {
			Job.getJobManager().endRule(rule);
		}
		assertEquals(blocked[0] == UNSET ? "setBlocked never called" : "clearBlocked never called", CLEARED, blocked[0]);
	}

	/**
	 * Tests that the progress monitor blocked state is cleared in the case that a rule
	 * is transferring to the waiting job while blocked.
	 */
	public void testBlockingAndUnblockingMonitorUsingTransfer() throws Exception {
		final int[] blocked = new int[] {UNSET};
		final ProgressMonitorWrapper wrapper = new ProgressMonitorWrapper(new NullProgressMonitor()) {
			@Override
			public void setBlocked(IStatus reason) {
				super.setBlocked(reason);
				blocked[0] = BLOCKED;
			}

			@Override
			public void clearBlocked() {
				super.clearBlocked();
				if (blocked[0] == BLOCKED)
					blocked[0] = CLEARED;
			}
		};
		final TestBarrier barrier = new TestBarrier(TestBarrier.STATUS_START);
		final IdentityRule rule = new IdentityRule();
		final Thread[] destinationThread = new Thread[1];
		Job conflicting = new Job("Conflicting") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				barrier.setStatus(TestBarrier.STATUS_RUNNING);
				barrier.waitForStatus(TestBarrier.STATUS_BLOCKED);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					fail("4.99", e);
				}
				getJobManager().transferRule(rule, destinationThread[0]);
				return Status.OK_STATUS;
			}
		};
		Job transferTo = new Job("TransferTo") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				destinationThread[0] = getThread();
				barrier.setStatus(TestBarrier.STATUS_BLOCKED);
				getJobManager().beginRule(rule, wrapper);
				getJobManager().endRule(rule);
				return Status.OK_STATUS;
			}
		};
		conflicting.setRule(rule);
		conflicting.schedule();
		barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
		transferTo.schedule();
		waitForCompletion(conflicting);
		waitForCompletion(transferTo);
		assertEquals(blocked[0] == UNSET ? "setBlocked never called" : "clearBlocked never called", CLEARED, blocked[0]);
	}

	/**
	 * Tests that the progress monitor blocked state is cleared in the case that a rule
	 * becomes available to a yielded job while it is blocked.
	 */
	public void testBlockingAndUnblockingMonitorUsingYield() throws Exception {
		final int[] blocked = new int[] {-1};
		final ProgressMonitorWrapper wrapper = new ProgressMonitorWrapper(new NullProgressMonitor()) {
			@Override
			public void setBlocked(IStatus reason) {
				super.setBlocked(reason);
				blocked[0] = BLOCKED;
			}

			@Override
			public void clearBlocked() {
				super.clearBlocked();
				if (blocked[0] == BLOCKED)
					blocked[0] = CLEARED;
			}
		};
		final TestBarrier barrier = new TestBarrier(TestBarrier.STATUS_START);
		IdentityRule rule = new IdentityRule();
		Job conflicting = new Job("Conflicting") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				barrier.setStatus(TestBarrier.STATUS_RUNNING);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					fail("4.99", e);
				}
				Job j = null;
				while (j == null)
					j = yieldRule(wrapper);
				return Status.OK_STATUS;
			}
		};
		conflicting.setRule(rule);
		conflicting.schedule();

		barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
		try {
			Job.getJobManager().beginRule(rule, null);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				fail("4.99", e);
			}
		} finally {
			Job.getJobManager().endRule(rule);
		}
		conflicting.join();
		assertEquals(blocked[0] == UNSET ? "setBlocked never called" : "clearBlocked never called", CLEARED, blocked[0]);
	}

	/**
	 * Tests that the progress monitor blocked state is cleared in the case that a rule
	 * is transferred back to a yielded job while it is blocked.
	 */
	public void testBlockingAndUnblockingMonitorUsingYieldAndTransfer() throws Exception {
		final int[] blocked = new int[] {-1};
		final ProgressMonitorWrapper wrapper = new ProgressMonitorWrapper(new NullProgressMonitor()) {
			@Override
			public void setBlocked(IStatus reason) {
				super.setBlocked(reason);
				blocked[0] = BLOCKED;
			}

			@Override
			public void clearBlocked() {
				super.clearBlocked();
				if (blocked[0] == BLOCKED)
					blocked[0] = CLEARED;
			}
		};
		final TestBarrier barrier = new TestBarrier(TestBarrier.STATUS_START);
		IdentityRule rule = new IdentityRule();
		Job conflicting = new Job("Conflicting") {
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				barrier.setStatus(TestBarrier.STATUS_RUNNING);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					fail("4.99", e);
				}
				Job j = null;
				while (j == null)
					j = yieldRule(wrapper);
				return Status.OK_STATUS;
			}
		};
		conflicting.setRule(rule);
		conflicting.schedule();

		barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
		Job.getJobManager().beginRule(rule, null);
		Job.getJobManager().transferRule(rule, conflicting.getThread());
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			fail("4.99", e);
		}
		conflicting.join();
		assertEquals(blocked[0] == UNSET ? "setBlocked never called" : "clearBlocked never called", CLEARED, blocked[0]);
	}
}
