Java tutorial
/******************************************************************************* * Copyright (c) 2000, 2014 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.internal.ui.synchronize; import com.ibm.icu.text.DateFormat; import com.ibm.icu.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; 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.*; import org.eclipse.core.runtime.*; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.widgets.Control; import org.eclipse.team.core.ITeamStatus; import org.eclipse.team.core.TeamException; import org.eclipse.team.core.synchronize.*; import org.eclipse.team.internal.core.BackgroundEventHandler; import org.eclipse.team.internal.ui.*; import org.eclipse.team.ui.synchronize.ISynchronizeModelElement; /** * 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) + " -> " //$NON-NLS-1$//$NON-NLS-2$ + getDebugDisplayLabel((ISynchronizeModelElement) element.getParent()) + " : " //$NON-NLS-1$ + getDebugDisplayLabel(provider)); } } /** * 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) + " -> " //$NON-NLS-1$//$NON-NLS-2$ + getDebugDisplayLabel((ISynchronizeModelElement) element.getParent()) + " : " //$NON-NLS-1$ + getDebugDisplayLabel(provider)); } } /** * 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", //$NON-NLS-1$ new TeamException("View update invoked from invalid thread")); //$NON-NLS-1$ } 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); } } }