/*******************************************************************************
 * Copyright (c) 2010, 2015 Oracle 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:
 *     Oracle Corporation - initial API and implementation
 *     IBM - conversion to JUnit test with assertions
 *******************************************************************************/
package org.eclipse.core.tests.runtime.jobs;

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

/**
 * Regression test for bug 316839.
 */
public class Bug_316839 extends AbstractJobManagerTest {

	ILock LOCK = Job.getJobManager().newLock();

	TestBarrier barrier = new TestBarrier(TestBarrier.STATUS_WAIT_FOR_START);

	YieldingTestJob yieldingJob;
	TestJob interruptingJob;
	boolean lockGraphWasEmpty = true;

	public void testBug() {
		// Schedule jobs
		yieldingJob = new YieldingTestJob("job with project rule "); //$NON-NLS-1$
		yieldingJob.setTestRule(new PathRule("/pX")); //$NON-NLS-1$
		yieldingJob.schedule();

		interruptingJob = new TestJob("job with root rule"); //$NON-NLS-1$		job.setRule(new Root());
		interruptingJob.setRule(new PathRule("/"));
		interruptingJob.addJobChangeListener(new JobChangeAdapter() {
			@Override
			public void done(IJobChangeEvent event) {
				//at the moment we are done, the lock graph should be empty
				lockGraphWasEmpty = ((JobManager) Job.getJobManager()).getLockManager().isEmpty();
			}
		});
		barrier.waitForStatus(TestBarrier.STATUS_RUNNING);
		interruptingJob.schedule();
		//let the yielding job perform its yield
		barrier.setStatus(TestBarrier.STATUS_WAIT_FOR_DONE);

		// wait for job to complete or for max time...
		waitForCompletion(yieldingJob);
		waitForCompletion(interruptingJob);
	}

	public class TestJob extends Job {
		public TestJob(String name) {
			super(name);
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			return Status.OK_STATUS;
		}
	}

	// This Job manages calls to begin/endRule()
	public class YieldingTestJob extends Job {
		private ISchedulingRule rule;

		public YieldingTestJob(String name) {
			super(name);
		}

		public void setTestRule(ISchedulingRule rule) {
			this.rule = rule;
		}

		@Override
		protected IStatus run(IProgressMonitor monitor) {
			getJobManager().beginRule(rule, monitor);
			try {
				barrier.setStatus(TestBarrier.STATUS_RUNNING);
				barrier.waitForStatus(TestBarrier.STATUS_WAIT_FOR_DONE);
				// Call to some dependent code that causes a yieldRule().
				// For example, the various routines of ModelManagerImpl
				// that get and return a shared model. If another thread /
				// Job is initializing the model, then the routine
				// ModelManagerImpl$SharedObject.waitForLoadAttempt()
				// calls Job.yieldRule().
				// This test will simulate the scenario by just calling
				// Job.yieldRule() in the same way.
				doYieldRule();
				return Status.OK_STATUS;
			} finally {
				getJobManager().endRule(rule);
			}
		}

		private void doYieldRule() {
			final Job current = Job.getJobManager().currentJob();
			if (current != null) {
				current.yieldRule(null);
			}
		}
	}
}
