/******************************************************************************* * Copyright (c) 2003, 2016 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 * Mickael Istria (Red Hat Inc.) Bug 264404 - Problem decorators *******************************************************************************/ package org.eclipse.ui.internal.navigator.resources.workbench; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.viewers.AbstractTreeViewer; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Control; import org.eclipse.ui.internal.navigator.resources.nested.PathComparator; import org.eclipse.ui.internal.navigator.resources.plugin.WorkbenchNavigatorPlugin; import org.eclipse.ui.model.WorkbenchContentProvider; /** * @since 3.2 */ public class ResourceExtensionContentProvider extends WorkbenchContentProvider { private static final Object[] NO_CHILDREN = new Object[0]; private Viewer viewer; /** * */ public ResourceExtensionContentProvider() { super(); } @Override public Object[] getElements(Object element) { return super.getChildren(element); } @Override public Object[] getChildren(Object element) { if(element instanceof IResource) return super.getChildren(element); return NO_CHILDREN; } @Override public boolean hasChildren(Object element) { try { if (element instanceof IContainer) { IContainer c = (IContainer) element; if (!c.isAccessible()) return false; return c.members().length > 0; } } catch (CoreException ex) { WorkbenchNavigatorPlugin.getDefault().getLog().log( new Status(IStatus.ERROR, WorkbenchNavigatorPlugin.PLUGIN_ID, 0, ex.getMessage(), ex)); return false; } return super.hasChildren(element); } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { super.inputChanged(viewer, oldInput, newInput); this.viewer = viewer; } /** * Process the resource delta. * * @param delta */ @Override protected void processDelta(IResourceDelta delta) { Control ctrl = viewer.getControl(); if (ctrl == null || ctrl.isDisposed()) { return; } final Collection<Runnable> runnables = new ArrayList<Runnable>(); final SortedSet<IResource> resourcesToRefresh = new TreeSet<IResource>(new Comparator<IResource>() { private PathComparator pathComparator = new PathComparator(); @Override public int compare(IResource arg0, IResource arg1) { return pathComparator.compare(arg0.getFullPath(), arg1.getFullPath()); } }); processDelta(delta, runnables, resourcesToRefresh); IResource currentTopLevelResource = null; for (IResource resource : resourcesToRefresh) { if (resource == null) { // paranoia, see bug 509821 continue; } if (currentTopLevelResource == null || !currentTopLevelResource.getFullPath().isPrefixOf(resource.getFullPath())) { currentTopLevelResource = resource; runnables.add(getRefreshRunnable(resource)); } } if (runnables.isEmpty()) { return; } //Are we in the UIThread? If so spin it until we are done if (ctrl.getDisplay().getThread() == Thread.currentThread()) { runUpdates(runnables); } else { ctrl.getDisplay().asyncExec(new Runnable(){ @Override public void run() { //Abort if this happens after disposes Control ctrl = viewer.getControl(); if (ctrl == null || ctrl.isDisposed()) { return; } runUpdates(runnables); } }); } } /** * Process a resource delta. Add runnables for addAndRemove and * resourceToUpdate. */ private void processDelta(IResourceDelta delta, Collection<Runnable> addAndRemoveRunnables, Set<IResource> toRefresh) { //he widget may have been destroyed // by the time this is run. Check for this and do nothing if so. Control ctrl = viewer.getControl(); if (ctrl == null || ctrl.isDisposed()) { return; } // Get the affected resource final IResource resource = delta.getResource(); // If any children have changed type, just do a full refresh of this // parent, // since a simple update on such children won't work, // and trying to map the change to a remove and add is too dicey. // The case is: folder A renamed to existing file B, answering yes to // overwrite B. IResourceDelta[] affectedChildren = delta.getAffectedChildren(IResourceDelta.CHANGED); for (IResourceDelta affectedChild : affectedChildren) { if ((affectedChild.getFlags() & IResourceDelta.TYPE) != 0) { toRefresh.add(resource); return; } } // Check the flags for changes the Navigator cares about. // See ResourceLabelProvider for the aspects it cares about. // Notice we don't care about F_CONTENT or F_MARKERS currently. int changeFlags = delta.getFlags(); if ((changeFlags & (IResourceDelta.OPEN | IResourceDelta.SYNC | IResourceDelta.TYPE | IResourceDelta.DESCRIPTION)) != 0) { /* support the Closed Projects filter; * when a project is closed, it may need to be removed from the view. */ IContainer parent = resource.getParent(); if (parent != null) { toRefresh.add(parent); } } // Replacing a resource may affect its label and its children if ((changeFlags & IResourceDelta.REPLACED) != 0) { toRefresh.add(resource); return; } if ((changeFlags & IResourceDelta.MARKERS) != 0) { IProject project = resource.getProject(); if (project != null) { toRefresh.add(project); return; } } // Handle changed children . for (IResourceDelta affectedChild : affectedChildren) { processDelta(affectedChild, addAndRemoveRunnables, toRefresh); } // @issue several problems here: // - should process removals before additions, to avoid multiple equal // elements in viewer // - Kim: processing removals before additions was the indirect cause of // 44081 and its varients // - Nick: no delta should have an add and a remove on the same element, // so processing adds first is probably OK // - using setRedraw will cause extra flashiness // - setRedraw is used even for simple changes // - to avoid seeing a rename in two stages, should turn redraw on/off // around combined removal and addition // - Kim: done, and only in the case of a rename (both remove and add // changes in one delta). IResourceDelta[] addedChildren = delta .getAffectedChildren(IResourceDelta.ADDED); IResourceDelta[] removedChildren = delta .getAffectedChildren(IResourceDelta.REMOVED); if (addedChildren.length == 0 && removedChildren.length == 0) { return; } final Object[] addedObjects; final Object[] removedObjects; // Process additions before removals as to not cause selection // preservation prior to new objects being added // Handle added children. Issue one update for all insertions. int numMovedFrom = 0; int numMovedTo = 0; if (addedChildren.length > 0) { addedObjects = new Object[addedChildren.length]; for (int i = 0; i < addedChildren.length; i++) { addedObjects[i] = addedChildren[i].getResource(); if ((addedChildren[i].getFlags() & IResourceDelta.MOVED_FROM) != 0) { ++numMovedFrom; } } } else { addedObjects = new Object[0]; } // Handle removed children. Issue one update for all removals. if (removedChildren.length > 0) { removedObjects = new Object[removedChildren.length]; for (int i = 0; i < removedChildren.length; i++) { removedObjects[i] = removedChildren[i].getResource(); if ((removedChildren[i].getFlags() & IResourceDelta.MOVED_TO) != 0) { ++numMovedTo; } } } else { removedObjects = new Object[0]; } // heuristic test for items moving within same folder (i.e. renames) final boolean hasRename = numMovedFrom > 0 && numMovedTo > 0; Runnable addAndRemove = new Runnable(){ @Override public void run() { if (viewer instanceof AbstractTreeViewer) { AbstractTreeViewer treeViewer = (AbstractTreeViewer) viewer; // Disable redraw until the operation is finished so we don't // get a flash of both the new and old item (in the case of // rename) // Only do this if we're both adding and removing files (the // rename case) if (hasRename) { treeViewer.getControl().setRedraw(false); } try { if (addedObjects.length > 0) { treeViewer.add(resource, addedObjects); } if (removedObjects.length > 0) { treeViewer.remove(removedObjects); } } finally { if (hasRename) { treeViewer.getControl().setRedraw(true); } } } else { ((StructuredViewer) viewer).refresh(resource); } } }; addAndRemoveRunnables.add(addAndRemove); } /** * Return a runnable for refreshing a resource. * @param resource * @return Runnable */ private Runnable getRefreshRunnable(final IResource resource) { return new Runnable(){ @Override public void run() { ((StructuredViewer) viewer).refresh(resource); } }; } /** * Run all of the runnables that are the widget updates * @param runnables */ private void runUpdates(Collection<Runnable> runnables) { for (Runnable runnable : runnables) { runnable.run(); } } }