| /******************************************************************************* |
| * Copyright (c) 2000, 2006 IBM Corporation and others. |
| * 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.team.core.diff.provider; |
| |
| import java.util.*; |
| |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.ILock; |
| import org.eclipse.team.core.diff.*; |
| 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}. |
| * <p> |
| * This class is not intended to be subclassed by clients. |
| * Clients can instead use {@link DiffTree}. |
| * |
| * @since 3.2 |
| */ |
| 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 listeners = new ListenerList(); |
| |
| private PathTree pathTree = new PathTree(); |
| |
| private ILock lock = Platform.getJobManager().newLock(); |
| |
| private DiffTreeStatistics statistics = new DiffTreeStatistics(); |
| |
| private DiffChangeEvent changes; |
| |
| private boolean lockedForModification; |
| |
| private Map propertyChanges = new HashMap(); |
| |
| /** |
| * Create an empty diff tree. |
| */ |
| public DiffTree() { |
| resetChanges(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.synchronize.ISyncDeltaTree#addSyncDeltaChangeListener(org.eclipse.team.core.synchronize.ISyncDeltaChangeListener) |
| */ |
| public void addDiffChangeListener(IDiffChangeListener listener) { |
| listeners.add(listener); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.synchronize.ISyncDeltaTree#removeSyncDeltaChangeListener(org.eclipse.team.core.synchronize.ISyncDeltaChangeListener) |
| */ |
| public void removeDiffChangeListener(IDiffChangeListener listener) { |
| listeners.remove(listener); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.synchronize.ISyncDeltaTree#accept(org.eclipse.core.runtime.IPath, org.eclipse.team.core.synchronize.ISyncDeltaVisitor) |
| */ |
| 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); |
| } |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.synchronize.ISyncDeltaTree#findMember(org.eclipse.core.runtime.IPath) |
| */ |
| public IDiff getDiff(IPath path) { |
| return (IDiff)pathTree.get(path); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.synchronize.ISyncDeltaTree#getAffectedChildren(org.eclipse.core.runtime.IPath) |
| */ |
| public IPath[] getChildren(IPath path) { |
| return pathTree.getChildren(path); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.delta.ISyncDeltaTree#isEmpty() |
| */ |
| 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 synchronized 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 synchronized 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) { |
| // Use a synchronized block to ensure that the event we send is static |
| final DiffChangeEvent event; |
| final Map propertyChanges; |
| |
| synchronized(this) { |
| event = getChangeEvent(); |
| resetChanges(); |
| 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() { |
| public void handleException(Throwable exception) { |
| // don't log the exception....it is already being logged in Platform#run |
| } |
| public void run() throws Exception { |
| try { |
| lockedForModification = true; |
| if (!event.isEmpty() || event.isReset()) |
| listener.diffsChanged(event, Policy.subMonitorFor(monitor, 100)); |
| for (Iterator iter = propertyChanges.keySet().iterator(); iter.hasNext();) { |
| Integer key = (Integer) iter.next(); |
| Set paths = (Set)propertyChanges.get(key); |
| listener.propertyChanged(DiffTree.this, key.intValue(), (IPath[]) 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()]); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#countFor(int, int) |
| */ |
| public long countFor(int state, int mask) { |
| if (state == 0) |
| return size(); |
| return statistics.countFor(state, mask); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#size() |
| */ |
| public int size() { |
| return pathTree.size(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#setPropertyToRoot(org.eclipse.core.runtime.IPath, int, boolean) |
| */ |
| 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 = new Integer(property); |
| Set changes = (Set)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); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#getProperty(org.eclipse.core.runtime.IPath, int) |
| */ |
| public boolean getProperty(IPath path, int property) { |
| return pathTree.getProperty(path, property); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#setBusy(org.eclipse.team.core.diff.IDiffNode[], org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| 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); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#clearBusy(org.eclipse.core.runtime.IProgressMonitor) |
| */ |
| 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); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.team.core.diff.IDiffTree#hasDiffsMatching(org.eclipse.core.runtime.IPath, org.eclipse.team.core.diff.FastDiffFilter) |
| */ |
| public boolean hasMatchingDiffs(IPath path, final FastDiffFilter filter) { |
| final RuntimeException found = new RuntimeException(); |
| try { |
| accept(path, new IDiffVisitor() { |
| public boolean visit(IDiff 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); |
| } |
| } |
| } |