blob: 8480dce74eea4aa57a8cdd11157f4e6e53af1c20 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}
}
}