/******************************************************************************* * Copyright (c) 2000, 2017 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.dltk.internal.ui.navigator; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IFolder; 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.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.dltk.core.DLTKCore; import org.eclipse.dltk.core.ElementChangedEvent; import org.eclipse.dltk.core.IBuildpathEntry; import org.eclipse.dltk.core.IElementChangedListener; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.IModelElementDelta; import org.eclipse.dltk.core.IProjectFragment; import org.eclipse.dltk.core.IScriptFolder; import org.eclipse.dltk.core.IScriptModel; import org.eclipse.dltk.core.IScriptProject; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.core.ScriptModelUtil; import org.eclipse.dltk.internal.ui.StandardModelElementContentProvider; import org.eclipse.dltk.internal.ui.scriptview.BuildPathContainer; import org.eclipse.dltk.internal.ui.scriptview.LibraryContainer; import org.eclipse.dltk.internal.ui.workingsets.WorkingSetModel; import org.eclipse.dltk.ui.DLTKUIPlugin; import org.eclipse.dltk.ui.PreferenceConstants; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IBasicPropertyConstants; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IWorkingSet; import org.eclipse.ui.progress.UIJob; /** * Content provider for the PackageExplorer. * * <p> * Since 2.1 this content provider can provide the children for flat or * hierarchical layout. * </p> * * @see org.eclipse.jdt.ui.StandardJavaElementContentProvider */ public class ScriptExplorerContentProvider extends StandardModelElementContentProvider 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; private boolean fIsFlatLayout; private boolean fShowLibrariesNode; private boolean fFoldPackages; private Collection<Runnable> fPendingUpdates; private UIJob fUpdateJob; /** * Creates a new content provider for Java elements. * * @param provideMembers * if set, members of compilation units and class files are shown */ public ScriptExplorerContentProvider(final boolean provideMembers) { super(provideMembers); fShowLibrariesNode = false; fIsFlatLayout = false; fFoldPackages = arePackagesFoldedInHierarchicalLayout(); fPendingUpdates = null; DLTKUIPlugin.getDefault().getPreferenceStore() .addPropertyChangeListener(this); } private boolean arePackagesFoldedInHierarchicalLayout() { return getPreferenceStore().getBoolean( PreferenceConstants.APPEARANCE_FOLD_PACKAGES_IN_PACKAGE_EXPLORER); } protected IPreferenceStore getPreferenceStore() { return DLTKUIPlugin.getDefault().getPreferenceStore(); } protected Object getViewerInput() { return fInput; } /* * (non-Javadoc) Method declared on IElementChangedListener. */ @Override public void elementChanged(final ElementChangedEvent event) { final ArrayList<Runnable> 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 { executeRunnables(runnables); } } protected final void executeRunnables( final Collection<Runnable> runnables) { // now post all collected runnables Control ctrl = fViewer.getControl(); if (ctrl != null && !ctrl.isDisposed()) { final boolean hasPendingUpdates; synchronized (this) { hasPendingUpdates = fPendingUpdates != null && !fPendingUpdates.isEmpty(); } // Are we in the UIThread? If so spin it until we are done if (!hasPendingUpdates && ctrl.getDisplay().getThread() == Thread.currentThread() && !fViewer.isBusy()) { runUpdates(runnables); } else { synchronized (this) { if (fPendingUpdates == null) { fPendingUpdates = runnables; } else { fPendingUpdates.addAll(runnables); } } postAsyncUpdate(ctrl.getDisplay()); } } } private void postAsyncUpdate(final Display display) { if (fUpdateJob == null) { fUpdateJob = new UIJob(display, "Update Script explorer") { //$NON-NLS-1$ @Override public IStatus runInUIThread(IProgressMonitor monitor) { TreeViewer viewer = fViewer; if (viewer != null && viewer.isBusy()) { // reschedule when viewer is busy: bug 184991 schedule(100); } else { runPendingUpdates(); } return Status.OK_STATUS; } }; fUpdateJob.setSystem(true); } fUpdateJob.schedule(); } /** * Run all of the runnables that are the widget updates. Must be called in * the display thread. */ public void runPendingUpdates() { Collection<Runnable> 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<Runnable> runnables) { Iterator<Runnable> runnableIterator = runnables.iterator(); while (runnableIterator.hasNext()) { runnableIterator.next().run(); } } private boolean inputDeleted(final Collection<Runnable> 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, ScriptExplorerContentProvider.ORIGINAL, fInput, runnables); return true; } @Override public void dispose() { super.dispose(); DLTKCore.removeElementChangedListener(this); DLTKUIPlugin.getDefault().getPreferenceStore() .removePropertyChangeListener(this); } @Override protected Object[] getProjectFragmentContent(final IProjectFragment root) throws ModelException { if (fIsFlatLayout) { return super.getProjectFragmentContent(root); } // hierarchical package mode ArrayList<Object> result = new ArrayList<>(); getHierarchicalPackageChildren(root, null, result); if (!isProjectProjectFragment(root)) { Object[] nonJavaResources = root.getForeignResources(); for (int i = 0; i < nonJavaResources.length; i++) { result.add(nonJavaResources[i]); } } return result.toArray(); } @Override protected Object[] getScriptFolderContent(final IScriptFolder fragment) throws ModelException { if (fIsFlatLayout) { return super.getScriptFolderContent(fragment); } // hierarchical package mode ArrayList<Object> result = new ArrayList<>(); getHierarchicalPackageChildren((IProjectFragment) fragment.getParent(), fragment, result); Object[] nonPackages = super.getScriptFolderContent(fragment); if (result.isEmpty()) { return nonPackages; } for (int i = 0; i < nonPackages.length; i++) { result.add(nonPackages[i]); } return result.toArray(); } @Override protected Object[] getFolderContent(final IFolder folder) throws CoreException { if (fIsFlatLayout) { return super.getFolderContent(folder); } // hierarchical package mode ArrayList<Object> result = new ArrayList<>(); getHierarchicalPackagesInFolder(folder, result); Object[] others = super.getFolderContent(folder); if (result.isEmpty()) { return others; } for (int i = 0; i < others.length; i++) { result.add(others[i]); } return result.toArray(); } @Override public Object[] getChildren(final Object parentElement) { try { if (parentElement instanceof IScriptModel) { return getExtendedChildren(parentElement, StandardModelElementContentProvider.concatenate( getScriptProjects((IScriptModel) parentElement), getNonJavaProjects( (IScriptModel) parentElement))); } if (parentElement instanceof ProjectFragmentContainer) { return getExtendedChildren(parentElement, getContainerProjectFragments( (ProjectFragmentContainer) parentElement)); } if (parentElement instanceof IProject) { if (!((IProject) parentElement).isAccessible()) { return StandardModelElementContentProvider.NO_CHILDREN; } return getExtendedChildren(parentElement, ((IProject) parentElement).members()); } return super.getChildren(parentElement); } catch (CoreException e) { return getExtendedChildren(parentElement, StandardModelElementContentProvider.NO_CHILDREN); } } @Override protected Object[] getProjectFragments(final IScriptProject project) throws ModelException { if (!project.getProject().isOpen()) { return StandardModelElementContentProvider.NO_CHILDREN; } List<Object> result = new ArrayList<>(); boolean addZIPContainer = false; IProjectFragment[] roots = project.getProjectFragments(); for (int i = 0; i < roots.length; i++) { IProjectFragment root = roots[i]; final IBuildpathEntry classpathEntry; try { classpathEntry = root.getRawBuildpathEntry(); } catch (ModelException e) { continue; } if (classpathEntry == null) { continue; } int entryKind = classpathEntry.getEntryKind(); if (entryKind == IBuildpathEntry.BPE_CONTAINER) { // all ClassPathContainers are added later } else if (fShowLibrariesNode && (entryKind == IBuildpathEntry.BPE_LIBRARY /* * || * entryKind * == * IBuildpathEntry * .BPE_VARIABLE */)) { addZIPContainer = true; } else { if (isProjectProjectFragment(root)) { // filter out package fragments that correspond to projects // and // replace them with the package fragments directly Object[] fragments = getProjectFragmentContent(root); for (int j = 0; j < fragments.length; j++) { result.add(fragments[j]); } } else { result.add(root); } } } if (addZIPContainer) { result.add(new LibraryContainer(project)); } // separate loop to make sure all containers are on the classpath IBuildpathEntry[] rawBuidspath = project.getRawBuildpath(); for (int i = 0; i < rawBuidspath.length; i++) { IBuildpathEntry classpathEntry = rawBuidspath[i]; if (classpathEntry .getEntryKind() == IBuildpathEntry.BPE_CONTAINER) { result.add(new BuildPathContainer(project, classpathEntry)); } } Object[] resources = project.getForeignResources(); for (int i = 0; i < resources.length; i++) { result.add(resources[i]); } return result.toArray(); } private Object[] getContainerProjectFragments( final ProjectFragmentContainer container) { return container.getChildren(); } private Object[] getNonJavaProjects(final IScriptModel model) throws ModelException { return model.getForeignResources(); } @Override protected Object internalGetParent(final Object element) { if (!fIsFlatLayout && element instanceof IScriptFolder) { return getHierarchicalPackageParent((IScriptFolder) element); } else if (element instanceof IProjectFragment) { // since we insert logical package containers we have to fix // up the parent for package fragment roots so that they refer // to the container and containers refer to the project IProjectFragment root = (IProjectFragment) element; try { IBuildpathEntry entry = root.getRawBuildpathEntry(); if (entry != null) { int entryKind = entry.getEntryKind(); if (entryKind == IBuildpathEntry.BPE_CONTAINER) { return new BuildPathContainer(root.getScriptProject(), entry); } else if (fShowLibrariesNode && (entryKind == IBuildpathEntry.BPE_LIBRARY /* * || * entryKind * == * IBuildpathEntry * . * BPE_VARIABLE */)) { return new LibraryContainer(root.getScriptProject()); } } } catch (ModelException e) { // fall through } } else if (element instanceof ProjectFragmentContainer) { return ((ProjectFragmentContainer) element).getScriptProject(); } return super.internalGetParent(element); } @Override public void inputChanged(final Viewer viewer, final Object oldInput, final Object newInput) { super.inputChanged(viewer, oldInput, newInput); fViewer = (TreeViewer) viewer; if (oldInput == null && newInput != null) { DLTKCore.addElementChangedListener(this); } else if (oldInput != null && newInput == null) { DLTKCore.removeElementChangedListener(this); } fInput = newInput; } // hierarchical packages /** * Returns the hierarchical packages inside a given fragment or root. * * @param parent * The parent package fragment root * @param fragment * The package to get the children for or 'null' to get the * children of the root. * @param result * Collection where the resulting elements are added * @throws JavaModelException */ private void getHierarchicalPackageChildren(final IProjectFragment parent, final IScriptFolder fragment, final Collection<Object> result) throws ModelException { IModelElement[] children = parent.getChildren(); if (fragment == null || fragment.isRootFolder()) { List<IModelElement> newElements = new ArrayList<>(); for (int i = 0; i < children.length; ++i) { if (children[i] instanceof IScriptFolder) { IScriptFolder scriptFolder = (IScriptFolder) children[i]; if (scriptFolder.isRootFolder()) { IModelElement[] members = scriptFolder.getChildren(); for (int j = 0; j < members.length; ++j) { newElements.add(members[j]); } continue; } } newElements.add(children[i]); } children = newElements .toArray(new IModelElement[newElements.size()]); } String prefix = fragment != null ? fragment.getElementName() + IScriptFolder.PACKAGE_DELIMETER_STR : ""; //$NON-NLS-1$ int prefixLen = prefix.length(); for (int i = 0; i < children.length; i++) { if (children[i] instanceof IScriptFolder) { IScriptFolder curr = (IScriptFolder) children[i]; String name = curr.getElementName(); if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf(IScriptFolder.PACKAGE_DELIMITER, prefixLen) == -1) { if (fFoldPackages) { curr = ScriptExplorerContentProvider.getFolded(children, curr); } result.add(curr); } /* * else if (fragment == null && curr.isRootFolder()) { * result.add(curr); } */ } else { result.add(children[i]); } } } /** * Returns the hierarchical packages inside a given folder. * * @param folder * The parent folder * @param result * Collection where the resulting elements are added * @throws CoreException * thrown when elements could not be accessed */ private void getHierarchicalPackagesInFolder(final IFolder folder, final Collection<Object> result) throws CoreException { IResource[] resources = folder.members(); for (int i = 0; i < resources.length; i++) { IResource resource = resources[i]; if (resource instanceof IFolder) { IFolder curr = (IFolder) resource; IModelElement element = DLTKCore.create(curr); if (element instanceof IScriptFolder) { if (fFoldPackages) { IScriptFolder fragment = (IScriptFolder) element; IProjectFragment root = (IProjectFragment) fragment .getParent(); element = ScriptExplorerContentProvider .getFolded(root.getChildren(), fragment); } result.add(element); } } } } public Object getHierarchicalPackageParent(final IScriptFolder child) { String name = child.getElementName(); IProjectFragment parent = (IProjectFragment) child.getParent(); int index = name.lastIndexOf(IScriptFolder.PACKAGE_DELIMITER); if (index != -1) { String realParentName = name.substring(0, index); IScriptFolder element = parent.getScriptFolder(realParentName); if (element != null && element.exists()) { try { if (fFoldPackages && ScriptExplorerContentProvider.isEmpty(element) && ScriptExplorerContentProvider .findSinglePackageChild(element, parent.getChildren()) != null) { return getHierarchicalPackageParent(element); } } catch (ModelException e) { // ignore } return element; } else if (element != null) { // bug 65240 IResource resource = element.getResource(); if (resource != null) { return resource; } } } if (parent.getResource() instanceof IProject) { return parent.getScriptProject(); } return parent; } private static IScriptFolder getFolded(final IModelElement[] children, IScriptFolder pack) throws ModelException { while (ScriptExplorerContentProvider.isEmpty(pack)) { IScriptFolder collapsed = ScriptExplorerContentProvider .findSinglePackageChild(pack, children); if (collapsed == null) { return pack; } pack = collapsed; } return pack; } private static boolean isEmpty(final IScriptFolder fragment) throws ModelException { return !fragment.containsScriptResources() && fragment.getForeignResources().length == 0; } private static IScriptFolder findSinglePackageChild( final IScriptFolder fragment, final IModelElement[] children) { String prefix = fragment.getElementName() + IScriptFolder.PACKAGE_DELIMITER; int prefixLen = prefix.length(); IScriptFolder found = null; for (int i = 0; i < children.length; i++) { IModelElement element = children[i]; String name = element.getElementName(); if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf(IScriptFolder.PACKAGE_DELIMITER, prefixLen) == -1) { if (found == null) { found = (IScriptFolder) element; } else { return null; } } } return found; } // ------ 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<Runnable> runnables) throws ModelException { int kind = delta.getKind(); int flags = delta.getFlags(); IModelElement element = delta.getElement(); int elementType = element.getElementType(); if (elementType != IModelElement.SCRIPT_MODEL && elementType != IModelElement.SCRIPT_PROJECT) { IScriptProject proj = element.getScriptProject(); if (proj == null || !proj.getProject().isOpen()) { // TODO: Not needed if parent already did the 'open' check! return false; } } if (elementType == IModelElement.SCRIPT_FOLDER) { if ((flags & (IModelElementDelta.F_CONTENT | IModelElementDelta.F_CHILDREN)) == IModelElementDelta.F_CONTENT) { if (!fIsFlatLayout) { Object parent = getHierarchicalPackageParent( (IScriptFolder) element); if (!(parent instanceof IProjectFragment)) { postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables); return true; } } // content change, without children info (for example resource // added/removed to class folder package) postRefresh(internalGetParent(element), PARENT, element, runnables); return true; } if (!fIsFlatLayout) { if (kind == IModelElementDelta.REMOVED) { final Object parent = getHierarchicalPackageParent( (IScriptFolder) element); if (parent instanceof IProjectFragment) { postRemove(element, runnables); return false; } else { postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables); return true; } } else if (kind == IModelElementDelta.ADDED) { final Object parent = getHierarchicalPackageParent( (IScriptFolder) element); if (parent instanceof IProjectFragment) { if (fFoldPackages) { postRefresh(parent, PARENT, element, runnables); return true; } else { postAdd(parent, element, runnables); return false; } } else { postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables); return true; } } handleAffectedChildren(delta, element, runnables); return false; } } if (elementType == IModelElement.SOURCE_MODULE) { ISourceModule cu = (ISourceModule) element; if (!ScriptModelUtil.isPrimary(cu)) { return false; } if (!getProvideMembers() && cu.isWorkingCopy() && kind == IModelElementDelta.CHANGED) { return false; } if (kind == IModelElementDelta.CHANGED && !isStructuralCUChange(flags)) { return false; // test moved ahead } if (!isOnClassPath(cu)) { // TODO: isOnClassPath expensive! Should // be put after all cheap tests return false; } } if (elementType == IModelElement.SCRIPT_PROJECT) { // handle open and closing of a project if ((flags & (IModelElementDelta.F_CLOSED | IModelElementDelta.F_OPENED)) != 0) { postRefresh(element, ORIGINAL, element, runnables); return false; } // if the class path has changed we refresh the entire project if ((flags & IModelElementDelta.F_BUILDPATH_CHANGED) != 0) { postRefresh(element, ORIGINAL, element, runnables); return false; } // if added it could be that the corresponding IProject is already // shown. Remove it first. // bug 184296 if (kind == IModelElementDelta.ADDED) { postRemove(element.getResource(), runnables); postAdd(element.getParent(), element, runnables); return false; } } if (kind == IModelElementDelta.REMOVED) { Object parent = internalGetParent(element); if (element instanceof IScriptFolder) { // refresh package fragment root to allow filtering empty // (parent) packages: bug 72923 if (fViewer.testFindItem(parent) != null) { postRefresh(parent, PARENT, element, runnables); } return true; } else if (element instanceof IProjectFragment && ((IProjectFragment) element) .getKind() != IProjectFragment.K_SOURCE) { // libs and class folders can show up twice (in library // container and as resource at original location) IResource resource = element.getResource(); if (resource != null) postRemove(resource, runnables); } postRemove(element, runnables); if (parent instanceof IScriptFolder) { postUpdateIcon((IScriptFolder) parent, runnables); } // we are filtering out empty subpackages, so we // a package becomes empty we remove it from the viewer. if (isScriptFolderEmpty(element.getParent())) { if (fViewer.testFindItem(parent) != null) { postRefresh(internalGetParent(parent), GRANT_PARENT, element, runnables); } return true; } return false; } if (kind == IModelElementDelta.ADDED) { Object parent = internalGetParent(element); // we are filtering out empty subpackages, so we // have to handle additions to them specially. if (parent instanceof IScriptFolder) { Object grandparent = internalGetParent(parent); // 1GE8SI6: ITPJUI:WIN98 - Rename is not shown in Packages View // avoid posting a refresh to an invisible parent if (parent.equals(fInput)) { postRefresh(parent, PARENT, element, runnables); } else { // refresh from grandparent if parent isn't visible yet if (fViewer.testFindItem(parent) == null) { postRefresh(grandparent, GRANT_PARENT, element, runnables); } else { postRefresh(parent, PARENT, element, runnables); } } return true; } else { postAdd(parent, element, runnables); } } if (elementType == IModelElement.SOURCE_MODULE || elementType == IModelElement.BINARY_MODULE) { if (kind == IModelElementDelta.CHANGED) { // isStructuralCUChange already performed above postRefresh(element, ORIGINAL, element, runnables); } return false; } if (elementType == IModelElement.PROJECT_FRAGMENT) { // the contents of an external JAR or class folder has changed if ((flags & IModelElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0) { postRefresh(element, ORIGINAL, element, runnables); return false; } if ((flags & (IModelElementDelta.F_CONTENT | IModelElementDelta.F_CHILDREN)) == IModelElementDelta.F_CONTENT) { // content change, without children info (for example resource // added/removed to class folder package) postRefresh(internalGetParent(element), PARENT, element, runnables); return true; } // the source attachment of a JAR has changed // if ((flags & ( | IModelElementDelta.F_SOURCEDETACHED)) != 0) { // postUpdateIcon(element, runnables); // } if (isBuildPathChange(delta)) { // throw the towel and do a full refresh of the affected java // project. postRefresh(element.getScriptProject(), PROJECT, element, runnables); return true; } } handleAffectedChildren(delta, element, runnables); return false; } private static boolean isStructuralCUChange(final int flags) { // No refresh on working copy creation (F_PRIMARY_WORKING_COPY) return (flags & IModelElementDelta.F_CHILDREN) != 0 || (flags & (IModelElementDelta.F_CONTENT | IModelElementDelta.F_FINE_GRAINED)) == IModelElementDelta.F_CONTENT; } /* package */void handleAffectedChildren(final IModelElementDelta delta, final IModelElement element, final Collection<Runnable> runnables) throws ModelException { int count = 0; IResourceDelta[] resourceDeltas = delta.getResourceDeltas(); if (resourceDeltas != null) { for (int i = 0; i < resourceDeltas.length; i++) { int kind = resourceDeltas[i].getKind(); if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { count++; } } } IModelElementDelta[] affectedChildren = delta.getAffectedChildren(); for (int i = 0; i < affectedChildren.length; i++) { int kind = affectedChildren[i].getKind(); if (kind == IModelElementDelta.ADDED || kind == IModelElementDelta.REMOVED) { count++; } } if (count > 1) { // more than one child changed, refresh from here downwards if (element instanceof IScriptFolder) { // a package fragment might become non empty refresh from the // parent IModelElement parent = (IModelElement) internalGetParent( element); // 1GE8SI6: ITPJUI:WIN98 - Rename is not shown in Packages View // avoid posting a refresh to an invisible parent if (element.equals(fInput)) { postRefresh(element, ScriptExplorerContentProvider.ORIGINAL, element, runnables); } else { postRefresh(parent, ScriptExplorerContentProvider.PARENT, element, runnables); } } else if (element instanceof IProjectFragment) { Object toRefresh = internalGetParent(element); postRefresh(toRefresh, ScriptExplorerContentProvider.ORIGINAL, toRefresh, runnables); } else { postRefresh(element, ScriptExplorerContentProvider.ORIGINAL, element, runnables); } return; } if (resourceDeltas != null) { for (int i = 0; i < resourceDeltas.length; i++) { if (processResourceDelta(resourceDeltas[i], element, runnables)) { return; // early return, element got refreshed } } } for (int i = 0; i < affectedChildren.length; i++) { if (processDelta(affectedChildren[i], runnables)) { return; // early return, element got refreshed } } } protected void processAffectedChildren( final IModelElementDelta[] affectedChildren, final Collection<Runnable> runnables) throws ModelException { for (int i = 0; i < affectedChildren.length; i++) { processDelta(affectedChildren[i], runnables); } } private boolean isOnClassPath(final ISourceModule element) { IScriptProject project = element.getScriptProject(); if (project == null || !project.exists()) { return false; } return project.isOnBuildpath(element); } /** * Updates the package icon * * @param element * the element to update * @param runnables * the resulting view changes as runnables (type {@link Runnable} * ) */ private void postUpdateIcon(final IModelElement element, final Collection<Runnable> runnables) { runnables.add(() -> fViewer.update(element, new String[] { IBasicPropertyConstants.P_IMAGE })); } /** * Process a resource delta. * * @param delta * the delta to process * @param parent * the parent * @param runnables * the resulting view changes as runnables (type {@link Runnable} * ) * @return true if the parent got refreshed */ private boolean processResourceDelta(final IResourceDelta delta, final Object parent, final Collection<Runnable> runnables) { int status = delta.getKind(); int flags = delta.getFlags(); IResource resource = delta.getResource(); // filter out changes affecting the output folder if (resource == null) { return false; } // this could be optimized by handling all the added children in the // parent if ((status & IResourceDelta.REMOVED) != 0) { if (parent instanceof IScriptFolder) { // refresh one level above to deal with empty package filtering // properly postRefresh(internalGetParent(parent), ScriptExplorerContentProvider.PARENT, parent, runnables); return true; } else { postRemove(resource, runnables); return false; } } if ((status & IResourceDelta.ADDED) != 0) { if (parent instanceof IScriptFolder) { // refresh one level above to deal with empty package filtering // properly postRefresh(internalGetParent(parent), ScriptExplorerContentProvider.PARENT, parent, runnables); return true; } else { postAdd(parent, resource, runnables); return false; } } if ((status & IResourceDelta.CHANGED) != 0) { if ((flags & IResourceDelta.TYPE) != 0) { postRefresh(parent, ScriptExplorerContentProvider.PARENT, resource, runnables); return true; } } // open/close state change of a project if ((flags & IResourceDelta.OPEN) != 0) { postProjectStateChanged(internalGetParent(parent), runnables); return true; } IResourceDelta[] resourceDeltas = delta.getAffectedChildren(); int count = 0; for (int i = 0; i < resourceDeltas.length; i++) { int kind = resourceDeltas[i].getKind(); if (kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED) { count++; if (count > 1) { postRefresh(parent, PARENT, resource, runnables); return true; } } } for (int i = 0; i < resourceDeltas.length; i++) { if (processResourceDelta(resourceDeltas[i], resource, runnables)) { return false; // early return, element got refreshed } } return false; } public void setIsFlatLayout(final boolean state) { fIsFlatLayout = state; } public void setShowLibrariesNode(final boolean state) { fShowLibrariesNode = state; } protected void postRefresh(Object root, final int relation, final Object affectedElement, final Collection<Runnable> runnables) { // JFace doesn't refresh when object isn't part of the viewer // Therefore move the refresh start down to the viewer's input if (isParent(root, fInput) || root instanceof IScriptModel) { root = fInput; } List<Object> toRefresh = new ArrayList<>(1); toRefresh.add(root); augmentElementToRefresh(toRefresh, relation, affectedElement); postRefresh(toRefresh, true, runnables); } /** * Can be implemented by subclasses to add additional elements to refresh * * @param toRefresh * the elements to refresh * @param relation * the relation to the affected element ({@link #GRANT_PARENT}, * {@link #PARENT}, {@link #ORIGINAL}, {@link #PROJECT}) * @param affectedElement * the affected element */ protected void augmentElementToRefresh(final List<Object> toRefresh, final int relation, final Object affectedElement) { } private boolean isParent(final Object root, final Object child) { Object parent = getParent(child); if (parent == null) { return false; } if (parent.equals(root)) { return true; } return isParent(root, parent); } protected void postRefresh(final List<?> toRefresh, final boolean updateLabels, final Collection<Runnable> runnables) { runnables.add(() -> { for (Object item : toRefresh) { fViewer.refresh(item, updateLabels); } }); } protected void postAdd(final Object parent, final Object element, final Collection<Runnable> runnables) { runnables.add(() -> { Widget[] items = fViewer.testFindItems(element); for (int i = 0; i < items.length; i++) { Widget item = items[i]; if (item instanceof TreeItem && !item.isDisposed()) { TreeItem parentItem = ((TreeItem) item).getParentItem(); if (parentItem != null && !parentItem.isDisposed() && parent.equals(parentItem.getData())) { return; // no add, element already added (most // likely by a refresh) } } } fViewer.add(parent, element); }); } protected void postRemove(final Object element, final Collection<Runnable> runnables) { runnables.add(() -> fViewer.remove(element)); } protected void postProjectStateChanged(final Object root, final Collection<Runnable> runnables) { runnables.add(() -> { fViewer.refresh(root, true); // trigger a synthetic selection change so that action refresh // their // enable state. fViewer.setSelection(fViewer.getSelection()); }); } /* * @see * org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse * .jface.util.PropertyChangeEvent) */ @Override public void propertyChange(final PropertyChangeEvent event) { if (arePackagesFoldedInHierarchicalLayout() != fFoldPackages) { fFoldPackages = arePackagesFoldedInHierarchicalLayout(); if (fViewer != null && !fViewer.getControl().isDisposed()) { fViewer.getControl().setRedraw(false); Object[] expandedObjects = fViewer.getExpandedElements(); fViewer.refresh(); fViewer.setExpandedElements(expandedObjects); fViewer.getControl().setRedraw(true); } } } }