/*******************************************************************************
* Copyright (c) 2000, 2006 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.rubypeople.rdt.internal.ui.packageview;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
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.rubypeople.rdt.core.ElementChangedEvent;
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.RubyCore;
import org.rubypeople.rdt.core.RubyModelException;
import org.rubypeople.rdt.internal.ui.RubyPlugin;
/**
* Content provider which provides package fragments for hierarchical
* Package Explorer layout.
*
* @since 2.1
*/
public class SourceFolderProvider implements IPropertyChangeListener {
private TreeViewer fViewer;
private boolean fFoldPackages;
public SourceFolderProvider() {
fFoldPackages= arePackagesFoldedInHierarchicalLayout();
RubyPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
}
/*
* @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(Object)
*/
public Object[] getChildren(Object parentElement) {
try {
if (parentElement instanceof IFolder) {
IResource[] resources= ((IFolder) parentElement).members();
return filter(getFolders(resources)).toArray();
} else if (parentElement instanceof IRubyElement) {
IRubyElement iRubyElement= (IRubyElement) parentElement;
int type= iRubyElement.getElementType();
switch (type) {
case IRubyElement.RUBY_PROJECT: {
IRubyProject project= (IRubyProject) iRubyElement;
ISourceFolderRoot root= project.findSourceFolderRoot(project.getPath());
if (root != null) {
List children= getTopLevelChildren(root);
return filter(children).toArray();
}
break;
}
case IRubyElement.SOURCE_FOLDER_ROOT: {
ISourceFolderRoot root= (ISourceFolderRoot) parentElement;
if (root.exists()) {
return filter(getTopLevelChildren(root)).toArray();
}
break;
}
case IRubyElement.SOURCE_FOLDER: {
ISourceFolder packageFragment = (ISourceFolder) parentElement;
if (!packageFragment.isDefaultPackage()) {
ISourceFolderRoot root= (ISourceFolderRoot) packageFragment.getParent();
List children = getPackageChildren(root, packageFragment);
return filter(children).toArray();
}
break;
}
default :
// do nothing
}
}
} catch (CoreException e) {
RubyPlugin.log(e);
}
return new Object[0];
}
private List filter(List children) throws RubyModelException {
if (fFoldPackages) {
int size= children.size();
for (int i = 0; i < size; i++) {
Object curr= children.get(i);
if (curr instanceof ISourceFolder) {
ISourceFolder fragment = (ISourceFolder) curr;
if (!fragment.isDefaultPackage() && isEmpty(fragment)) {
ISourceFolder collapsed= getCollapsed(fragment);
if (collapsed != null) {
children.set(i, collapsed); // replace with collapsed
}
}
}
}
}
return children;
}
private ISourceFolder getCollapsed(ISourceFolder pack) throws RubyModelException {
IRubyElement[] children= ((ISourceFolderRoot) pack.getParent()).getChildren();
ISourceFolder child= getSinglePackageChild(pack, children);
while (child != null && isEmpty(child)) {
ISourceFolder collapsed= getSinglePackageChild(child, children);
if (collapsed == null) {
return child;
}
child= collapsed;
}
return child;
}
private boolean isEmpty(ISourceFolder fragment) throws RubyModelException {
return !fragment.containsRubyResources() && fragment.getNonRubyResources().length == 0;
}
private static ISourceFolder getSinglePackageChild(ISourceFolder fragment, IRubyElement[] children) {
String prefix= fragment.getElementName() + '.';
int prefixLen= prefix.length();
ISourceFolder found= null;
for (int i= 0; i < children.length; i++) {
IRubyElement element= children[i];
String name= element.getElementName();
if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf('.', prefixLen) == -1) {
if (found == null) {
found= (ISourceFolder) element;
} else {
return null;
}
}
}
return found;
}
private static List getPackageChildren(ISourceFolderRoot parent, ISourceFolder fragment) throws RubyModelException {
IRubyElement[] children= parent.getChildren();
ArrayList list= new ArrayList(children.length);
String prefix= fragment.getElementName() + File.separatorChar;
int prefixLen= prefix.length();
for (int i= 0; i < children.length; i++) {
IRubyElement element= children[i];
if (element instanceof ISourceFolder) { // see bug 134256
String name= element.getElementName();
if (name.startsWith(prefix) && name.length() > prefixLen && name.indexOf(File.separatorChar, prefixLen) == -1) {
list.add(element);
}
}
}
return list;
}
private static List<IRubyElement> getTopLevelChildren(ISourceFolderRoot root) throws RubyModelException {
IRubyElement[] elements= root.getChildren();
ArrayList<IRubyElement> topLevelElements= new ArrayList<IRubyElement>(elements.length);
for (int i= 0; i < elements.length; i++) {
IRubyElement iRubyElement= elements[i];
// for default src folder, grab it's scripts (unless it's part of project as src folder root)
if (iRubyElement instanceof ISourceFolder && (iRubyElement.getElementName().indexOf(File.separatorChar)==-1) && !(((ISourceFolder)iRubyElement).isDefaultPackage()) ) {
topLevelElements.add(iRubyElement);
}
}
return topLevelElements;
}
private List getFolders(IResource[] resources) throws RubyModelException {
List list= new ArrayList(resources.length);
for (int i= 0; i < resources.length; i++) {
IResource resource= resources[i];
if (resource instanceof IFolder) {
IFolder folder= (IFolder) resource;
IRubyElement element= RubyCore.create(folder);
if (element instanceof ISourceFolder) {
list.add(element);
}
}
}
return list;
}
/*
* @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(Object)
*/
public Object getParent(Object element) {
if (element instanceof ISourceFolder) {
ISourceFolder frag = (ISourceFolder) element;
//@Changed: a fix, before: if(frag.exists() && isEmpty(frag))
return filterParent(getActualParent(frag));
}
return null;
}
private Object getActualParent(ISourceFolder fragment) {
try {
if (fragment.exists()) {
IRubyElement parent = fragment.getParent();
if ((parent instanceof ISourceFolderRoot) && parent.exists()) {
ISourceFolderRoot root = (ISourceFolderRoot) parent;
if (root.isExternal()) {
return findNextLevelParentByElementName(fragment);
} else {
IResource resource = fragment.getUnderlyingResource();
if ((resource != null) && (resource instanceof IFolder)) {
IFolder folder = (IFolder) resource;
IResource res = folder.getParent();
IRubyElement el = RubyCore.create(res);
if (el != null) {
return el;
} else {
return res;
}
}
}
return parent;
}
}
} catch (RubyModelException e) {
RubyPlugin.log(e);
}
return null;
}
private Object filterParent(Object parent) {
if (fFoldPackages && (parent!=null)) {
try {
if (parent instanceof ISourceFolder) {
ISourceFolder fragment = (ISourceFolder) parent;
if (isEmpty(fragment) && hasSingleChild(fragment)) {
return filterParent(getActualParent(fragment));
}
}
} catch (RubyModelException e) {
RubyPlugin.log(e);
}
}
return parent;
}
private boolean hasSingleChild(ISourceFolder fragment) {
return getChildren(fragment).length==1;
}
private Object findNextLevelParentByElementName(ISourceFolder child) {
String name= child.getElementName();
int index= name.lastIndexOf(File.separatorChar);
if (index != -1) {
String realParentName= name.substring(0, index);
ISourceFolder element= ((ISourceFolderRoot) child.getParent()).getSourceFolder(realParentName);
if (element.exists()) {
return element;
}
}
return child.getParent();
}
/*
* @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(Object)
*/
public boolean hasChildren(Object element) {
if (element instanceof ISourceFolder) {
ISourceFolder fragment= (ISourceFolder) element;
if(fragment.isDefaultPackage())
return false;
}
return getChildren(element).length > 0;
}
/*
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(Object)
*/
public Object[] getElements(Object inputElement) {
return getChildren(inputElement);
}
/*
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
public void dispose() {
RubyPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
}
/**
* Called when the view is closed and opened.
*
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(Viewer, Object, Object)
*/
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
fViewer= (TreeViewer)viewer;
}
/*
* @see org.eclipse.jdt.core.IElementChangedListener#elementChanged(org.eclipse.jdt.core.ElementChangedEvent)
*/
public void elementChanged(ElementChangedEvent event) {
processDelta(event.getDelta());
}
public void processDelta(IRubyElementDelta delta) {
int kind = delta.getKind();
final IRubyElement element = delta.getElement();
if (element instanceof ISourceFolder) {
if (kind == IRubyElementDelta.REMOVED) {
postRunnable(new Runnable() {
public void run() {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
if (!fFoldPackages)
fViewer.remove(element);
else
refreshGrandParent(element);
}
}
});
return;
} else if (kind == IRubyElementDelta.ADDED) {
final Object parent = getParent(element);
if (parent != null) {
postRunnable(new Runnable() {
public void run() {
Control ctrl = fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
if (!fFoldPackages)
fViewer.add(parent, element);
else
refreshGrandParent(element);
}
}
});
}
return;
}
}
}
// XXX: needs to be revisited - might be a performance issue
private void refreshGrandParent(final IRubyElement element) {
if (element instanceof ISourceFolder) {
Object gp= getGrandParent((ISourceFolder)element);
if (gp instanceof IRubyElement) {
IRubyElement el = (IRubyElement) gp;
if(el.exists())
fViewer.refresh(gp);
} else if (gp instanceof IFolder) {
IFolder folder= (IFolder)gp;
if (folder.exists())
fViewer.refresh(folder);
}
}
}
private Object getGrandParent(ISourceFolder element) {
Object parent= findNextLevelParentByElementName(element);
if (parent instanceof ISourceFolderRoot) {
ISourceFolderRoot root= (ISourceFolderRoot) parent;
if(isRootProject(root))
return root.getRubyProject();
else return root;
}
Object grandParent= getParent(parent);
if(grandParent==null){
return parent;
}
return grandParent;
}
private boolean isRootProject(ISourceFolderRoot root) {
if (ISourceFolderRoot.DEFAULT_PACKAGEROOT_PATH.equals(root.getElementName()))
return true;
return false;
}
private void postRunnable(final Runnable r) {
Control ctrl= fViewer.getControl();
if (ctrl != null && !ctrl.isDisposed()) {
Display currentDisplay= Display.getCurrent();
if (currentDisplay != null && currentDisplay.equals(ctrl.getDisplay()))
ctrl.getDisplay().syncExec(r);
else
ctrl.getDisplay().asyncExec(r);
}
}
/*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(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);
}
}
}
private boolean arePackagesFoldedInHierarchicalLayout(){
// TODO Uncomment and allow folding packages preference setting?
// return PreferenceConstants.getPreferenceStore().getBoolean(PreferenceConstants.APPEARANCE_FOLD_PACKAGES_IN_PACKAGE_EXPLORER);
return false;
}
}