| /******************************************************************************* |
| * Copyright (c) 2000, 2018 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.internal.ui.synchronize; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| import org.eclipse.compare.structuremergeviewer.IDiffElement; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IMarker; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceStatus; |
| import org.eclipse.core.resources.IWorkspaceRunnable; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| 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.Path; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.viewers.AbstractTreeViewer; |
| import org.eclipse.jface.viewers.CheckboxTreeViewer; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.StructuredViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.events.TreeEvent; |
| import org.eclipse.swt.events.TreeListener; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.team.core.ITeamStatus; |
| import org.eclipse.team.core.synchronize.ISyncInfoSetChangeEvent; |
| import org.eclipse.team.core.synchronize.ISyncInfoSetChangeListener; |
| import org.eclipse.team.core.synchronize.ISyncInfoTreeChangeEvent; |
| import org.eclipse.team.core.synchronize.SyncInfo; |
| import org.eclipse.team.core.synchronize.SyncInfoSet; |
| import org.eclipse.team.internal.core.TeamPlugin; |
| import org.eclipse.team.internal.ui.Policy; |
| import org.eclipse.team.internal.ui.TeamUIPlugin; |
| import org.eclipse.team.internal.ui.Utils; |
| import org.eclipse.team.ui.synchronize.ISynchronizeModelElement; |
| import org.eclipse.team.ui.synchronize.ISynchronizePage; |
| import org.eclipse.team.ui.synchronize.ISynchronizePageConfiguration; |
| import org.eclipse.team.ui.synchronize.SynchronizePageActionGroup; |
| |
| /** |
| * This class is responsible for creating and maintaining a presentation model of |
| * {@link SynchronizeModelElement} elements that can be shown in a viewer. The model |
| * is based on the synchronization information contained in the provided {@link SyncInfoSet}. |
| */ |
| public abstract class AbstractSynchronizeModelProvider implements ISynchronizeModelProvider, ISyncInfoSetChangeListener, TreeListener { |
| |
| /** |
| * Property constant for the expansion state for the elements displayed by the page. The |
| * expansion state is a List of resource paths. |
| */ |
| public static final String P_VIEWER_EXPANSION_STATE = TeamUIPlugin.ID + ".P_VIEWER_EXPANSION_STATE"; //$NON-NLS-1$ |
| |
| /** |
| * Property constant for the selection state for the elements displayed by the page. The |
| * selection state is a List of resource paths. |
| */ |
| public static final String P_VIEWER_SELECTION_STATE = TeamUIPlugin.ID + ".P_VIEWER_SELECTION_STATE"; //$NON-NLS-1$ |
| |
| /** |
| * Property constant for the checked state for the elements displayed by the page. The |
| * checked state is a List of resource paths. |
| */ |
| public static final String P_VIEWER_CHECKED_STATE = TeamUIPlugin.ID + ".P_VIEWER_CHECKED_STATE"; //$NON-NLS-1$ |
| |
| private ISynchronizeModelElement root; |
| |
| private ISynchronizePageConfiguration configuration; |
| |
| private SyncInfoSet set; |
| |
| private SynchronizeModelUpdateHandler updateHandler; |
| |
| private boolean disposed = false; |
| |
| private SynchronizePageActionGroup actionGroup; |
| |
| private ListenerList<IPropertyChangeListener> listeners; |
| |
| private static final boolean DEBUG = false; |
| |
| /** |
| * Constructor for creating a sub-provider |
| * @param parentProvider the parent provider |
| * @param parentNode the root node of the model built by this provider |
| * @param configuration the sync page configuration |
| * @param set the sync info set from which the model is built |
| */ |
| protected AbstractSynchronizeModelProvider(AbstractSynchronizeModelProvider parentProvider, ISynchronizeModelElement parentNode, ISynchronizePageConfiguration configuration, SyncInfoSet set) { |
| Assert.isNotNull(set); |
| Assert.isNotNull(parentNode); |
| this.root = parentNode; |
| this.set = set; |
| this.configuration = configuration; |
| if (parentProvider == null) { |
| // The update handler will register for sync change events |
| // with the sync set when the handler is activated |
| updateHandler = new SynchronizeModelUpdateHandler(this); |
| getTree().addTreeListener(this); |
| } else { |
| // We will use the parent's update handler and register for changes with the given set |
| updateHandler = parentProvider.updateHandler; |
| set.addSyncSetChangedListener(this); |
| } |
| } |
| |
| private Tree getTree() { |
| return ((Tree)((AbstractTreeViewer)getViewer()).getControl()); |
| } |
| |
| /** |
| * Constructor for creating a root model provider. |
| * @param configuration the sync page configuration |
| * @param set the sync info set from which the model is built |
| */ |
| protected AbstractSynchronizeModelProvider(ISynchronizePageConfiguration configuration, SyncInfoSet set) { |
| this(null, new UnchangedResourceModelElement(null, ResourcesPlugin.getWorkspace().getRoot()) { |
| /* |
| * Override to ensure that the diff viewer will appear in CompareEditorInputs |
| */ |
| @Override |
| public boolean hasChildren() { |
| return true; |
| } |
| }, configuration, set); |
| // Register the action group for this provider, since it is the root provider |
| SynchronizePageActionGroup actionGroup = getActionGroup(); |
| if (actionGroup != null) { |
| configuration.addActionContribution(actionGroup); |
| } |
| } |
| |
| /** |
| * Return the action group for this provider or <code>null</code> |
| * if there are no actions associated with this provider. The action |
| * group will be registered with the configuration if this is |
| * the root provider. If this provider is a sub-provider, it |
| * is up to the parent provider to register the action group. |
| * <p> |
| * The action group for a provider is created by calling the |
| * <code>createdActionGroup</code> method. If this method returns |
| * a non-null group, it is cached so it can be disposed |
| * when the provider is disposed. |
| * @return the action group for this provider or <code>null</code> |
| * if there are no actions associated with this provider |
| */ |
| public final synchronized SynchronizePageActionGroup getActionGroup() { |
| if (actionGroup == null) { |
| actionGroup = createActionGroup(); |
| } |
| return actionGroup; |
| } |
| |
| /** |
| * Create the action group for this provider. By default, |
| * a <code>null</code> is returned. Subclasses may override. |
| * @return the action group for this provider or <code>null</code> |
| */ |
| protected SynchronizePageActionGroup createActionGroup() { |
| return null; |
| } |
| |
| /** |
| * Return the set that contains the elements this provider is using as |
| * a basis for creating a presentation model. This cannot be null. |
| * |
| * @return the set that contains the elements this provider is |
| * using as a basis for creating a presentation model. |
| */ |
| @Override |
| public SyncInfoSet getSyncInfoSet() { |
| return set; |
| } |
| |
| /** |
| * Returns the input created by this provider or <code>null</code> if |
| * {@link #prepareInput(IProgressMonitor)} hasn't been called on this object yet. |
| * |
| * @return the input created by this provider. |
| */ |
| @Override |
| public ISynchronizeModelElement getModelRoot() { |
| return root; |
| } |
| |
| /** |
| * Return the page configuration for this provider. |
| * |
| * @return the page configuration for this provider. |
| */ |
| public ISynchronizePageConfiguration getConfiguration() { |
| return configuration; |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| public StructuredViewer getViewer() { |
| ISynchronizePage page = configuration.getPage(); |
| if (page == null) return null; |
| Viewer viewer = page.getViewer(); |
| if (viewer instanceof AbstractTreeViewer) { |
| return (AbstractTreeViewer)viewer; |
| } |
| return null; |
| } |
| |
| /** |
| * Builds the viewer model based on the contents of the sync set. |
| */ |
| @Override |
| public ISynchronizeModelElement prepareInput(IProgressMonitor monitor) { |
| // Connect to the sync set which will register us as a listener and give us a reset event |
| // in a background thread |
| if (isRootProvider()) { |
| updateHandler.connect(monitor); |
| } else { |
| getSyncInfoSet().connect(this, monitor); |
| } |
| return getModelRoot(); |
| } |
| |
| /** |
| * Calculate the problem marker that should be shown on the given |
| * element. The returned property can be either |
| * ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY or |
| * ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY. |
| * @param element a synchronize model element |
| * @return the marker property that should be displayed on the element |
| * or <code>null</code> if no marker should be displayed |
| */ |
| public String calculateProblemMarker(ISynchronizeModelElement element) { |
| IResource resource = element.getResource(); |
| String property = null; |
| if (resource != null && resource.exists()) { |
| try { |
| IMarker[] markers = resource.findMarkers(IMarker.PROBLEM, true, getLogicalModelDepth(resource)); |
| for (int i = 0; i < markers.length; i++) { |
| IMarker marker = markers[i]; |
| try { |
| Integer severity = (Integer) marker.getAttribute(IMarker.SEVERITY); |
| if (severity != null) { |
| if (severity.intValue() == IMarker.SEVERITY_ERROR) { |
| property = ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY; |
| break; |
| } else if (severity.intValue() == IMarker.SEVERITY_WARNING) { |
| property = ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY; |
| // Keep going because there may be errors on other resources |
| } |
| } |
| } catch (CoreException e) { |
| if (!resource.exists()) { |
| // The resource was deleted concurrently. Forget any previously found property |
| property = null; |
| break; |
| } |
| // If the marker exists, log the exception and continue. |
| // Otherwise, just ignore the exception and keep going |
| if (marker.exists()) { |
| TeamPlugin.log(e); |
| } |
| } |
| } |
| } catch (CoreException e) { |
| // If the resource exists (is accessible), log the exception and continue. |
| // Otherwise, just ignore the exception |
| if (resource.isAccessible() |
| && e.getStatus().getCode() != IResourceStatus.RESOURCE_NOT_FOUND |
| && e.getStatus().getCode() != IResourceStatus.PROJECT_NOT_OPEN) { |
| TeamPlugin.log(e); |
| } |
| } |
| } else if (resource == null) { |
| // For non-resource elements, show the same propogaqted marker as the children |
| IDiffElement[] children = element.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| IDiffElement child = children[i]; |
| if (child instanceof ISynchronizeModelElement) { |
| ISynchronizeModelElement childElement = (ISynchronizeModelElement)child; |
| if (childElement.getProperty(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY)) { |
| property = ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY; |
| break; |
| } else if (childElement.getProperty(ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY)) { |
| property = ISynchronizeModelElement.PROPAGATED_WARNING_MARKER_PROPERTY; |
| // Keep going because there may be errors on other resources |
| } |
| |
| } |
| } |
| } |
| return property; |
| } |
| |
| /** |
| * Return the logical model depth used for marker propagation |
| * @param resource the resource |
| * @return the depth the resources should be traversed |
| */ |
| protected int getLogicalModelDepth(IResource resource) { |
| return IResource.DEPTH_INFINITE; |
| } |
| |
| /** |
| * Update the label of the given diff node. The label for nodes queued |
| * using this method will not be updated until <code>firePendingLabelUpdates</code> |
| * is called. |
| * @param diffNode the diff node to be updated |
| */ |
| protected void queueForLabelUpdate(ISynchronizeModelElement diffNode) { |
| updateHandler.queueForLabelUpdate(diffNode); |
| } |
| |
| /** |
| * Throw away any old state associated with this provider and |
| * rebuild the model from scratch. |
| */ |
| protected void reset() { |
| // save expansion state |
| if(isRootProvider() && hasViewerState()) { |
| saveViewerState(); |
| } |
| |
| // Clear existing model, but keep the root node |
| clearModelObjects(getModelRoot()); |
| |
| // Rebuild the model |
| buildModelObjects(getModelRoot()); |
| |
| // Notify listeners that model has changed |
| ISynchronizeModelElement root = getModelRoot(); |
| if(root instanceof SynchronizeModelElement) { |
| ((SynchronizeModelElement)root).fireChanges(); |
| } |
| |
| if (Utils.canUpdateViewer(getViewer())) { |
| // If we can update the viewer, that means that the view was updated |
| // when the model was rebuilt. |
| refreshModelRoot(); |
| } else { |
| // Only refresh the view if there is now background update in |
| // progress. If there is, the background update will refresh |
| if (!updateHandler.isPerformingBackgroundUpdate()) { |
| Utils.asyncExec((Runnable) () -> refreshModelRoot(), getViewer()); |
| } |
| } |
| } |
| |
| private void refreshModelRoot() { |
| StructuredViewer viewer = getViewer(); |
| if (viewer != null && !viewer.getControl().isDisposed()) { |
| try { |
| viewer.getControl().setRedraw(false); |
| if (isRootProvider() || getModelRoot().getParent() == null) { |
| // Refresh the entire view |
| viewer.refresh(); |
| } else { |
| // Only refresh the model root bu also ensure that |
| // the parents of the model root and the model root |
| // itself are added to the view |
| addToViewer(getModelRoot()); |
| } |
| // restore expansion state |
| if (isRootProvider()) |
| restoreViewerState(); |
| } finally { |
| viewer.getControl().setRedraw(true); |
| } |
| } |
| } |
| |
| /** |
| * For each node create children based on the contents of |
| * @param node |
| * @return the diff elements |
| */ |
| protected abstract IDiffElement[] buildModelObjects(ISynchronizeModelElement node); |
| |
| /** |
| * Returns whether the viewer has state to be saved. |
| * @return whether the viewer has state to be saved |
| */ |
| protected abstract boolean hasViewerState(); |
| |
| /* |
| * Return all the resources that are expanded in the page. |
| * This method should only be called in the UI thread |
| * after validating that the viewer is still valid. |
| */ |
| protected IResource[] getExpandedResources() { |
| Set<IResource> expanded = new HashSet<>(); |
| IResource[] savedExpansionState = getCachedResources(P_VIEWER_EXPANSION_STATE); |
| for (int i = 0; i < savedExpansionState.length; i++) { |
| IResource resource = savedExpansionState[i]; |
| expanded.add(resource); |
| } |
| StructuredViewer viewer = getViewer(); |
| Object[] objects = ((AbstractTreeViewer) viewer).getVisibleExpandedElements(); |
| IResource[] currentExpansionState = getResources(objects); |
| for (int i = 0; i < currentExpansionState.length; i++) { |
| IResource resource = currentExpansionState[i]; |
| expanded.add(resource); |
| } |
| return expanded.toArray(new IResource[expanded.size()]); |
| } |
| |
| /* |
| * Return all the resources that are selected in the page. |
| * This method should only be called in the UI thread |
| * after validating that the viewer is still valid. |
| */ |
| protected IResource[] getSelectedResources() { |
| StructuredViewer viewer = getViewer(); |
| return getResources(viewer.getStructuredSelection().toArray()); |
| } |
| |
| /* |
| * Return all the resources that are checked in the page. |
| * This method should only be called in the UI thread |
| * after validating that the viewer is still valid. |
| */ |
| protected IResource[] getCheckedResources() { |
| StructuredViewer viewer = getViewer(); |
| if (viewer instanceof CheckboxTreeViewer){ |
| return getResources(((CheckboxTreeViewer)viewer).getCheckedElements()); |
| } |
| |
| return new IResource[0]; |
| } |
| |
| /* |
| * Expand the resources if they appear in the page. |
| * This method should only be called in the UI thread |
| * after validating that the viewer is still valid. |
| */ |
| protected void expandResources(IResource[] resources) { |
| Set<ISynchronizeModelElement> expandedElements = new HashSet<>(); |
| StructuredViewer viewer = getViewer(); |
| for (int j = 0; j < resources.length; j++) { |
| IResource resource = resources[j]; |
| ISynchronizeModelElement[] elements = getModelObjects(resource); |
| // Only expand when there is one element per resource |
| if (elements.length == 1) { |
| for (int i = 0; i < elements.length; i++) { |
| ISynchronizeModelElement element = elements[i]; |
| // Add all parents of the element to the expansion set |
| while (element != null) { |
| expandedElements.add(element); |
| element = (ISynchronizeModelElement)element.getParent(); |
| } |
| } |
| } |
| } |
| if (!expandedElements.isEmpty()) |
| ((AbstractTreeViewer) viewer).setExpandedElements(expandedElements.toArray()); |
| } |
| |
| protected IResource[] getResources(Object[] objects) { |
| Set<IResource> result = new HashSet<>(); |
| if (objects.length > 0) { |
| for (int i = 0; i < objects.length; i++) { |
| if (objects[i] instanceof ISynchronizeModelElement) { |
| IResource resource = ((ISynchronizeModelElement)objects[i]).getResource(); |
| if(resource != null) |
| result.add(resource); |
| } |
| } |
| } |
| return result.toArray(new IResource[result.size()]); |
| } |
| |
| private void clearResourceCache(String configProperty) { |
| getConfiguration().setProperty(configProperty, null); |
| } |
| |
| private void cacheResources(IResource[] resources, String configProperty) { |
| if (resources.length > 0) { |
| ISynchronizePageConfiguration config = getConfiguration(); |
| ArrayList<String> paths = new ArrayList<>(); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| String path = resource.getFullPath().toString(); |
| if (resource.getType() != IResource.FILE && path.charAt(path.length() - 1) != IPath.SEPARATOR) { |
| // Include a trailing slash on folders and projects. |
| // It is used when recreating cached resources that don't exist locally |
| path += IPath.SEPARATOR; |
| } |
| paths.add(path); |
| } |
| config.setProperty(configProperty, paths); |
| } else { |
| clearResourceCache(configProperty); |
| } |
| } |
| |
| private IResource[] getCachedResources(String configProperty) { |
| List paths = (List)getConfiguration().getProperty(configProperty); |
| if (paths == null) |
| return new IResource[0]; |
| IContainer container = ResourcesPlugin.getWorkspace().getRoot(); |
| ArrayList<IResource> resources = new ArrayList<>(); |
| for (Iterator it = paths.iterator(); it.hasNext();) { |
| String path = (String) it.next(); |
| IResource resource = getResourceForPath(container, path); |
| if (resource != null) { |
| resources.add(resource); |
| } |
| } |
| return resources.toArray(new IResource[resources.size()]); |
| } |
| |
| /** |
| * Save the viewer state (expansion and selection) |
| */ |
| protected void saveViewerState() { |
| // save visible expanded elements and selection |
| final StructuredViewer viewer = getViewer(); |
| if (viewer != null && !viewer.getControl().isDisposed() && viewer instanceof AbstractTreeViewer) { |
| //check to see if we should store the checked states of the tree |
| |
| final boolean storeChecks = ((SynchronizePageConfiguration)configuration).getViewerStyle() == SynchronizePageConfiguration.CHECKBOX; |
| final IResource[][] expandedResources = new IResource[1][0]; |
| final IResource[][] selectedResources = new IResource[1][0]; |
| final IResource[][] checkedResources = new IResource[1][0]; |
| viewer.getControl().getDisplay().syncExec(() -> { |
| if (viewer != null && !viewer.getControl().isDisposed()) { |
| expandedResources[0] = getExpandedResources(); |
| selectedResources[0] = getSelectedResources(); |
| if (storeChecks) |
| checkedResources [0] = getCheckedResources(); |
| } |
| }); |
| |
| // Save expansion and selection |
| cacheResources(expandedResources[0], P_VIEWER_EXPANSION_STATE); |
| cacheResources(selectedResources[0], P_VIEWER_SELECTION_STATE); |
| if (storeChecks) |
| cacheResources(checkedResources[0], P_VIEWER_CHECKED_STATE); |
| } |
| } |
| |
| /** |
| * Restore the expansion state and selection of the viewer. |
| * This method must be invoked from within the UI thread. |
| */ |
| protected void restoreViewerState() { |
| // restore expansion state and selection state |
| final StructuredViewer viewer = getViewer(); |
| if (viewer != null && !viewer.getControl().isDisposed() && viewer instanceof AbstractTreeViewer) { |
| IResource[] resourcesToExpand = getCachedResources(P_VIEWER_EXPANSION_STATE); |
| IResource[] resourcesToSelect = getCachedResources(P_VIEWER_SELECTION_STATE); |
| if (((SynchronizePageConfiguration)configuration).getViewerStyle() == SynchronizePageConfiguration.CHECKBOX){ |
| IResource[] resourcesToCheck = getCachedResources(P_VIEWER_CHECKED_STATE); |
| checkResources(resourcesToCheck); |
| } |
| expandResources(resourcesToExpand); |
| selectResources(resourcesToSelect); |
| } |
| } |
| |
| /* |
| * Select the given resources in the view. This method can |
| * only be invoked from the UI thread. |
| */ |
| protected void selectResources(IResource[] resourcesToSelect) { |
| StructuredViewer viewer = getViewer(); |
| final ArrayList<ISynchronizeModelElement> selectedElements = new ArrayList<>(); |
| for (int i = 0; i < resourcesToSelect.length; i++) { |
| IResource resource = resourcesToSelect[i]; |
| ISynchronizeModelElement[] elements = getModelObjects(resource); |
| // Only preserve the selection if there is one element for the resource |
| if (elements.length == 1) { |
| selectedElements.add(elements[0]); |
| } |
| } |
| if (!selectedElements.isEmpty()) |
| viewer.setSelection(new StructuredSelection(selectedElements)); |
| } |
| |
| /* |
| * Check the given resources in the view. This method can |
| * only be invoked from the UI thread. |
| */ |
| protected void checkResources(IResource[] resourcesToCheck) { |
| Set<ISynchronizeModelElement> checkedElements = new HashSet<>(); |
| StructuredViewer viewer = getViewer(); |
| if (!(viewer instanceof CheckboxTreeViewer)) |
| return; |
| |
| for (int j = 0; j < resourcesToCheck.length; j++) { |
| IResource resource = resourcesToCheck[j]; |
| if (resource.getType() != IResource.FILE) |
| continue; |
| |
| ISynchronizeModelElement[] elements = getModelObjects(resource); |
| // Only expand when there is one element per resource |
| if (elements.length == 1) { |
| for (int i = 0; i < elements.length; i++) { |
| ISynchronizeModelElement element = elements[i]; |
| checkedElements.add(element); |
| } |
| } |
| } |
| if (!checkedElements.isEmpty()) |
| ((CheckboxTreeViewer) viewer).setCheckedElements(checkedElements.toArray()); |
| } |
| |
| /* |
| * Convert a path to a resource by first looking in the resource |
| * tree and, if that fails, by using the path format to create |
| * a handle. |
| */ |
| private IResource getResourceForPath(IContainer container, String path) { |
| IResource resource = container.findMember(path, true /* include phantoms */); |
| if (resource == null) { |
| try { |
| // The resource doesn't have an entry on the resources tree |
| // but may still appear in the view so try to deduce the type |
| // from the path |
| if (path.endsWith(Character.toString(IPath.SEPARATOR))) { |
| resource = container.getFolder(new Path(null, path)); |
| } else { |
| resource = container.getFile(new Path(null, path)); |
| } |
| } catch (IllegalArgumentException e) { |
| // Couldn't get a resource handle so ignore |
| } |
| } |
| return resource; |
| } |
| |
| @Override |
| public void treeCollapsed(TreeEvent e) { |
| clearResourceCache(P_VIEWER_EXPANSION_STATE); |
| } |
| |
| @Override |
| public void treeExpanded(TreeEvent e) { |
| clearResourceCache(P_VIEWER_EXPANSION_STATE); |
| } |
| |
| /** |
| * Return all the model objects in this provider that represent the given resource |
| * @param resource the resource |
| * @return the model objects for the resource |
| */ |
| protected abstract ISynchronizeModelElement[] getModelObjects(IResource resource); |
| |
| @Override |
| public void saveState() { |
| saveViewerState(); |
| } |
| |
| /** |
| * Method invoked when a sync element is added or removed or its state changes. |
| * This method can be invoked from the UI thread or a background thread. |
| * @param element synchronize element |
| * @param clear <code>true</code> if the conflict bit of the element was cleared |
| * (i.e. the element has been deleted) |
| */ |
| protected void propogateConflictState(ISynchronizeModelElement element, boolean clear) { |
| boolean isConflict = clear ? false : isConflicting(element); |
| boolean wasConflict = element.getProperty(ISynchronizeModelElement.PROPAGATED_CONFLICT_PROPERTY); |
| // Only propagate and update parent labels if the state of the element has changed |
| if (isConflict != wasConflict) { |
| element.setPropertyToRoot(ISynchronizeModelElement.PROPAGATED_CONFLICT_PROPERTY, isConflict); |
| updateHandler.updateParentLabels(element); |
| } |
| } |
| |
| /** |
| * Return whether the given model element represents a conflict. |
| * @param element the element being tested |
| * @return whether the element is a conflict |
| */ |
| protected boolean isConflicting(ISynchronizeModelElement element) { |
| return (element.getKind() & SyncInfo.DIRECTION_MASK) == SyncInfo.CONFLICTING; |
| } |
| |
| /** |
| * Dispose of the provider |
| */ |
| @Override |
| public void dispose() { |
| // Only dispose the update handler if it is |
| // directly associated with this provider |
| if (isRootProvider()) { |
| updateHandler.dispose(); |
| getTree().removeTreeListener(this); |
| } else { |
| set.removeSyncSetChangedListener(this); |
| } |
| if (actionGroup != null) { |
| Utils.syncExec((Runnable) () -> actionGroup.dispose(), getViewer()); |
| } |
| this.disposed = true; |
| } |
| |
| private boolean isRootProvider() { |
| return updateHandler.getProvider() == this; |
| } |
| |
| /** |
| * Return whether this provide has been disposed. |
| * @return whether this provide has been disposed |
| */ |
| public boolean isDisposed() { |
| return disposed; |
| } |
| |
| /** |
| * Return the closest parent elements that represents a model element that |
| * could contains the given resource. Multiple elements need only be returned |
| * if two or more logical views are being shown and each view has an element |
| * that could contain the resource. |
| * @param resource the resource |
| * @return one or more lowest level parents that could contain the resource |
| */ |
| public abstract ISynchronizeModelElement[] getClosestExistingParents(IResource resource); |
| |
| /** |
| * Handle the changes made to the viewer's <code>SyncInfoSet</code>. |
| * This method delegates the changes to the three methods <code>handleResourceChanges(ISyncInfoSetChangeEvent)</code>, |
| * <code>handleResourceRemovals(ISyncInfoSetChangeEvent)</code> and |
| * <code>handleResourceAdditions(ISyncInfoSetChangeEvent)</code>. |
| * @param event |
| * the event containing the changed resources. |
| */ |
| protected void handleChanges(ISyncInfoTreeChangeEvent event, IProgressMonitor monitor) { |
| handleResourceChanges(event); |
| handleResourceRemovals(event); |
| handleResourceAdditions(event); |
| } |
| |
| /** |
| * Update the viewer for the sync set additions in the provided event. This |
| * method is invoked by <code>handleChanges(ISyncInfoSetChangeEvent)</code>. |
| * Subclasses may override. |
| * @param event |
| */ |
| protected abstract void handleResourceAdditions(ISyncInfoTreeChangeEvent event); |
| |
| /** |
| * Update the viewer for the sync set changes in the provided event. This |
| * method is invoked by <code>handleChanges(ISyncInfoSetChangeEvent)</code>. |
| * Subclasses may override. |
| * @param event |
| */ |
| protected abstract void handleResourceChanges(ISyncInfoTreeChangeEvent event); |
| |
| /** |
| * Update the viewer for the sync set removals in the provided event. This |
| * method is invoked by <code>handleChanges(ISyncInfoSetChangeEvent)</code>. |
| * Subclasses may override. |
| * @param event |
| */ |
| protected abstract void handleResourceRemovals(ISyncInfoTreeChangeEvent event); |
| |
| @Override |
| public void syncInfoChanged(final ISyncInfoSetChangeEvent event, final IProgressMonitor monitor) { |
| if (! (event instanceof ISyncInfoTreeChangeEvent)) { |
| reset(); |
| } else { |
| updateHandler.runViewUpdate(() -> handleChanges((ISyncInfoTreeChangeEvent)event, monitor), true /* preserve expansion */); |
| } |
| } |
| |
| @Override |
| public void syncInfoSetErrors(SyncInfoSet set, ITeamStatus[] errors, IProgressMonitor monitor) { |
| // Not handled |
| |
| } |
| |
| @Override |
| public void syncInfoSetReset(SyncInfoSet set, IProgressMonitor monitor) { |
| reset(); |
| } |
| |
| protected void addToViewer(ISynchronizeModelElement node) { |
| if (DEBUG) { |
| System.out.println("Adding model element " + node.getName()); //$NON-NLS-1$ |
| } |
| propogateConflictState(node, false); |
| // Set the marker property on this node. |
| // There is no need to propagate this to the parents |
| // as they will be displaying the proper marker already |
| String property = calculateProblemMarker(node); |
| if (property != null) { |
| node.setProperty(property, true); |
| // Parent resource nodes would have been properly calculated when they were added. |
| // However, non-resource nodes would not so we need to propagate the marker to them |
| propogateMarkerPropertyToParent(node, property); |
| } |
| if (Utils.canUpdateViewer(getViewer())) { |
| doAdd((SynchronizeModelElement)node.getParent(), node); |
| } |
| updateHandler.nodeAdded(node, this); |
| } |
| |
| /* |
| * Propagate the marker property to the parent if it is not already there. |
| * Only propagate warnings if the parent isn't an error already. |
| */ |
| private void propogateMarkerPropertyToParent(ISynchronizeModelElement node, String property) { |
| ISynchronizeModelElement parent = (ISynchronizeModelElement)node.getParent(); |
| if (parent != null |
| && !parent.getProperty(property) |
| && !parent.getProperty(ISynchronizeModelElement.PROPAGATED_ERROR_MARKER_PROPERTY)) { |
| parent.setProperty(property, true); |
| propogateMarkerPropertyToParent(parent, property); |
| } |
| } |
| |
| /** |
| * Remove any traces of the model element and any of it's descendants in the |
| * hierarchy defined by the content provider from the content provider and |
| * the viewer it is associated with. |
| * @param nodes the model elements to remove |
| */ |
| protected void removeFromViewer(ISynchronizeModelElement[] nodes) { |
| List<ISynchronizeModelElement> rootsToClear = new ArrayList<>(); |
| for (int i = 0; i < nodes.length; i++) { |
| ISynchronizeModelElement node = nodes[i]; |
| if (DEBUG) { |
| System.out.println("Removing model element " + node.getName()); //$NON-NLS-1$ |
| } |
| ISynchronizeModelElement rootToClear= getRootToClear(node); |
| if (DEBUG) { |
| if (rootToClear != node) { |
| System.out.println("Removing parent element " + rootToClear.getName()); //$NON-NLS-1$ |
| } |
| } |
| propogateConflictState(rootToClear, true /* clear the conflict */); |
| clearModelObjects(rootToClear); |
| rootsToClear.add(rootToClear); |
| } |
| ISynchronizeModelElement[] roots = rootsToClear.toArray(new ISynchronizeModelElement[rootsToClear.size()]); |
| if (Utils.canUpdateViewer(getViewer())) { |
| doRemove(roots); |
| } |
| for (int i = 0; i < roots.length; i++) { |
| ISynchronizeModelElement element = roots[i]; |
| updateHandler.nodeRemoved(element, this); |
| } |
| } |
| |
| /** |
| * Clear the model objects from the diff tree, cleaning up any cached state |
| * (such as resource to model object map). This method recurses deeply on |
| * the tree to allow the cleanup of any cached state for the children as |
| * well. |
| * @param node the root node |
| */ |
| protected final void clearModelObjects(ISynchronizeModelElement node) { |
| // When clearing model objects, any parents of the node |
| // That are not out-of-sync, not the model root and that would |
| // be empty as a result of this clear, should also be cleared. |
| ISynchronizeModelElement rootToClear = getRootToClear(node); |
| // Recursively clear the nodes from the root |
| recursiveClearModelObjects(rootToClear); |
| if (node == getModelRoot()) { |
| IDiffElement[] children = node.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| IDiffElement element = children[i]; |
| ((SynchronizeModelElement)node).remove(element); |
| } |
| } else { |
| SynchronizeModelElement parent = ((SynchronizeModelElement)node.getParent()); |
| if (parent != null) parent.remove(node); |
| } |
| } |
| |
| /** |
| * Method that subclasses can override when clearing model objects. |
| * @param node the node to be cleared recursively |
| */ |
| protected void recursiveClearModelObjects(ISynchronizeModelElement node) { |
| // Clear all the children of the node |
| IDiffElement[] children = node.getChildren(); |
| for (int i = 0; i < children.length; i++) { |
| IDiffElement element = children[i]; |
| if (element instanceof ISynchronizeModelElement) { |
| ISynchronizeModelElement sme = (ISynchronizeModelElement) element; |
| ISynchronizeModelProvider provider = getProvider(sme); |
| if (provider != null && provider instanceof AbstractSynchronizeModelProvider) { |
| ((AbstractSynchronizeModelProvider)provider).recursiveClearModelObjects(sme); |
| } else { |
| recursiveClearModelObjects(sme); |
| } |
| } |
| } |
| // Notify the update handler that the node has been cleared |
| if (node != getModelRoot()) |
| updateHandler.modelObjectCleared(node); |
| } |
| |
| /* |
| * Remove to root should only remove to the root of the provider and not the |
| * diff tree. |
| */ |
| private ISynchronizeModelElement getRootToClear(ISynchronizeModelElement node) { |
| if (node == getModelRoot()) return node; |
| ISynchronizeModelElement parent = (ISynchronizeModelElement)node.getParent(); |
| if (parent != null && parent != getModelRoot() && !isOutOfSync(parent) && parent.getChildren().length == 1) { |
| return getRootToClear(parent); |
| } |
| return node; |
| } |
| |
| /* |
| * Return whether the node represents an out-of-sync resource. |
| */ |
| protected boolean isOutOfSync(ISynchronizeModelElement node) { |
| SyncInfo info = Utils.getSyncInfo(node); |
| return (info != null && info.getKind() != SyncInfo.IN_SYNC); |
| } |
| |
| protected boolean isOutOfSync(IResource resource) { |
| SyncInfo info = getSyncInfoSet().getSyncInfo(resource); |
| return (info != null && info.getKind() != SyncInfo.IN_SYNC); |
| } |
| |
| /** |
| * Return the provider that created and manages the given |
| * model element. The default is to return the receiver. |
| * Subclasses may override. |
| * @param element the synchronize model element |
| * @return the provider that created the element |
| */ |
| protected ISynchronizeModelProvider getProvider(ISynchronizeModelElement element) { |
| return this; |
| } |
| |
| /** |
| * 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) { |
| updateHandler.doAdd(parent, element); |
| } |
| |
| /** |
| * Remove the element from the viewer |
| * @param elements the elements to be removed |
| */ |
| protected void doRemove(ISynchronizeModelElement[] elements) { |
| AbstractTreeViewer viewer = (AbstractTreeViewer)getViewer(); |
| try { |
| viewer.remove(elements); |
| } catch (SWTException e) { |
| // The remove failed due to an SWT exception. Log it and continue |
| TeamUIPlugin.log(IStatus.ERROR, "An error occurred removing elements from the synchronize view", e); //$NON-NLS-1$ |
| } |
| if (DEBUG) { |
| for (int i = 0; i < elements.length; i++) { |
| ISynchronizeModelElement element = elements[i]; |
| System.out.println("Removing view item " + element.getName()); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * This is a callback from the model update handler that gets invoked |
| * when a node is added to the viewer. It is only invoked for the |
| * root level model provider. |
| * @param node |
| * @param provider the provider that added the node |
| */ |
| protected void nodeAdded(ISynchronizeModelElement node, AbstractSynchronizeModelProvider provider) { |
| // Default is to do nothing |
| } |
| |
| /** |
| * This is a callback from the model update handler that gets invoked |
| * when a node is removed from the viewer. It is only invoked for the |
| * root level model provider. A removed node may have children for |
| * which a <code>nodeRemoved</code> callback is not received (see |
| * <code>modelObjectCleared</code>). |
| * @param node |
| */ |
| protected void nodeRemoved(ISynchronizeModelElement node, AbstractSynchronizeModelProvider provider) { |
| // Default is to do nothing |
| } |
| |
| /** |
| * This is a callback from the model update handler that gets invoked |
| * when a node is cleared from the model. It is only invoked for the |
| * root level model provider. This callback is deep in the sense that |
| * a callback is sent for each node that is cleared. |
| * @param node the node that was cleared. |
| */ |
| public void modelObjectCleared(ISynchronizeModelElement node) { |
| // Default is to do nothing |
| } |
| |
| @Override |
| public void addPropertyChangeListener(IPropertyChangeListener listener) { |
| synchronized (this) { |
| if (listeners == null) { |
| listeners = new ListenerList<>(ListenerList.IDENTITY); |
| } |
| listeners.add(listener); |
| } |
| |
| } |
| @Override |
| public void removePropertyChangeListener(IPropertyChangeListener listener) { |
| if (listeners != null) { |
| synchronized (this) { |
| listeners.remove(listener); |
| if (listeners.isEmpty()) { |
| listeners = null; |
| } |
| } |
| } |
| } |
| |
| protected void firePropertyChange(String key, Object oldValue, Object newValue) { |
| Object[] allListeners; |
| synchronized(this) { |
| allListeners = listeners.getListeners(); |
| } |
| final PropertyChangeEvent event = new PropertyChangeEvent(this, key, oldValue, newValue); |
| for (int i = 0; i < allListeners.length; i++) { |
| final IPropertyChangeListener listener = (IPropertyChangeListener)allListeners[i]; |
| SafeRunner.run(new ISafeRunnable() { |
| @Override |
| public void handleException(Throwable exception) { |
| // Error is logged by platform |
| } |
| @Override |
| public void run() throws Exception { |
| listener.propertyChange(event); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Wait until the provider is done processing any events and |
| * the page content are up-to-date. |
| * This method is for testing purposes only. |
| * @param monitor |
| */ |
| public void waitUntilDone(IProgressMonitor monitor) { |
| monitor.worked(1); |
| // wait for the event handler to process changes. |
| while(updateHandler.getEventHandlerJob().getState() != Job.NONE) { |
| monitor.worked(1); |
| try { |
| Thread.sleep(10); |
| } catch (InterruptedException e) { |
| } |
| Policy.checkCanceled(monitor); |
| } |
| monitor.worked(1); |
| } |
| |
| @Override |
| public String toString() { |
| ISynchronizeModelElement element = getModelRoot(); |
| String name = getClass().getName(); |
| int index = name.lastIndexOf("."); //$NON-NLS-1$ |
| if (index != -1) { |
| name = name.substring(index + 1); |
| } |
| String name2 = element.getName(); |
| if (name2.length() == 0) { |
| name2 = "/"; //$NON-NLS-1$ |
| } |
| return name + ": " + name2; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Execute a runnable which performs an update of the model being displayed |
| * by this provider. The runnable should be executed in a thread-safe manner |
| * which results 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 runInUIThread |
| */ |
| public void performUpdate(IWorkspaceRunnable runnable, boolean preserveExpansion, boolean runInUIThread) { |
| updateHandler.performUpdate(runnable, preserveExpansion, runInUIThread); |
| } |
| } |