/******************************************************************************* * Copyright (c) 2009 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 * Zend Technologies *******************************************************************************/ package org.eclipse.php.internal.ui.projectoutlineview; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IPath; import org.eclipse.dltk.core.*; import org.eclipse.dltk.internal.core.Model; import org.eclipse.dltk.internal.ui.navigator.ScriptExplorerContentProvider; import org.eclipse.dltk.internal.ui.workingsets.WorkingSetModel; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.php.internal.ui.Logger; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IWorkingSet; /** * Content provider for the Project outline view * * * @see org.eclipse.jdt.ui.StandardJavaElementContentProvider */ public class ProjectOutlineContentProvider extends ScriptExplorerContentProvider implements ITreeContentProvider, IElementChangedListener, IPropertyChangeListener { protected static final int ORIGINAL = 0; protected static final int PARENT = 1 << 0; protected static final int GRANT_PARENT = 1 << 1; protected static final int PROJECT = 1 << 2; private TreeViewer fViewer; private Object fInput; static IScriptProject scripProject = null; private Collection fPendingUpdates; /** * Creates a new content provider for Java elements. * * @param provideMembers * if set, members of compilation units and class files are shown */ public ProjectOutlineContentProvider(final boolean provideMembers) { super(provideMembers); fPendingUpdates = null; } @Override protected Object getViewerInput() { return fInput; } /* * (non-Javadoc) Method declared on IElementChangedListener. */ @Override public void elementChanged(final ElementChangedEvent event) { final ArrayList runnables = new ArrayList(); try { // 58952 delete project does not update Package Explorer [package // explorer] // if the input to the viewer is deleted then refresh to avoid the // display of stale elements if (inputDeleted(runnables)) { return; } processDelta(event.getDelta(), runnables); } catch (ModelException e) { DLTKUIPlugin.log(e); } finally { executeProjOutlineRunnables(runnables); } } protected final void executeProjOutlineRunnables(final Collection runnables) { // now post all collected runnables Control ctrl = fViewer.getControl(); if (ctrl != null && !ctrl.isDisposed()) { // Are we in the UIThread? If so spin it until we are done if (ctrl.getDisplay().getThread() == Thread.currentThread()) { runUpdates(runnables); } else { synchronized (this) { if (fPendingUpdates == null) { fPendingUpdates = runnables; } else { fPendingUpdates.addAll(runnables); } } ctrl.getDisplay().asyncExec(new Runnable() { @Override public void run() { runPendingUpdates(); } }); } } } /** * Run all of the runnables that are the widget updates. Must be called in * the display thread. */ @Override public void runPendingUpdates() { Collection pendingUpdates; synchronized (this) { pendingUpdates = fPendingUpdates; fPendingUpdates = null; } if (pendingUpdates != null && fViewer != null) { Control control = fViewer.getControl(); if (control != null && !control.isDisposed()) { runUpdates(pendingUpdates); } } } private void runUpdates(final Collection runnables) { Iterator runnableIterator = runnables.iterator(); while (runnableIterator.hasNext()) { ((Runnable) runnableIterator.next()).run(); } } private boolean inputDeleted(final Collection runnables) { if (fInput == null) { return false; } if (fInput instanceof IModelElement && ((IModelElement) fInput).exists()) { return false; } if (fInput instanceof IResource && ((IResource) fInput).exists()) { return false; } if (fInput instanceof WorkingSetModel) { return false; } if (fInput instanceof IWorkingSet) { // https://bugs.eclipse.org/bugs/show_bug.cgi?id=156239 return false; } postRefresh(fInput, ProjectOutlineContentProvider.ORIGINAL, fInput, runnables); return true; } @Override public Object[] getChildren(final Object element) { if (element instanceof IScriptProject) { scripProject = (IScriptProject) element; return ProjectOutlineGroups.values(); } if (element instanceof ProjectOutlineGroups) return ((ProjectOutlineGroups) element).getChildren(); return super.getChildren(element); } @Override public boolean hasChildren(Object element) { if (element instanceof ProjectOutlineGroups) { return true; } return super.hasChildren(element); } protected Object internalGetParentGroupNode(final Object element) { if (element instanceof IModelElement) { IModelElement modelElement = (IModelElement) element; if (OutlineUtils.isGlobalClass(modelElement)) return ProjectOutlineGroups.GROUP_CLASSES; if (OutlineUtils.isGlobalFunction(modelElement)) return ProjectOutlineGroups.GROUP_FUNCTIONS; if (OutlineUtils.isConstant(modelElement)) return ProjectOutlineGroups.GROUP_CONSTANTS; return ProjectOutlineGroups.GROUP_NAMESPACES; } return null; } /* * (non-Javadoc) Method declared on IContentProvider. */ @Override public void inputChanged(final Viewer viewer, final Object oldInput, Object newInput) { super.inputChanged(viewer, oldInput, newInput); if (null != newInput && newInput instanceof Model) { try { IScriptProject[] scriptProjects = ((Model) newInput).getScriptProjects(); newInput = scriptProjects.length > 0 ? scriptProjects[0] : new Object[0]; } catch (ModelException e) { Logger.logException(e); } } fViewer = (TreeViewer) viewer; if (fInput == null || !fInput.equals(newInput)) fInput = newInput; } // ------ delta processing ------ /** * Processes a delta recursively. When more than two children are affected * the tree is fully refreshed starting at this node. * * @param delta * the delta to process * @param runnables * the resulting view changes as runnables (type {@link Runnable} * ) * @return true is returned if the conclusion is to refresh a parent of an * element. In that case no siblings need to be processed * @throws JavaModelException * thrown when the access to an element failed */ private boolean processDelta(final IModelElementDelta delta, final Collection runnables) throws ModelException { int kind = delta.getKind(); IModelElement element = delta.getElement(); Object parent = internalGetParentGroupNode(element); if (kind == IModelElementDelta.ADDED) { // if it is an IOpenable (source module and up) - refresh the view IModelElement refreshRoot = (element instanceof IOpenable) ? element.getScriptProject() : element.getParent(); if (null == parent) { postRefresh(refreshRoot, ProjectOutlineContentProvider.ORIGINAL, element, runnables); return false; } // adding element if parent is not null // (means it should be showed on the project outline view) postAdd(parent, element, runnables); } else if (kind == IModelElementDelta.REMOVED) { if (element instanceof IOpenable) { final IPath removedPath = element.getPath(); fViewer.getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { for (TreeItem node : fViewer.getTree().getItems()) { // iterating on all 1st level (classes, constants, // and // functions) TreeItem[] treeItems = node.getItems(); for (TreeItem treeItem : treeItems) { // iterating on all 2nd level elements and // checking : // if item path is prefixed by the removed // element - // need to remove it. IModelElement itemData = (IModelElement) treeItem.getData(); if (itemData != null) { if (removedPath.isPrefixOf(itemData.getPath())) { postRemove((IModelElement) treeItem.getData(), runnables); } } } } } }); } else { // if element is not folder/project/SourceModule etc' - just // need to remove it from the view postRemove(element, runnables); } return false; } handleAffectedChildren(delta, element, runnables); return false; } /* package */void handleAffectedChildren(final IModelElementDelta delta, final IModelElement element, final Collection runnables) throws ModelException { IModelElementDelta[] affectedChildren = delta.getAffectedChildren(); if (affectedChildren.length > 1) { int count = 0; for (int i = 0; i < affectedChildren.length; i++) { // if there is more than if (affectedChildren[i].getElement() instanceof IOpenable) { count++; } if (count > 1) { postRefresh(fInput, ProjectOutlineContentProvider.ORIGINAL, element, runnables); return; } } } for (int i = 0; i < affectedChildren.length; i++) { if (processDelta(affectedChildren[i], runnables)) { return; // early return, element got refreshed } } } }