Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: e828ab0f8bb60e47d61526d959b86ce159746e0a (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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
/*******************************************************************************
 * Copyright (c) 2000, 2017 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.team.core.diff.provider;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.jobs.ILock;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.team.core.diff.FastDiffFilter;
import org.eclipse.team.core.diff.IDiff;
import org.eclipse.team.core.diff.IDiffChangeListener;
import org.eclipse.team.core.diff.IDiffTree;
import org.eclipse.team.core.diff.IDiffVisitor;
import org.eclipse.team.core.diff.IThreeWayDiff;
import org.eclipse.team.internal.core.Policy;
import org.eclipse.team.internal.core.mapping.DiffChangeEvent;
import org.eclipse.team.internal.core.mapping.PathTree;
import org.eclipse.team.internal.core.subscribers.DiffTreeStatistics;

/**
 * Implementation of {@link IDiffTree}.
 *
 * @since 3.2
 * @noextend This class is not intended to be subclassed by clients. Clients can
 *           instead use {@link DiffTree}.
 */
public class DiffTree implements IDiffTree {

	/**
	 * Constant that indicates the start of the property value
	 * range that clients can use when storing properties in this tree.
	 */
	public static final int START_CLIENT_PROPERTY_RANGE = 1024;

	private ListenerList<IDiffChangeListener> listeners = new ListenerList<>();

	private PathTree pathTree = new PathTree();

	private ILock lock = Job.getJobManager().newLock();

	private DiffTreeStatistics statistics = new DiffTreeStatistics();

	private DiffChangeEvent changes;

	private  boolean lockedForModification;

	private Map<Integer, Set<IPath>> propertyChanges = new HashMap<>();

	/**
	 * Create an empty diff tree.
	 */
	public DiffTree() {
		resetChanges();
	}

	@Override
	public void addDiffChangeListener(IDiffChangeListener listener) {
		listeners.add(listener);
	}

	@Override
	public void removeDiffChangeListener(IDiffChangeListener listener) {
		listeners.remove(listener);
	}

	@Override
	public void accept(IPath path, IDiffVisitor visitor, int depth) {
		IDiff delta = getDiff(path);
		if (delta == null || visitor.visit(delta)) {
			if (depth == IResource.DEPTH_ZERO)
				return;
			IPath[] children = getChildren(path);
			for (int i = 0; i < children.length; i++) {
				IPath child = children[i];
				accept(child, visitor, depth == IResource.DEPTH_ONE ? IResource.DEPTH_ZERO : IResource.DEPTH_INFINITE);
			}
		}
	}

	@Override
	public IDiff getDiff(IPath path) {
		return (IDiff)pathTree.get(path);
	}

	@Override
	public IPath[] getChildren(IPath path) {
		return pathTree.getChildren(path);
	}

	@Override
	public boolean isEmpty() {
		return pathTree.isEmpty();
	}

	/**
	 * Add the given {@link IDiff} to the tree. A change event will
	 * be generated unless the call to this method is nested in between calls
	 * to <code>beginInput()</code> and <code>endInput(IProgressMonitor)</code>
	 * in which case the event for this addition and any other sync set
	 * change will be fired in a batched event when <code>endInput</code>
	 * is invoked.
	 * <p>
	 * Invoking this method outside of the above mentioned block will result
	 * in the <code>endInput(IProgressMonitor)</code> being invoked with a null
	 * progress monitor. If responsiveness is required, the client should always
	 * nest sync set modifications within <code>beginInput/endInput</code>.
	 * </p>
	 * @param delta the delta to be added to this set.
	 */
	public void add(IDiff delta) {
		try {
			beginInput();
			IDiff oldDiff = getDiff(delta.getPath());
			internalAdd(delta);
			if (oldDiff != null) {
				internalChanged(delta);
			} else {
				internalAdded(delta);
			}
		} finally {
			endInput(null);
		}
	}

	/**
	 * Remove the given local resource from the set. A change event will
	 * be generated unless the call to this method is nested in between calls
	 * to <code>beginInput()</code> and <code>endInput(IProgressMonitor)</code>
	 * in which case the event for this removal and any other sync set
	 * change will be fired in a batched event when <code>endInput</code>
	 * is invoked.
	 * <p>
	 * Invoking this method outside of the above mentioned block will result
	 * in the <code>endInput(IProgressMonitor)</code> being invoked with a null
	 * progress monitor. If responsiveness is required, the client should always
	 * nest sync set modifications within <code>beginInput/endInput</code>.
	 * </p>
	 *
	 * @param path the path to remove
	 */
	public void remove(IPath path) {
		try {
			beginInput();
			IDiff delta = getDiff(path);
			if (delta != null) {
				internalRemove(delta);
				internalRemoved(path, delta);
			}
		} finally {
			endInput(null);
		}
	}

	/**
	 * Clear the contents of the set
	 */
	public void clear() {
		try {
			beginInput();
			pathTree.clear();
			statistics.clear();
			internalReset();
		} finally {
			endInput(null);
		}
	}

	/**
	 * This method is used to obtain a lock on the set which ensures thread safety
	 * and batches change notification. If the set is locked by another thread,
	 * the calling thread will block until the lock
	 * becomes available. This method uses an <code>org.eclipse.core.runtime.jobs.ILock</code>.
	 * <p>
	 * It is important that the lock is released after it is obtained. Calls to <code>endInput</code>
	 * should be done in a finally block as illustrated in the following code snippet.
	 * <pre>
	 *   try {
	 *       set.beginInput();
	 *       // do stuff
	 *   } finally {
	 *      set.endInput(progress);
	 *   }
	 * </pre>
	 * </p><p>
	 * Calls to <code>beginInput</code> and <code>endInput</code> can be nested and must be matched.
	 * </p>
	 */
	public void beginInput() {
		lock.acquire();
	}

	/**
	 * This method is used to release the lock on this set. The progress monitor is needed to allow
	 * listeners to perform long-running operations is response to the set change. The lock is held
	 * while the listeners are notified so listeners must be cautious in order to avoid deadlock.
	 * @param monitor a progress monitor
	 * @see #beginInput()
	 */
	public void endInput(IProgressMonitor monitor) {
		try {
			if (lock.getDepth() == 1) {
				// Remain locked while firing the events so the handlers
				// can expect the set to remain constant while they process the events
				fireChanges(Policy.monitorFor(monitor));
			}
		} finally {
			lock.release();
		}
	}

	private void fireChanges(final IProgressMonitor monitor) {

		final DiffChangeEvent event = getChangeEvent();
		resetChanges();
		final Map<Integer, Set<IPath>> propertyChanges = this.propertyChanges;
		this.propertyChanges = new HashMap<>();

		if(event.isEmpty() && ! event.isReset() && propertyChanges.isEmpty()) return;
		Object[] listeners = this.listeners.getListeners();
		for (int i = 0; i < listeners.length; i++) {
			final IDiffChangeListener listener = (IDiffChangeListener)listeners[i];
			SafeRunner.run(new ISafeRunnable() {
				@Override
				public void handleException(Throwable exception) {
					// don't log the exception....it is already being logged in Platform#run
				}
				@Override
				public void run() throws Exception {
					try {
						lockedForModification = true;
						if (!event.isEmpty() || event.isReset())
							listener.diffsChanged(event, Policy.subMonitorFor(monitor, 100));
						for (Iterator<Integer> iter = propertyChanges.keySet().iterator(); iter.hasNext();) {
							Integer key = iter.next();
							Set<IPath> paths = propertyChanges.get(key);
							listener.propertyChanged(DiffTree.this, key.intValue(), paths.toArray(new IPath[paths
									.size()]));
						}

					} finally {
						lockedForModification = false;
					}
				}
			});
		}
		monitor.done();
	}

	private DiffChangeEvent getChangeEvent() {
		return changes;
	}

	private void resetChanges() {
		changes = createEmptyChangeEvent();
	}

	private DiffChangeEvent createEmptyChangeEvent() {
		return new DiffChangeEvent(this);
	}

	private void internalAdd(IDiff delta) {
		Assert.isTrue(!lockedForModification);
		IDiff oldDiff = (IDiff)pathTree.get(delta.getPath());
		pathTree.put(delta.getPath(), delta);
		if(oldDiff == null) {
			statistics.add(delta);
		} else {
			statistics.remove(oldDiff);
			statistics.add(delta);
		}
		boolean isConflict = false;
		if (delta instanceof IThreeWayDiff) {
			IThreeWayDiff twd = (IThreeWayDiff) delta;
			isConflict = twd.getDirection() == IThreeWayDiff.CONFLICTING;
		}
		setPropertyToRoot(delta, P_HAS_DESCENDANT_CONFLICTS, isConflict);
	}

	private void internalRemove(IDiff delta) {
		Assert.isTrue(!lockedForModification);
		statistics.remove(delta);
		setPropertyToRoot(delta, P_HAS_DESCENDANT_CONFLICTS, false);
		setPropertyToRoot(delta, P_BUSY_HINT, false);
		pathTree.remove(delta.getPath());
	}

	private void internalAdded(IDiff delta) {
		changes.added(delta);
	}

	private void internalChanged(IDiff delta) {
		changes.changed(delta);
	}
	private void internalRemoved(IPath path, IDiff delta) {
		changes.removed(path, delta);
	}

	private void internalReset() {
		changes.reset();
	}

	/**
	 * Return the paths in this tree that contain diffs.
	 * @return the paths in this tree that contain diffs.
	 */
	public IPath[] getPaths() {
		return pathTree.getPaths();
	}

	/**
	 * Return all the diffs contained in this diff tree.
	 * @return all the diffs contained in this diff tree
	 */
	public IDiff[] getDiffs() {
		return (IDiff[]) pathTree.values().toArray(new IDiff[pathTree.size()]);
	}

	@Override
	public long countFor(int state, int mask) {
		if (state == 0)
			return size();
		return statistics.countFor(state, mask);
	}

	@Override
	public int size() {
		return pathTree.size();
	}

	public void setPropertyToRoot(IDiff node, int property, boolean value) {
		try {
			beginInput();
			IPath[] paths = pathTree.setPropogatedProperty(node.getPath(), property, value);
			accumulatePropertyChanges(property, paths);
		} finally {
			endInput(null);
		}
	}

	private void accumulatePropertyChanges(int property, IPath[] paths) {
		Integer key = Integer.valueOf(property);
		Set<IPath> changes = propertyChanges.get(key);
		if (changes == null) {
			changes = new HashSet<>();
			propertyChanges.put(key, changes);
		}
		for (int i = 0; i < paths.length; i++) {
			IPath path = paths[i];
			changes.add(path);
		}
	}

	@Override
	public boolean getProperty(IPath path, int property) {
		return pathTree.getProperty(path, property);
	}

	@Override
	public void setBusy(IDiff[] diffs, IProgressMonitor monitor) {
		try {
			beginInput();
			for (int i = 0; i < diffs.length; i++) {
				IDiff node = diffs[i];
				setPropertyToRoot(node, P_BUSY_HINT, true);
			}
		} finally {
			endInput(monitor);
		}
	}

	@Override
	public void clearBusy(IProgressMonitor monitor) {
		try {
			beginInput();
			IPath[] paths = pathTree.getPaths();
			for (int i = 0; i < paths.length; i++) {
				IPath path = paths[i];
				IPath[] changed = pathTree.setPropogatedProperty(path, P_BUSY_HINT, false);
				accumulatePropertyChanges(P_BUSY_HINT, changed);
			}
		} finally {
			endInput(monitor);
		}
	}

	@Override
	public boolean hasMatchingDiffs(IPath path, final FastDiffFilter filter) {
		final RuntimeException found = new RuntimeException();
		try {
			accept(path, delta -> {
				if (filter.select(delta)) {
					throw found;
				}
				return false;
			}, IResource.DEPTH_INFINITE);
		} catch (RuntimeException e) {
			if (e == found)
				return true;
			throw e;
		}
		return false;
	}

	/**
	 * Report to any listeners that an error has occurred while populating the
	 * set. Listeners will be notified that an error occurred and can react
	 * accordingly.
	 * </p>
	 *
	 * @param status
	 *            the status that describes the error that occurred.
	 */
	public void reportError(IStatus status) {
		try {
			beginInput();
			getChangeEvent().errorOccurred(status);
		} finally {
			endInput(null);
		}
	}
}

Back to the top