Skip to main content
summaryrefslogtreecommitdiffstats
blob: 30d5cf3648a7e6159020f7a20ae25e9979ac22d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*******************************************************************************
 * Copyright (c) 2008, 2009 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0, which accompanies this distribution
 * and is available at http://www.eclipse.org/legal/epl-v10.html.
 * 
 * Contributors:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.core.internal.utility;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.synchronizers.Synchronizer;

/**
 * This synchronizer will perform synchronizations in an Eclipse job on a
 * separate thread, allowing calls to {@link Synchronizer#synchronize()}
 * to return immediately.
 * <p>
 * If necessary, the client-supplied job command should handle any
 * exceptions appropriately. Although, the default exception-handling provided
 * by the Eclipse Job Framework is probably adequate in most cases:<ul>
 * <li>An {@link org.eclipse.core.runtime.OperationCanceledException OperationCanceledException}
 *     results in a {@link org.eclipse.core.runtime.Status#CANCEL_STATUS CANCEL_STATUS}.
 * <li>Any non-{@link ThreadDeath} {@link Throwable}
 *     results in a {@link org.eclipse.core.runtime.IStatus#ERROR ERROR}
 *     {@link org.eclipse.core.runtime.IStatus IStatus}
 * </ul>
 * @see org.eclipse.core.internal.jobs.Worker#run()
 */
public class JobSynchronizer
	implements Synchronizer
{
	/**
	 * The synchronization is performed by this job. The same job is used
	 * for every start/stop cycle (since a job can be re-started).
	 */
	private final SynchronizationJob job;


	// ********** construction **********

	/**
	 * Construct a job synchronizer that uses the specified job command to
	 * perform the synchronization. Assign the generated Eclipse job the
	 * specified name.
	 */
	public JobSynchronizer(String jobName, JobCommand command) {
		this(jobName, command, null);
	}

	/**
	 * Construct a job synchronizer that uses the specified job command to
	 * perform the synchronization. Assign the generated Eclipse job the
	 * specified name and scheduling rule.
	 */
	public JobSynchronizer(String jobName, JobCommand command, ISchedulingRule schedulingRule) {
		super();
		this.job = this.buildJob(jobName, command, schedulingRule);
	}

	SynchronizationJob buildJob(String jobName, JobCommand command, ISchedulingRule schedulingRule) {
		return new SynchronizationJob(jobName, command, schedulingRule);
	}


	// ********** Synchronizer implementation **********

	/**
	 * Allow the job to be scheduled, but postpone the first synchronization
	 * until requested, via {@link #synchronize()}.
	 */
	public void start() {
		this.job.start();
	}

	/**
	 * "Schedule" the job.
	 */
	public void synchronize() {
		this.job.synchronize();
	}

	/**
	 * Wait for the current job execution to complete.
	 */
	public void stop() {
		this.job.stop();
	}

	@Override
	public String toString() {
		return StringTools.buildToStringFor(this, this.job);
	}


	// ********** synchronization job **********

	/**
	 * This is the job that gets scheduled by the job synchronizer.
	 * When the job is run it executes the client-supplied job command.
	 */
	static class SynchronizationJob extends Job {
		/**
		 * The client-supplied job command that executes every time the job
		 * runs.
		 */
		private final JobCommand command;

		/**
		 * When this flag is set to false, the job does not stop immediately;
		 * but it will not be scheduled to run again.
		 */
		// use 'volatile' because synchronization isn't really required
		volatile boolean shouldSchedule;


		SynchronizationJob(String jobName, JobCommand command, ISchedulingRule schedulingRule) {
			super(jobName);
			if (command == null) {
				throw new NullPointerException();
			}
			this.command = command;
			this.shouldSchedule = false;
			this.setRule(schedulingRule);
		}

		/**
		 * Just set the "should schedule" flag so the job <em>can</em> be
		 * scheduled; but don't actually schedule it.
		 */
		void start() {
			if (this.shouldSchedule) {
				throw new IllegalStateException("The Synchronizer was not stopped."); //$NON-NLS-1$
			}
			this.shouldSchedule = true;
		}

		/**
		 * Simply re-schedule the job, allowing the current execution
		 * to run to completion (i.e. do not try to cancel it prematurely).
		 * This should minimize the number of times the job is re-executed
		 * (recursively and otherwise).
		 */
		void synchronize() {
			this.schedule();
		}

		/**
		 * Any uncaught exceptions thrown by the command will be reasonably
		 * handled by the Job Framework.
		 * @see org.eclipse.core.internal.jobs.Worker#run()
		 */
		@Override
		protected IStatus run(IProgressMonitor monitor) {
			return this.command.execute(monitor);
		}

		/**
		 * Prevent the job from running again and wait for the current
		 * execution, if there is any, to end before returning.
		 */
		void stop() {
			if ( ! this.shouldSchedule) {
				throw new IllegalStateException("The Synchronizer was not started."); //$NON-NLS-1$
			}
			// this will prevent the job from being scheduled to run again
			this.shouldSchedule = false;
			// this will cancel the job if it has already been scheduled, but is currently WAITING
			this.cancel();
			try {
				// if the job is currently RUNNING, wait until it is done before returning
				this.join();
			} catch (InterruptedException ex) {
				// the thread that called #stop() was interrupted while waiting to
				// join the synchronization job - ignore;
				// 'shouldSchedule' is still set to 'false', so the job loop will still stop - we
				// just won't wait around for it...
			}
		}

		/**
		 * This is part of the normal {@link Job} behavior. By default, it is
		 * not used (i.e. it always returns <code>true</code>).
		 * We implement it here.
		 */
		@Override
		public boolean shouldSchedule() {
			return this.shouldSchedule;
		}

	}

}

Back to the top