/** Create the marker update handler. */ public SynchronizeModelUpdateHandler(AbstractSynchronizeModelProvider provider) { super( TeamUIMessages.SynchronizeModelProvider_0, TeamUIMessages.SynchronizeModelUpdateHandler_0); // this.provider = provider; ResourcesPlugin.getWorkspace().addResourceChangeListener(this); provider.getSyncInfoSet().addSyncSetChangedListener(this); }
public void dispose() { shutdown(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); provider.getSyncInfoSet().removeSyncSetChangedListener(this); }
/** * Handler that serializes the updating of a synchronize model provider. All modifications to the * synchronize model are performed in this handler's thread. */ public class SynchronizeModelUpdateHandler extends BackgroundEventHandler implements IResourceChangeListener, ISyncInfoSetChangeListener { private static final IWorkspaceRoot ROOT = ResourcesPlugin.getWorkspace().getRoot(); // Event that indicates that the markers for a set of elements has changed private static final int MARKERS_CHANGED = 1; private static final int BUSY_STATE_CHANGED = 2; private static final int RESET = 3; private static final int SYNC_INFO_SET_CHANGED = 4; private AbstractSynchronizeModelProvider provider; private Set pendingLabelUpdates = Collections.synchronizedSet(new HashSet()); // Flag to indicate the need for an early dispath in order to show // busy for elements involved in an operation private boolean dispatchEarly = false; private static final int EARLY_DISPATCH_INCREMENT = 100; /** Custom event for posting marker changes */ class MarkerChangeEvent extends Event { private final ISynchronizeModelElement[] elements; public MarkerChangeEvent(ISynchronizeModelElement[] elements) { super(MARKERS_CHANGED); this.elements = elements; } public ISynchronizeModelElement[] getElements() { return elements; } } /** Custom event for posting busy state changes */ class BusyStateChangeEvent extends Event { private final ISynchronizeModelElement element; private final boolean isBusy; public BusyStateChangeEvent(ISynchronizeModelElement element, boolean isBusy) { super(BUSY_STATE_CHANGED); this.element = element; this.isBusy = isBusy; } public ISynchronizeModelElement getElement() { return element; } public boolean isBusy() { return isBusy; } } /** Custom event for posting sync info set changes */ class SyncInfoSetChangeEvent extends Event { private final ISyncInfoSetChangeEvent event; public SyncInfoSetChangeEvent(ISyncInfoSetChangeEvent event) { super(SYNC_INFO_SET_CHANGED); this.event = event; } public ISyncInfoSetChangeEvent getEvent() { return event; } } private IPropertyChangeListener listener = new IPropertyChangeListener() { public void propertyChange(final PropertyChangeEvent event) { if (event.getProperty() == ISynchronizeModelElement.BUSY_PROPERTY) { Object source = event.getSource(); if (source instanceof ISynchronizeModelElement) updateBusyState( (ISynchronizeModelElement) source, ((Boolean) event.getNewValue()).booleanValue()); } } }; private boolean performingBackgroundUpdate; /* * Map used to keep track of additions so they can be added in batch at the end of the update */ private Map additionsMap; /** Create the marker update handler. */ public SynchronizeModelUpdateHandler(AbstractSynchronizeModelProvider provider) { super( TeamUIMessages.SynchronizeModelProvider_0, TeamUIMessages.SynchronizeModelUpdateHandler_0); // this.provider = provider; ResourcesPlugin.getWorkspace().addResourceChangeListener(this); provider.getSyncInfoSet().addSyncSetChangedListener(this); } /** * Return the marker types that are of interest to this handler. * * @return the marker types that are of interest to this handler */ protected String[] getMarkerTypes() { return new String[] {IMarker.PROBLEM}; } /** * Return the <code>AbstractTreeViewer</code> associated with this provider or <code>null</code> * if the viewer is not of the proper type. * * @return the structured viewer that is displaying the model managed by this provider */ public StructuredViewer getViewer() { return provider.getViewer(); } /* (non-Javadoc) * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(final IResourceChangeEvent event) { String[] markerTypes = getMarkerTypes(); Set handledResources = new HashSet(); Set changes = new HashSet(); // Accumulate all distinct resources that have had problem marker // changes for (int idx = 0; idx < markerTypes.length; idx++) { IMarkerDelta[] markerDeltas = event.findMarkerDeltas(markerTypes[idx], true); for (int i = 0; i < markerDeltas.length; i++) { IMarkerDelta delta = markerDeltas[i]; IResource resource = delta.getResource(); if (!handledResources.contains(resource)) { handledResources.add(resource); ISynchronizeModelElement[] elements = provider.getClosestExistingParents(delta.getResource()); if (elements != null && elements.length > 0) { for (int j = 0; j < elements.length; j++) { ISynchronizeModelElement element = elements[j]; changes.add(element); } } } } } if (!changes.isEmpty()) { updateMarkersFor( (ISynchronizeModelElement[]) changes.toArray(new ISynchronizeModelElement[changes.size()])); } } private void updateMarkersFor(ISynchronizeModelElement[] elements) { queueEvent(new MarkerChangeEvent(elements), false /* not on front of queue */); } protected void updateBusyState(ISynchronizeModelElement element, boolean isBusy) { queueEvent(new BusyStateChangeEvent(element, isBusy), false /* not on front of queue */); } /* (non-Javadoc) * @see org.eclipse.team.internal.core.BackgroundEventHandler#processEvent(org.eclipse.team.internal.core.BackgroundEventHandler.Event, org.eclipse.core.runtime.IProgressMonitor) */ protected void processEvent(Event event, IProgressMonitor monitor) throws CoreException { switch (event.getType()) { case BackgroundEventHandler.RUNNABLE_EVENT: executeRunnable(event, monitor); break; case MARKERS_CHANGED: // Changes contains all elements that need their labels updated long start = System.currentTimeMillis(); ISynchronizeModelElement[] elements = getChangedElements(event); for (int i = 0; i < elements.length; i++) { ISynchronizeModelElement element = elements[i]; propagateProblemMarkers(element); updateParentLabels(element); } if (Policy.DEBUG_SYNC_MODELS) { long time = System.currentTimeMillis() - start; DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss.SSS"); // $NON-NLS-1$ String took = TIME_FORMAT.format(new Date(time)); System.out.println( took + " for " + elements.length + " files"); // $NON-NLS-1$//$NON-NLS-2$ } break; case BUSY_STATE_CHANGED: BusyStateChangeEvent e = (BusyStateChangeEvent) event; queueForLabelUpdate(e.getElement()); if (e.isBusy()) { // indicate that we want an early dispatch to show busy elements dispatchEarly = true; } break; case RESET: // Perform the reset immediately pendingLabelUpdates.clear(); provider.reset(); break; case SYNC_INFO_SET_CHANGED: // Handle the sync change immediately handleChanges(((SyncInfoSetChangeEvent) event).getEvent(), monitor); default: break; } } private ISynchronizeModelElement[] getChangedElements(Event event) { if (event.getType() == MARKERS_CHANGED) { return ((MarkerChangeEvent) event).getElements(); } return new ISynchronizeModelElement[0]; } /* (non-Javadoc) * @see org.eclipse.team.internal.core.BackgroundEventHandler#doDispatchEvents(org.eclipse.core.runtime.IProgressMonitor) */ protected boolean doDispatchEvents(IProgressMonitor monitor) throws TeamException { // Fire label changed dispatchEarly = false; if (pendingLabelUpdates.isEmpty()) { return false; } else { Utils.asyncExec( new Runnable() { public void run() { firePendingLabelUpdates(); } }, getViewer()); return true; } } /** * Forces the viewer to update the labels for queued elemens whose label has changed during this * round of changes. This method should only be invoked in the UI thread. */ protected void firePendingLabelUpdates() { if (!Utils.canUpdateViewer(getViewer())) return; try { Object[] updates = pendingLabelUpdates.toArray(new Object[pendingLabelUpdates.size()]); updateLabels(updates); } finally { pendingLabelUpdates.clear(); } } /* * Forces the viewer to update the labels for the given elements */ private void updateLabels(Object[] elements) { StructuredViewer tree = getViewer(); if (Utils.canUpdateViewer(tree)) { tree.update(elements, null); } } /** * Queue all the parent elements for a label update. * * @param element the element whose label and parent labels need to be updated */ public void updateParentLabels(ISynchronizeModelElement element) { queueForLabelUpdate(element); while (element.getParent() != null) { element = (ISynchronizeModelElement) element.getParent(); queueForLabelUpdate(element); } } /** * Update the label of the given diff node. Diff nodes are accumulated and updated in a single * call. * * @param diffNode the diff node to be updated */ protected void queueForLabelUpdate(ISynchronizeModelElement diffNode) { pendingLabelUpdates.add(diffNode); } /** * Calculate and propagate problem markers in the element model * * @param element the ssynchronize element */ private void propagateProblemMarkers(ISynchronizeModelElement element) { IResource resource = element.getResource(); if (resource != null) { String property = provider.calculateProblemMarker(element); // If it doesn't have a direct change, a parent might boolean recalculateParentDecorations = hadProblemProperty(element, property); if (recalculateParentDecorations) { ISynchronizeModelElement parent = (ISynchronizeModelElement) element.getParent(); if (parent != null) { propagateProblemMarkers(parent); } } } } // none -> error // error -> none // none -> warning // warning -> none // warning -> error // error -> warning private boolean hadProblemProperty(ISynchronizeModelElement element, String property) { boolean hadError = element.getProperty(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY); boolean hadWarning = element.getProperty(ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY); // Force recalculation of parents of phantom resources IResource resource = element.getResource(); if (resource != null && resource.isPhantom()) { return true; } if (hadError) { if (!(property == ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY)) { element.setPropertyToRoot(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY, false); if (property != null) { // error -> warning element.setPropertyToRoot(property, true); } // error -> none // recalculate parents return true; } return false; } else if (hadWarning) { if (!(property == ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY)) { element.setPropertyToRoot( ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY, false); if (property != null) { // warning -> error element.setPropertyToRoot(property, true); return false; } // warning -> none return true; } return false; } else { if (property == ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY) { // none -> error element.setPropertyToRoot(property, true); return false; } else if (property == ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY) { // none -> warning element.setPropertyToRoot(property, true); return true; } return false; } } /* * Queue an event that will reset the provider */ private void reset() { queueEvent(new ResourceEvent(ROOT, RESET, IResource.DEPTH_INFINITE), false); } public void dispose() { shutdown(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); provider.getSyncInfoSet().removeSyncSetChangedListener(this); } /* (non-Javadoc) * @see org.eclipse.team.internal.core.BackgroundEventHandler#getShortDispatchDelay() */ protected long getShortDispatchDelay() { if (dispatchEarly) { dispatchEarly = false; return EARLY_DISPATCH_INCREMENT; } return super.getShortDispatchDelay(); } /** * This method is invoked whenever a node is added to the viewer by the provider or a * sub-provider. The handler adds an update listener to the node and notifies the root provider * that a node was added. * * @param element the added element * @param provider the provider that added the element */ public void nodeAdded( ISynchronizeModelElement element, AbstractSynchronizeModelProvider provider) { element.addPropertyChangeListener(listener); this.provider.nodeAdded(element, provider); if (Policy.DEBUG_SYNC_MODELS) { System.out.println( "Node added: " + getDebugDisplayLabel(element) + " -> " + getDebugDisplayLabel((ISynchronizeModelElement) element.getParent()) + " : " + getDebugDisplayLabel(provider)); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } /** * This method is invoked whenever a node is removed the viewer by the provider or a sub-provider. * The handler removes any listener and notifies the root provider that a node was removed. The * node removed may have children for which a nodeRemoved callback was not invoked (see * modelObjectCleared). * * @param element the removed element * @param provider the provider that added the element */ public void nodeRemoved( ISynchronizeModelElement element, AbstractSynchronizeModelProvider provider) { element.removePropertyChangeListener(listener); this.provider.nodeRemoved(element, provider); if (Policy.DEBUG_SYNC_MODELS) { System.out.println( "Node removed: " + getDebugDisplayLabel(element) + " -> " + getDebugDisplayLabel((ISynchronizeModelElement) element.getParent()) + " : " + getDebugDisplayLabel(provider)); // $NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } /** * This method is invoked whenever a model object (i.e. node) is cleared from the model. This is * similar to node removal but is deep. * * @param node the node that was cleared */ public void modelObjectCleared(ISynchronizeModelElement node) { node.removePropertyChangeListener(listener); this.provider.modelObjectCleared(node); if (Policy.DEBUG_SYNC_MODELS) { System.out.println("Node cleared: " + getDebugDisplayLabel(node)); // $NON-NLS-1$ } } private String getDebugDisplayLabel(ISynchronizeModelElement node) { if (node == null) { return "ROOT"; //$NON-NLS-1$ } if (node.getResource() != null) { return node.getResource().getFullPath().toString(); } return node.getName(); } private String getDebugDisplayLabel(AbstractSynchronizeModelProvider provider2) { return provider2.toString(); } /* (non-Javadoc) * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoSetReset(org.eclipse.team.core.synchronize.SyncInfoSet, org.eclipse.core.runtime.IProgressMonitor) */ public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) { if (provider.isDisposed()) { set.removeSyncSetChangedListener(this); } else { reset(); } } /* (non-Javadoc) * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoChanged(org.eclipse.team.core.synchronize.ISyncInfoSetChangeEvent, org.eclipse.core.runtime.IProgressMonitor) */ public void syncInfoChanged(final ISyncInfoSetChangeEvent event, IProgressMonitor monitor) { if (!(event instanceof ISyncInfoTreeChangeEvent)) { reset(); } else { queueEvent(new SyncInfoSetChangeEvent(event), false); } } /* * Handle the sync info set change event in the UI thread. */ private void handleChanges(final ISyncInfoSetChangeEvent event, final IProgressMonitor monitor) { runViewUpdate( new Runnable() { public void run() { provider.handleChanges((ISyncInfoTreeChangeEvent) event, monitor); firePendingLabelUpdates(); } }, true /* preserve expansion */); } /* (non-Javadoc) * @see org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener#syncInfoSetErrors(org.eclipse.team.core.synchronize.SyncInfoSet, org.eclipse.team.core.ITeamStatus[], org.eclipse.core.runtime.IProgressMonitor) */ public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) { // When errors occur we currently don't process them. It may be possible to decorate // elements in the model with errors, but currently we prefer to let ignore and except // another listener to display them. } public ISynchronizeModelProvider getProvider() { return provider; } public void connect(IProgressMonitor monitor) { getProvider().getSyncInfoSet().connect(this, monitor); } public void runViewUpdate(final Runnable runnable, final boolean preserveExpansion) { if (Utils.canUpdateViewer(getViewer()) || isPerformingBackgroundUpdate()) { internalRunViewUpdate(runnable, preserveExpansion); } else { if (Thread.currentThread() != getEventHandlerJob().getThread()) { // Run view update should only be called from the UI thread or // the update handler thread. // We will log the problem for now and make it an assert later TeamUIPlugin.log( IStatus.WARNING, "View update invoked from invalid thread", new TeamException( "View update invoked from invalid thread")); //$NON-NLS-1$ //$NON-NLS-2$ } final Control ctrl = getViewer().getControl(); if (ctrl != null && !ctrl.isDisposed()) { ctrl.getDisplay() .syncExec( new Runnable() { public void run() { if (!ctrl.isDisposed()) { BusyIndicator.showWhile( ctrl.getDisplay(), new Runnable() { public void run() { internalRunViewUpdate(runnable, preserveExpansion); } }); } } }); } } } /* * Return whether the event handler is performing a background view update. * In other words, a client has invoked <code>performUpdate</code>. */ public boolean isPerformingBackgroundUpdate() { return Thread.currentThread() == getEventHandlerJob().getThread() && performingBackgroundUpdate; } /* * Method that can be called from the UI thread to update the view model. */ private void internalRunViewUpdate(final Runnable runnable, boolean preserveExpansion) { StructuredViewer viewer = getViewer(); IResource[] expanded = null; IResource[] selected = null; try { if (Utils.canUpdateViewer(viewer)) { viewer.getControl().setRedraw(false); if (preserveExpansion) { expanded = provider.getExpandedResources(); selected = provider.getSelectedResources(); } if (viewer instanceof AbstractTreeViewer && additionsMap == null) additionsMap = new HashMap(); } runnable.run(); } finally { if (Utils.canUpdateViewer(viewer)) { try { if (additionsMap != null && !additionsMap.isEmpty() && Utils.canUpdateViewer(viewer)) { for (Iterator iter = additionsMap.keySet().iterator(); iter.hasNext(); ) { ISynchronizeModelElement parent = (ISynchronizeModelElement) iter.next(); if (Policy.DEBUG_SYNC_MODELS) { System.out.println("Adding child view items of " + parent.getName()); // $NON-NLS-1$ } Set toAdd = (Set) additionsMap.get(parent); ((AbstractTreeViewer) viewer).add(parent, toAdd.toArray(new Object[toAdd.size()])); } additionsMap = null; } if (expanded != null) { provider.expandResources(expanded); } if (selected != null) { provider.selectResources(selected); } } finally { viewer.getControl().setRedraw(true); } } } ISynchronizeModelElement root = provider.getModelRoot(); if (root instanceof SynchronizeModelElement) ((SynchronizeModelElement) root).fireChanges(); } /** * Execute a runnable which performs an update of the model being displayed by the handler's * provider. The runnable should be executed in a thread-safe manner which esults in the view * being updated. * * @param runnable the runnable which updates the model. * @param preserveExpansion whether the expansion of the view should be preserver * @param updateInUIThread if <code>true</code>, the model will be updated in the UI thread. * Otherwise, the model will be updated in the handler thread and the view updated in the UI * thread at the end. */ public void performUpdate( final IWorkspaceRunnable runnable, boolean preserveExpansion, boolean updateInUIThread) { if (updateInUIThread) { queueEvent( new BackgroundEventHandler.RunnableEvent( getUIUpdateRunnable(runnable, preserveExpansion), true), true); } else { queueEvent( new BackgroundEventHandler.RunnableEvent( getBackgroundUpdateRunnable(runnable, preserveExpansion), true), true); } } /** Wrap the runnable in an outer runnable that preserves expansion. */ private IWorkspaceRunnable getUIUpdateRunnable( final IWorkspaceRunnable runnable, final boolean preserveExpansion) { return new IWorkspaceRunnable() { public void run(final IProgressMonitor monitor) throws CoreException { final CoreException[] exception = new CoreException[] {null}; runViewUpdate( new Runnable() { public void run() { try { runnable.run(monitor); } catch (CoreException e) { exception[0] = e; } } }, true /* preserve expansion */); if (exception[0] != null) throw exception[0]; } }; } /* * Wrap the runnable in an outer runnable that preserves expansion if requested * and refreshes the view when the update is completed. */ private IWorkspaceRunnable getBackgroundUpdateRunnable( final IWorkspaceRunnable runnable, final boolean preserveExpansion) { return new IWorkspaceRunnable() { IResource[] expanded; IResource[] selected; public void run(IProgressMonitor monitor) throws CoreException { if (preserveExpansion) recordExpandedResources(); try { performingBackgroundUpdate = true; runnable.run(monitor); } finally { performingBackgroundUpdate = false; } updateView(); } private void recordExpandedResources() { final StructuredViewer viewer = getViewer(); if (viewer != null && !viewer.getControl().isDisposed() && viewer instanceof AbstractTreeViewer) { viewer .getControl() .getDisplay() .syncExec( new Runnable() { public void run() { if (viewer != null && !viewer.getControl().isDisposed()) { expanded = provider.getExpandedResources(); selected = provider.getSelectedResources(); } } }); } } private void updateView() { // Refresh the view and then set the expansion runViewUpdate( new Runnable() { public void run() { provider.getViewer().refresh(); if (expanded != null) provider.expandResources(expanded); if (selected != null) provider.selectResources(selected); } }, false /* do not preserve expansion (since it is done above) */); } }; } /* * Execute the RunnableEvent */ private void executeRunnable(Event event, IProgressMonitor monitor) { try { // Dispatch any queued results to clear pending output events dispatchEvents(Policy.subMonitorFor(monitor, 1)); } catch (TeamException e) { handleException(e); } try { ((RunnableEvent) event).run(Policy.subMonitorFor(monitor, 1)); } catch (CoreException e) { handleException(e); } } /** * Add the element to the viewer. * * @param parent the parent of the element which is already added to the viewer * @param element the element to be added to the viewer */ protected void doAdd(ISynchronizeModelElement parent, ISynchronizeModelElement element) { if (additionsMap == null) { if (Policy.DEBUG_SYNC_MODELS) { System.out.println("Added view item " + element.getName()); // $NON-NLS-1$ } AbstractTreeViewer viewer = (AbstractTreeViewer) getViewer(); viewer.add(parent, element); } else { // Accumulate the additions if (Policy.DEBUG_SYNC_MODELS) { System.out.println("Queueing view item for addition " + element.getName()); // $NON-NLS-1$ } Set toAdd = (Set) additionsMap.get(parent); if (toAdd == null) { toAdd = new HashSet(); additionsMap.put(parent, toAdd); } toAdd.add(element); } } }