/*******************************************************************************
* 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.ui.browsing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IResource;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ElementChangedEvent;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IElementChangedListener;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.IPackageDeclaration;
import org.eclipse.dltk.core.IParent;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IScriptFolder;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceReference;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.internal.ui.StandardModelElementContentProvider;
import org.eclipse.dltk.ui.DLTKUIPlugin;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.IBasicPropertyConstants;
import org.eclipse.jface.viewers.ListViewer;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
class ScriptBrowsingContentProvider extends StandardModelElementContentProvider
implements IElementChangedListener {
private StructuredViewer fViewer;
private Object fInput;
private ScriptBrowsingPart fBrowsingPart;
private int fReadsInDisplayThread;
private IDLTKLanguageToolkit fToolkit;
public ScriptBrowsingContentProvider(boolean provideMembers,
ScriptBrowsingPart browsingPart, IDLTKLanguageToolkit languageToolkit) {
super(provideMembers);
fBrowsingPart = browsingPart;
fViewer = fBrowsingPart.getViewer();
DLTKCore.addElementChangedListener(this);
this.fToolkit = languageToolkit;
}
public IDLTKLanguageToolkit getToolkit() {
return this.fToolkit;
}
@Override
public boolean hasChildren(Object element) {
startReadInDisplayThread();
try {
return super.hasChildren(element);
} finally {
finishedReadInDisplayThread();
}
}
@Override
public Object[] getChildren(Object element) {
if (!exists(element))
return NO_CHILDREN;
startReadInDisplayThread();
try {
if (element instanceof Collection) {
Collection elements = (Collection) element;
if (elements.isEmpty())
return NO_CHILDREN;
Object[] result = new Object[0];
Iterator iter = ((Collection) element).iterator();
while (iter.hasNext()) {
Object[] children = getChildren(iter.next());
if (children != NO_CHILDREN)
result = concatenate(result, children);
}
return filterExternal( result );
}
if (element instanceof IScriptFolder)
return getScriptFolderContents((IScriptFolder) element);
if (fProvideMembers && element instanceof IType)
return getChildren((IType) element);
if (fProvideMembers && element instanceof ISourceReference
&& element instanceof IParent)
return removeImportAndPackageDeclarations(super
.getChildren(element));
if (element instanceof IScriptProject)
return getProjectFragments((IScriptProject) element);
return super.getChildren(element);
} catch (ModelException e) {
return NO_CHILDREN;
} finally {
finishedReadInDisplayThread();
}
}
private Object[] filterExternal(Object[] elements) {
List result = new ArrayList();
Map pr = new HashMap();
for (int i = 0; i < elements.length; i++) {
Object element = elements[i];
if( element instanceof IModelElement ) {
String name = ((IModelElement)element).getElementName();
if( pr.containsKey(name) ) {
IModelElement pre = (IModelElement) pr.get(name);
IProjectFragment preModule = (IProjectFragment) pre.getAncestor(IModelElement.PROJECT_FRAGMENT);
IProjectFragment elementModule = (IProjectFragment) ((IModelElement)element).getAncestor(IModelElement.PROJECT_FRAGMENT);
if( preModule.isExternal() && !elementModule.isExternal()) { // we need to replace with this element.
result.remove(pre);
result.add(element);
pr.put(name, element);
}
}
else {
result.add(element);
pr.put(name, element);
}
}
else {
result.add(element);
}
}
return result.toArray();
}
private Object[] getScriptFolderContents(IScriptFolder fragment)
throws ModelException {
ISourceReference[] sourceRefs;
// if (fragment.getElementType() == IProjectFragment.K_SOURCE) {
sourceRefs = fragment.getSourceModules();
// }
Object[] result = new Object[0];
for (int i = 0; i < sourceRefs.length; i++)
result = concatenate(
result,
removeImportAndPackageDeclarations(getChildren(sourceRefs[i])));
return concatenate(result, fragment.getForeignResources());
}
private Object[] removeImportAndPackageDeclarations(Object[] members) {
ArrayList tempResult = new ArrayList(members.length);
for (int i = 0; i < members.length; i++)
if (/* !(members[i] instanceof IImportContainer) && */!(members[i] instanceof IPackageDeclaration))
tempResult.add(members[i]);
return tempResult.toArray();
}
private Object[] getChildren(IType type) throws ModelException {
// IParent parent;
// if (type.isBinary())
// parent= type.getClassFile();
// else {
// parent = type.getSourceModule();
// }
// if (type.getDeclaringType() != null)
// return type.getChildren();
// Add import declarations
IModelElement[] members = type.getChildren();
ArrayList tempResult = new ArrayList(members.length);
// for (int i= 0; i < members.length; i++)
// if ((members[i] instanceof IImportContainer))
// tempResult.add(members[i]);
tempResult.addAll(Arrays.asList(type.getChildren()));
return tempResult.toArray();
}
@Override
protected Object[] getProjectFragments(IScriptProject project)
throws ModelException {
if (!project.getProject().isOpen())
return NO_CHILDREN;
IProjectFragment[] roots = project.getProjectFragments();
List list = new ArrayList(roots.length);
// filter out package fragments that correspond to projects and
// replace them with the package fragments directly
for (int i = 0; i < roots.length; i++) {
IProjectFragment root = roots[i];
if (!root.isExternal()) {
Object[] children = root.getChildren();
for (int k = 0; k < children.length; k++)
list.add(children[k]);
} else if (hasChildren(root)) {
list.add(root);
}
}
return concatenate(list.toArray(), project.getForeignResources());
}
// ---------------- Element change handling
/*
* (non-Javadoc) Method declared on IContentProvider.
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
super.inputChanged(viewer, oldInput, newInput);
if (newInput instanceof Collection) {
// Get a template object from the collection
Collection col = (Collection) newInput;
if (!col.isEmpty())
newInput = col.iterator().next();
else
newInput = null;
}
fInput = newInput;
}
/*
* (non-Javadoc) Method declared on IContentProvider.
*/
@Override
public void dispose() {
super.dispose();
DLTKCore.removeElementChangedListener(this);
}
/*
* (non-Javadoc) Method declared on IElementChangedListener.
*/
@Override
public void elementChanged(final ElementChangedEvent event) {
try {
processDelta(event.getDelta());
} catch (ModelException e) {
DLTKUIPlugin.log(e.getStatus());
}
}
/**
* Processes a delta recursively. When more than two children are affected
* the tree is fully refreshed starting at this node. The delta is processed
* in the current thread but the viewer updates are posted to the UI thread.
*/
protected void processDelta(IModelElementDelta delta) throws ModelException {
int kind = delta.getKind();
int flags = delta.getFlags();
final IModelElement element = delta.getElement();
final boolean isElementValidForView = fBrowsingPart
.isValidElement(element);
// if (!getProvideWorkingCopy() && element instanceof ISourceModule &&
// ((ISourceModule)element).isWorkingCopy())
// return;
if (element != null
&& element.getElementType() == IModelElement.SOURCE_MODULE
&& !isOnBuildPath((ISourceModule) element))
return;
// handle open and closing of a solution or project
if (((flags & IModelElementDelta.F_CLOSED) != 0)
|| ((flags & IModelElementDelta.F_OPENED) != 0)) {
postRefresh(null);
return;
}
if (kind == IModelElementDelta.REMOVED) {
Object parent = internalGetParent(element);
if (isElementValidForView) {
if (element instanceof ISourceModule
&& !((ISourceModule) element).isWorkingCopy()) {
postRefresh(null);
} else if (element instanceof ISourceModule
&& ((ISourceModule) element).isWorkingCopy()) {
// if (getProvideWorkingCopy())
postRefresh(null);
} else if (parent instanceof ISourceModule /*
* &&
* getProvideWorkingCopy()
*/
&& !((ISourceModule) parent).isWorkingCopy()) {
if (element instanceof ISourceModule
&& ((ISourceModule) element).isWorkingCopy()) {
// working copy removed from system - refresh
postRefresh(null);
}
} else if (element instanceof ISourceModule
&& ((ISourceModule) element).isWorkingCopy()
&& parent != null && parent.equals(fInput))
// closed editor - removing working copy
postRefresh(null);
else
postRemove(element);
}
if (fBrowsingPart.isAncestorOf(element, fInput)) {
if (element instanceof ISourceModule
&& ((ISourceModule) element).isWorkingCopy()) {
postAdjustInputAndSetSelection(((IModelElement) fInput)
.getPrimaryElement());
} else
postAdjustInputAndSetSelection(null);
}
if (fInput != null && fInput.equals(element))
postRefresh(null);
if (parent instanceof IScriptFolder
&& fBrowsingPart.isValidElement(parent)) {
// refresh if package gets empty (might be filtered)
if (isScriptFolderEmpty((IScriptFolder) parent)
&& fViewer.testFindItem(parent) != null)
postRefresh(null);
}
return;
}
if (kind == IModelElementDelta.ADDED
&& delta.getMovedFromElement() != null
&& element instanceof ISourceModule)
return;
if (kind == IModelElementDelta.ADDED) {
if (isElementValidForView) {
Object parent = internalGetParent(element);
if (element instanceof ISourceModule
&& !((ISourceModule) element).isWorkingCopy()) {
postAdd(parent, ((ISourceModule) element).getTypes());
} else if (parent instanceof ISourceModule
&& !((ISourceModule) parent).isWorkingCopy()) {
// do nothing
} else if (element instanceof ISourceModule
&& ((ISourceModule) element).isWorkingCopy()) {
// new working copy comes to live
postRefresh(null);
} else
postAdd(parent, element);
} else if (fInput == null) {
IModelElement newInput = fBrowsingPart
.findInputForJavaElement(element);
if (newInput != null)
postAdjustInputAndSetSelection(element);
} else if (element instanceof IType
&& fBrowsingPart.isValidInput(element)) {
IModelElement cu1 = element
.getAncestor(IModelElement.SOURCE_MODULE);
IModelElement cu2 = ((IModelElement) fInput)
.getAncestor(IModelElement.SOURCE_MODULE);
if (cu1 != null && cu2 != null && cu1.equals(cu2))
postAdjustInputAndSetSelection(element);
}
return;
}
if (kind == IModelElementDelta.CHANGED) {
if (fInput != null && fInput.equals(element)
&& (flags & IModelElementDelta.F_CHILDREN) != 0
&& (flags & IModelElementDelta.F_FINE_GRAINED) != 0) {
postRefresh(null, true);
return;
}
if (isElementValidForView
&& (flags & IModelElementDelta.F_MODIFIERS) != 0) {
postUpdateIcon(element);
}
}
if (isBuildPathChange(delta))
// throw the towel and do a full refresh
postRefresh(null);
if ((flags & IModelElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0
&& fInput instanceof IModelElement) {
IProjectFragment pkgRoot = (IProjectFragment) element;
IModelElement inputsParent = ((IModelElement) fInput)
.getAncestor(IModelElement.PROJECT_FRAGMENT);
if (pkgRoot.equals(inputsParent))
postRefresh(null);
}
// the source attachment of a JAR has changed
// if (element instanceof IProjectFragment && (((flags &
// IModelElementDelta.F_SOURCEATTACHED) != 0 || ((flags &
// IModelElementDelta.F_SOURCEDETACHED)) != 0)))
// postUpdateIcon(element);
IModelElementDelta[] affectedChildren = delta.getAffectedChildren();
if (affectedChildren.length > 1) {
// a package fragment might become non empty refresh from the parent
if (element instanceof IScriptFolder) {
IModelElement parent = (IModelElement) internalGetParent(element);
// avoid posting a refresh to an invisible parent
if (element.equals(fInput)) {
postRefresh(element);
} else {
postRefresh(parent);
}
}
// more than one child changed, refresh from here downwards
if (element instanceof IProjectFragment && isElementValidForView) {
postRefresh(skipProjectProjectFragment((IProjectFragment) element));
return;
}
}
for (int i = 0; i < affectedChildren.length; i++) {
processDelta(affectedChildren[i]);
}
}
private boolean isOnBuildPath(ISourceModule element) throws ModelException {
IScriptProject project = element.getScriptProject();
if (project == null || !project.exists())
return false;
return project.isOnBuildpath(element);
}
/**
* Updates the package icon
*/
private void postUpdateIcon(final IModelElement element) {
postRunnable(() -> {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed())
fViewer.update(element,
new String[] { IBasicPropertyConstants.P_IMAGE });
});
}
private void postRefresh(final Object root, final boolean updateLabels) {
postRunnable(() -> {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed())
fViewer.refresh(root, updateLabels);
});
}
private void postRefresh(final Object root) {
postRefresh(root, false);
}
private void postAdd(final Object parent, final Object element) {
postAdd(parent, new Object[] { element });
}
private void postAdd(final Object parent, final Object[] elements) {
if (elements == null || elements.length <= 0)
return;
postRunnable(() -> {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
Object[] newElements = getNewElements(elements);
if (fViewer instanceof AbstractTreeViewer) {
if (fViewer.testFindItem(parent) == null) {
Object root = ((AbstractTreeViewer) fViewer).getInput();
if (root != null)
((AbstractTreeViewer) fViewer).add(root,
newElements);
} else
((AbstractTreeViewer) fViewer).add(parent, newElements);
} else if (fViewer instanceof ListViewer)
((ListViewer) fViewer).add(newElements);
else if (fViewer instanceof TableViewer)
((TableViewer) fViewer).add(newElements);
if (fViewer.testFindItem(elements[0]) != null)
fBrowsingPart.adjustInputAndSetSelection(elements[0]);
}
});
}
private Object[] getNewElements(Object[] elements) {
int elementsLength = elements.length;
ArrayList result = new ArrayList(elementsLength);
for (int i = 0; i < elementsLength; i++) {
Object element = elements[i];
if (fViewer.testFindItem(element) == null)
result.add(element);
}
return result.toArray();
}
private void postRemove(final Object element) {
postRemove(new Object[] { element });
}
private void postRemove(final Object[] elements) {
if (elements.length <= 0)
return;
postRunnable(() -> {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
if (fViewer instanceof AbstractTreeViewer)
((AbstractTreeViewer) fViewer).remove(elements);
else if (fViewer instanceof ListViewer)
((ListViewer) fViewer).remove(elements);
else if (fViewer instanceof TableViewer)
((TableViewer) fViewer).remove(elements);
}
});
}
private void postAdjustInputAndSetSelection(final Object element) {
postRunnable(() -> {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
ctrl.setRedraw(false);
fBrowsingPart.adjustInputAndSetSelection(element);
ctrl.setRedraw(true);
}
});
}
protected void startReadInDisplayThread() {
if (isDisplayThread())
fReadsInDisplayThread++;
}
protected void finishedReadInDisplayThread() {
if (isDisplayThread())
fReadsInDisplayThread--;
}
private boolean isDisplayThread() {
Control ctrl = fViewer.getControl();
if (ctrl == null)
return false;
Display currentDisplay = Display.getCurrent();
return currentDisplay != null
&& currentDisplay.equals(ctrl.getDisplay());
}
private void postRunnable(final Runnable r) {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
fBrowsingPart.setProcessSelectionEvents(false);
try {
if (isDisplayThread() && fReadsInDisplayThread == 0)
ctrl.getDisplay().syncExec(r);
else
ctrl.getDisplay().asyncExec(r);
} finally {
fBrowsingPart.setProcessSelectionEvents(true);
}
}
}
/**
* Returns the parent for the element.
* <p>
* Note: This method will return a working copy if the parent is a working
* copy. The super class implementation returns the original element
* instead.
* </p>
*/
@Override
protected Object internalGetParent(Object element) {
if (element instanceof IScriptProject) {
return ((IScriptProject) element).getModel();
}
// try to map resources to the containing package fragment
if (element instanceof IResource) {
IResource parent = ((IResource) element).getParent();
Object jParent = DLTKCore.create(parent);
if (jParent != null)
return jParent;
return parent;
}
// for package fragments that are contained in a project package
// fragment
// we have to skip the package fragment root as the parent.
if (element instanceof IScriptFolder) {
IProjectFragment parent = (IProjectFragment) ((IScriptFolder) element)
.getParent();
return skipProjectProjectFragment(parent);
}
if (element instanceof IModelElement)
return ((IModelElement) element).getParent();
return null;
}
}