package org.rubypeople.rdt.internal.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.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; import org.rubypeople.rdt.core.ElementChangedEvent; import org.rubypeople.rdt.core.IElementChangedListener; import org.rubypeople.rdt.core.IImportContainer; import org.rubypeople.rdt.core.IParent; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyElementDelta; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.ISourceFolder; import org.rubypeople.rdt.core.ISourceFolderRoot; import org.rubypeople.rdt.core.ISourceReference; import org.rubypeople.rdt.core.IType; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.internal.core.LogicalType; import org.rubypeople.rdt.internal.core.RubyBlock; import org.rubypeople.rdt.internal.corext.util.RubyModelUtil; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.ui.StandardRubyElementContentProvider; public class RubyBrowsingContentProvider extends StandardRubyElementContentProvider implements IElementChangedListener { private RubyBrowsingPart fBrowsingPart; private StructuredViewer fViewer; private int fReadsInDisplayThread; private Object fInput; public RubyBrowsingContentProvider(boolean provideMembers, RubyBrowsingPart browsingPart) { super(provideMembers); fBrowsingPart = browsingPart; fViewer = fBrowsingPart.getViewer(); RubyCore.addElementChangedListener(this); } public boolean hasChildren(Object element) { startReadInDisplayThread(); try { return super.hasChildren(element); } finally { finishedReadInDisplayThread(); } } 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 result; } if (element instanceof ISourceFolder) return getFolderContents((ISourceFolder) element); if (fProvideMembers && element instanceof IType) return removeBlocks(getChildren((IType) element)); if (fProvideMembers && element instanceof ISourceReference && element instanceof IParent) return removeBlocks(removeImportDeclarations(super.getChildren(element))); if (element instanceof IRubyProject) return getSourceFolderRoots((IRubyProject) element); return removeBlocks(super.getChildren(element)); } catch (RubyModelException e) { return NO_CHILDREN; } finally { finishedReadInDisplayThread(); } } private Object[] removeImportDeclarations(Object[] members) { ArrayList tempResult = new ArrayList(members.length); for (int i = 0; i < members.length; i++) if (!(members[i] instanceof IImportContainer)) tempResult.add(members[i]); return tempResult.toArray(); } protected Object[] getFolderContents(ISourceFolder fragment) throws RubyModelException { ISourceReference[] sourceRefs = fragment.getRubyScripts(); Object[] result = new Object[0]; for (int i = 0; i < sourceRefs.length; i++) result = concatenate(result, getChildren(sourceRefs[i])); result = includeSubtypes(result); result = convertToLogicalTypes(result); return result; } private Object[] includeSubtypes(Object[] result) throws RubyModelException { List<Object> list = new ArrayList<Object>(); for (int j = 0; j < result.length; j++) { if (result[j] instanceof IType) { IType type = (IType) result[j]; list.addAll(Arrays.asList(includeSubtypes(type.getTypes()))); } list.add(result[j]); } return list.toArray(new Object[list.size()]); } private Object[] convertToLogicalTypes(Object[] result) { Map<String, IType> uniques = new HashMap<String, IType>(); for (int j = 0; j < result.length; j++) { if (result[j] instanceof IType) { IType type = (IType) result[j]; String name = type.getFullyQualifiedName(); if (!uniques.containsKey(name)) { uniques.put(name, type); } else { IType other = uniques.get(name); LogicalType logical = new LogicalType(new IType[] { other, type }); uniques.put(name, logical); } } } Collection values = uniques.values(); result = values.toArray(new Object[values.size()]); return result; } protected Object[] getSourceFolderRoots(IRubyProject project) throws RubyModelException { if (!project.getProject().isOpen()) return NO_CHILDREN; ISourceFolderRoot[] roots = project.getSourceFolderRoots(); 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++) { ISourceFolderRoot 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.getNonRubyResources()); } private Object[] getChildren(IType type) throws RubyModelException { IParent parent = type.getRubyScript(); if (type.getDeclaringType() != null) return type.getChildren(); // Add import declarations IRubyElement[] members = parent.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(); } private boolean isDisplayThread() { Control ctrl = fViewer.getControl(); if (ctrl == null) return false; Display currentDisplay = Display.getCurrent(); return currentDisplay != null && currentDisplay.equals(ctrl.getDisplay()); } protected void startReadInDisplayThread() { if (isDisplayThread()) fReadsInDisplayThread++; } protected void finishedReadInDisplayThread() { if (isDisplayThread()) fReadsInDisplayThread--; } /* * (non-Javadoc) Method declared on IContentProvider. */ 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. */ public void dispose() { super.dispose(); RubyCore.removeElementChangedListener(this); } /** * 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> */ protected Object internalGetParent(Object element) { if (element instanceof IRubyProject) { return ((IRubyProject) element).getRubyModel(); } // try to map resources to the containing package fragment if (element instanceof IResource) { IResource parent = ((IResource) element).getParent(); Object jParent = RubyCore.create(parent); if (jParent != null) return jParent; return parent; } if (element instanceof IRubyElement) return ((IRubyElement) element).getParent(); return null; } /* * (non-Javadoc) Method declared on IElementChangedListener. */ public void elementChanged(final ElementChangedEvent event) { try { processDelta(event.getDelta()); } catch (RubyModelException e) { RubyPlugin.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(IRubyElementDelta delta) throws RubyModelException { int kind = delta.getKind(); int flags = delta.getFlags(); final IRubyElement element = delta.getElement(); final boolean isElementValidForView = fBrowsingPart.isValidElement(element); if (!getProvideWorkingCopy() && element instanceof IRubyScript && ((IRubyScript) element).isWorkingCopy()) return; if (element != null && element.getElementType() == IRubyElement.SCRIPT && !isOnClassPath((IRubyScript) element)) return; // handle open and closing of a solution or project if (((flags & IRubyElementDelta.F_CLOSED) != 0) || ((flags & IRubyElementDelta.F_OPENED) != 0)) { postRefresh(null); return; } if (kind == IRubyElementDelta.REMOVED) { Object parent = internalGetParent(element); if (isElementValidForView) { if (element instanceof IRubyScript && !((IRubyScript) element).isWorkingCopy()) { postRefresh(null); } else if (element instanceof IRubyScript && ((IRubyScript) element).isWorkingCopy()) { if (getProvideWorkingCopy()) postRefresh(null); } else if (parent instanceof IRubyScript && getProvideWorkingCopy() && !((IRubyScript) parent).isWorkingCopy()) { if (element instanceof IRubyScript && ((IRubyScript) element).isWorkingCopy()) { // working copy removed from system - refresh postRefresh(null); } } else if (element instanceof IRubyScript && ((IRubyScript) 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 IRubyScript && ((IRubyScript) element).isWorkingCopy()) { postAdjustInputAndSetSelection(RubyModelUtil.toOriginal((IRubyElement) fInput)); } else postAdjustInputAndSetSelection(null); } if (fInput != null && fInput.equals(element)) postRefresh(null); return; } if (kind == IRubyElementDelta.ADDED && delta.getMovedFromElement() != null && element instanceof IRubyScript) return; if (kind == IRubyElementDelta.ADDED) { if (isElementValidForView) { Object parent = internalGetParent(element); if (element instanceof IRubyScript && !((IRubyScript) element).isWorkingCopy()) { postAdd(parent, ((IRubyScript) element).getTypes()); } else if (parent instanceof IRubyScript && getProvideWorkingCopy() && !((IRubyScript) parent).isWorkingCopy()) { // do nothing } else if (element instanceof IRubyScript && ((IRubyScript) element).isWorkingCopy()) { // new working copy comes to live postRefresh(null); } else postAdd(parent, element); } else if (fInput == null) { IRubyElement newInput = fBrowsingPart.findInputForRubyElement(element); if (newInput != null) postAdjustInputAndSetSelection(element); } else if (element instanceof IType && fBrowsingPart.isValidInput(element)) { IRubyElement cu1 = element.getAncestor(IRubyElement.SCRIPT); IRubyElement cu2 = ((IRubyElement) fInput).getAncestor(IRubyElement.SCRIPT); if (cu1 != null && cu2 != null && cu1.equals(cu2)) postAdjustInputAndSetSelection(element); } return; } if (kind == IRubyElementDelta.CHANGED) { if (fInput != null && fInput.equals(element) && (flags & IRubyElementDelta.F_CHILDREN) != 0 && (flags & IRubyElementDelta.F_FINE_GRAINED) != 0) { postRefresh(null, true); return; } if (isElementValidForView && (flags & IRubyElementDelta.F_MODIFIERS) != 0) { postUpdateIcon(element); } } if (isClassPathChange(delta)) // throw the towel and do a full refresh postRefresh(null); IRubyElementDelta[] affectedChildren = delta.getAffectedChildren(); for (int i = 0; i < affectedChildren.length; i++) { processDelta(affectedChildren[i]); } } private boolean isOnClassPath(IRubyScript element) throws RubyModelException { IRubyProject project = element.getRubyProject(); if (project == null || !project.exists()) return false; return project.isOnLoadpath(element); } /** * Updates the package icon */ private void postUpdateIcon(final IRubyElement element) { postRunnable(new Runnable() { public void run() { 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(new Runnable() { public void run() { 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(new Runnable() { public void run() { 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(new Runnable() { public void run() { 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(new Runnable() { public void run() { Control ctrl = fViewer.getControl(); if (ctrl != null && !ctrl.isDisposed()) { ctrl.setRedraw(false); fBrowsingPart.adjustInputAndSetSelection(element); ctrl.setRedraw(true); } } }); } 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); } } } }