/******************************************************************************
* Copyright (c) 2010-2013, Linagora
*
* 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:
* Linagora - initial API and implementation
*******************************************************************************/
package com.ebmwebsourcing.petals.common.internal.provisional.projectscnf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerFilter;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.navigator.CommonViewer;
import com.ebmwebsourcing.petals.common.internal.PetalsCommonPlugin;
import com.ebmwebsourcing.petals.common.internal.projectscnf.EmptyJavaPackageFilter;
import com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener;
import com.ebmwebsourcing.petals.common.internal.projectscnf.PetalsProjectManager;
import com.ebmwebsourcing.petals.common.internal.projectscnf.StatisticsTimer;
import com.ebmwebsourcing.petals.common.internal.provisional.utils.JavaUtils;
/**
* The base content provider to use for all the contributions to the Petals projects view.
* <p>
* This content provider relies on a project manager, which caches and manages associations between
* a project category and projects. This content provider supports resources and Java elements for
* Java projects (in particular source folders and Java packages).
* </p>
* <p>
* This content provider is notified every time a work space change is made.
* Viewer modifications are grouped and executed in a separate thread to gain performances.
* </p>
*
* <p>
* Given the complexity of all the cases it has to handle, tests should be
* automated for this content provider. These tests were performed manually for the
* moment. There are listed here after.
* </p>
* <ul>
* <li>SU project, not Java: create a file at the root.</li>
* <li>SU project, not Java: delete a file at the root.</li>
* <li>SU project, not Java: create a folder at the root.</li>
* <li>SU project, not Java: delete a folder at the root.</li>
*
* <li>SU project, Java: create a file at the root.</li>
* <li>SU project, Java: delete a file at the root.</li>
* <li>SU project, Java: create a folder at the root.</li>
* <li>SU project, Java: delete a folder at the root.</li>
*
* <li>SU project, Java: create a file in a source folder.</li>
* <li>SU project, Java: delete a file in a source folder.</li>
* <li>SU project, Java: create a folder in a source folder.</li>
* <li>SU project, Java: delete a folder in a source folder.</li>
* <li>SU project, Java: create a package in a source folder.</li>
* <li>SU project, Java: delete a package in a source folder.</li>
*
* <li>SU project, Java: create a file in a Java package.</li>
* <li>SU project, Java: delete a file in a Java package.</li>
* <li>SU project, Java: create a folder in a Java package.</li>
* <li>SU project, Java: delete a folder in a Java package.</li>
* <li>SU project, Java: create a package in a Java package.</li>
* <li>SU project, Java: delete a package in a Java package.</li>
*
* <li>SU project, Java: create a class file in an existing Java package.</li>
* <li>SU project, Java: create a class file in a non-existing Java package.</li>
* <li>SU project, Java: delete a class file from a Java package.</li>
*
* <li>Create a SU project with a wizard.</li>
* <li>Create a SA project with a wizard.</li>
* <li>Create a SL project with a wizard.</li>
* <li>Create a component project with a wizard.</li>
* <li>Delete a project from the workspace (not from the disk) and import it back in the workspace.</li>
*
* <li>(Sub-classes) Create an association between a SU and a SA project.</li>
* <li>(Sub-classes) Delete the association between a SU and a SA project.</li>
* <li>(Sub-classes) Delete a SA project which is associated with a SU project.</li>
* <li>(Sub-classes) Delete a SU project which is associated with a SA project.</li>
* </ul>
*
* @author Vincent Zurczak - EBM WebSourcing
*/
public class PetalsProjectContentProvider implements ITreeContentProvider, IPetalsProjectResourceChangeListener {
protected Viewer viewer;
protected final List<Runnable> runnables = new ArrayList<Runnable> ();
/**
* Constructor.
*/
public PetalsProjectContentProvider() {
PetalsProjectManager.INSTANCE.addListener( this );
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider
* #getChildren(java.lang.Object)
*/
@Override
public Object[] getChildren( Object element ) {
StatisticsTimer statisticsTimer = new StatisticsTimer();
statisticsTimer.setEnabled( false );
statisticsTimer.start( " [ CHILDREN ] " );
String child;
if( element instanceof IResource )
child = ((IResource) element).getName();
else if( element instanceof PetalsProjectCategory )
child = ((PetalsProjectCategory) element).getLabel();
else
child = "";
statisticsTimer.track( " [ CHILDREN ] Getting children for " + element.getClass().getSimpleName() + " : " + child );
try {
if( element instanceof IResource
&& ! ((IResource) element).isAccessible())
return new Object[ 0 ];
// Work space root is not handled here but in another content provider
// Categories
if( element instanceof PetalsProjectCategory ) {
List<IProject> projects = PetalsProjectManager.INSTANCE.getProjects((PetalsProjectCategory) element);
if( projects == null )
return new Object[ 0 ];
if(((PetalsProjectCategory) element).isRootProjectVisible())
return projects.toArray();
List<Object> children = new ArrayList<Object> ();
for( IProject project : projects )
children.addAll( Arrays.asList( getChildren( project )));
return children.toArray();
}
// Package fragment root
if( element instanceof IPackageFragmentRoot ) {
try {
List<Object> list = new ArrayList<Object> ();
if( PetalsProjectManager.isJavaLayoutFlat()) {
list.addAll( findFlatChildren( element, (CommonViewer) this.viewer ));
} else {
list.addAll( findHierarchicalChildren( element, (CommonViewer) this.viewer));
}
return list.toArray();
} catch( JavaModelException e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
}
// Package fragment
if( element instanceof IPackageFragment ) {
try {
List<Object> list = new ArrayList<Object> ();
if( PetalsProjectManager.isJavaLayoutFlat()) {
list.addAll( findFlatChildren( element, (CommonViewer) this.viewer ));
} else {
list.addAll( findHierarchicalChildren( element, (CommonViewer) this.viewer));
}
return list.toArray();
} catch( JavaModelException e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
}
// Other resources
if( element instanceof IContainer ) {
// Prepare the main variables
IProject project = ((IContainer) element).getProject();
List<Object> children = new ArrayList<Object> ();
IJavaProject javaProject = null;
try {
if( project.hasNature( JavaCore.NATURE_ID ))
javaProject = JavaCore.create( project );
} catch( CoreException e1 ) {
PetalsCommonPlugin.log( e1, IStatus.ERROR );
}
// Java projects have a special treatment
try {
if( javaProject != null && element instanceof IProject )
children.addAll( Arrays.asList( javaProject.getAllPackageFragmentRoots()));
} catch( CoreException e1 ) {
PetalsCommonPlugin.log( e1, IStatus.ERROR );
}
// Default behavior
try {
List<IResource> members = Arrays.asList(((IContainer) element).members());
if( javaProject != null ) {
for( IResource res : members ) {
if( JavaCore.create( res ) == null )
children.add( res );
}
}
else
children.addAll( members );
} catch( CoreException e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
return children.toArray();
}
return new Object[ 0 ];
} finally {
statisticsTimer.stop( " [ CHILDREN ] " );
}
}
/**
* Finds one of the categories displayed by this content provider.
* @param id the ID of the category to find
* @return the {@link PetalsProjectCategory} or null if the ID does not match anything
*/
protected PetalsProjectCategory getProjectCategoryById( String id ) {
PetalsProjectCategory result = null;
for( PetalsProjectCategory cat : PetalsProjectManager.INSTANCE.getProjectCategories()) {
if( id.equals( cat.getId())) {
result = cat;
break;
}
}
return result;
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider
* #getParent(java.lang.Object)
*/
@Override
public Object getParent( Object element ) {
if( element instanceof PetalsProjectCategory )
return null;
// What do we have?
IResource res = null;
IJavaElement javaElement = null;
boolean isInJavaProject = false;
if( element instanceof IResource ) {
res = (IResource) element;
try {
if( ((IResource) element).exists())
isInJavaProject = res.getProject().hasNature( JavaCore.NATURE_ID );
if( isInJavaProject )
javaElement = JavaCore.create( res );
} catch( CoreException e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
} else if( element instanceof IJavaElement ) {
isInJavaProject = true;
javaElement = (IJavaElement) element;
res = javaElement.getResource();
}
// Projects should return a category
// Otherwise, selection won't work when we want to reveal a new project
if( res instanceof IProject ) {
List<PetalsProjectCategory> cats = PetalsProjectManager.INSTANCE.getCategories((IProject) res);
return cats != null && cats.size() > 0 ? cats.get( 0 ) : null;
}
// If the parent is a project and this root project is not displayed, then return the Petals category
if( res != null && res.getParent() instanceof IProject ) {
Object parent = getParent( res.getParent());
if( parent instanceof PetalsProjectCategory
&& ! ((PetalsProjectCategory) parent).isRootProjectVisible())
return parent;
}
// Java elements
if( javaElement != null ) {
// PETALSSTUD-165: Selection of Java resources fails for JSR-181
if( element instanceof IPackageFragment ) {
PetalsCnfPackageFragment ppf = PetalsProjectManager.INSTANCE.dirtyViewerMap.get( javaElement );
if( ppf != null ) {
if( ppf.getParent() instanceof PetalsCnfPackageFragment )
return ((PetalsCnfPackageFragment) ppf.getParent()).getFragment();
return ppf.getParent();
}
}
IJavaElement elt = javaElement.getParent();
if( elt instanceof IJavaProject )
return ((IJavaProject) elt).getProject();
return elt;
// PETALSSTUD-165: Selection of Java resources fails for JSR-181
}
// Otherwise, return the parent
if( res != null ) {
// Be careful, the parent of a resource may sometimes be a Java element (e.g. a file in a package)
res = res.getParent();
javaElement = JavaCore.create( res );
if( javaElement instanceof IJavaProject )
return res.getProject();
if( javaElement instanceof IPackageFragment || javaElement instanceof IPackageFragmentRoot )
return javaElement;
return res;
}
return null;
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.IContentProvider
* #inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
@Override
public void inputChanged( Viewer viewer, Object oldInput, Object newInput ) {
this.viewer = viewer;
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.IContentProvider
* #dispose()
*/
@Override
public void dispose() {
// nothing
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider
* #getElements(java.lang.Object)
*/
@Override
public Object[] getElements( Object inputElement ) {
PetalsProjectManager.INSTANCE.dirtyViewerMap.clear();
return new Object[ 0 ];
}
/*
* (non-Jsdoc)
* @see org.eclipse.jface.viewers.ITreeContentProvider
* #hasChildren(java.lang.Object)
*/
@Override
public boolean hasChildren( Object element ) {
StatisticsTimer statisticsTimer = new StatisticsTimer();
statisticsTimer.setEnabled( false );
statisticsTimer.start( " [ HAS CHILDREN ] " );
String child;
if( element instanceof IResource )
child = ((IResource) element).getName();
else if( element instanceof PetalsProjectCategory )
child = ((PetalsProjectCategory) element).getLabel();
else
child = "";
statisticsTimer.track( " [ HAS CHILDREN ] Element: " + element.getClass().getSimpleName() + " : " + child );
try {
boolean result = false;
try {
if( element instanceof IContainer ) {
result = ((IContainer) element).isAccessible()
&& ((IContainer) element).members().length > 0;
} else if( element instanceof IJavaElement ) {
if( element instanceof IPackageFragment
&& ((IPackageFragment) element).isDefaultPackage())
result = ((IPackageFragment) element).getNonJavaResources().length > 0 || ((IPackageFragment) element).hasChildren();
else
result = ((IJavaElement) element).getJavaModel().hasChildren();
} else if( element instanceof PetalsProjectCategory ) {
List<IProject> projects = PetalsProjectManager.INSTANCE.getProjects((PetalsProjectCategory) element);
result = projects != null && projects.size() > 0;
}
} catch( CoreException e ) {
PetalsCommonPlugin.log( e, IStatus.ERROR );
}
return result;
} finally {
statisticsTimer.stop( " [ HAS CHILDREN ] " );
}
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #resourcesAdded(java.util.Collection)
*/
@Override
public void resourcesAdded( final Collection<IResource> resources ) {
if( !( this.viewer instanceof AbstractTreeViewer ))
return;
// Compute as much things as possible outside the UI thread
// Group elements to reduce the number of runnables
List<IProject> projects = new ArrayList<IProject> ();
Map<Object,List<IResource>> parentToResources = new HashMap<Object,List<IResource>> ();
for( final IResource res : resources ) {
if( res instanceof IProject ) {
projects.add((IProject) res);
} else {
final Object parent = getParent( res );
List<IResource> list = parentToResources.get( parent );
if( list == null )
list = new ArrayList<IResource>();
list.add( res );
if( parent != null )
parentToResources.put( parent, list );
else
PetalsCommonPlugin.log( "Could not find the parent for " + res, IStatus.ERROR );
}
}
// Update the viewer
final AbstractTreeViewer treeViewer = (AbstractTreeViewer) this.viewer;
// Projects
for( final IProject p : projects ) {
List<PetalsProjectCategory> cats = PetalsProjectManager.INSTANCE.getCategories( p );
if( cats == null )
cats = Collections.emptyList();
for( final PetalsProjectCategory cat : cats ) {
Runnable runnable = new Runnable() {
@Override
public void run() {
boolean visible = true;
if( treeViewer.getFilters() != null ) {
for( int i=0; visible && i<treeViewer.getFilters().length; i++ ) {
ViewerFilter filter = treeViewer.getFilters()[ i ];
visible = filter.select( treeViewer, cat, p );
}
}
if( visible ) {
treeViewer.getControl().setRedraw( false );
treeViewer.add( cat, p );
treeViewer.getControl().setRedraw( true );
}
}
};
this.runnables.add( runnable );
}
}
// Other resources
for( final Map.Entry<Object,List<IResource>> entry : parentToResources.entrySet()) {
Runnable runnable = new Runnable() {
@Override
public void run() {
treeViewer.getControl().setRedraw( false );
treeViewer.add( entry.getKey(), entry.getValue().toArray());
treeViewer.getControl().setRedraw( true );
}
};
this.runnables.add( runnable );
}
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #resourcesRemoved(java.util.Collection)
*/
@Override
public void resourcesRemoved( final Collection<IResource> resources ) {
if( !( this.viewer instanceof AbstractTreeViewer ))
return;
// Compute as much things as possible outside the UI thread
// Group elements to reduce the number of runnables
Map<Object,List<IResource>> parentToResources = new HashMap<Object,List<IResource>> ();
for( final IResource res : resources ) {
final Object parent = getParent( res );
List<IResource> list = parentToResources.get( parent );
if( list == null )
list = new ArrayList<IResource>();
list.add( res );
parentToResources.put( parent, list );
}
// Update the viewer
final AbstractTreeViewer treeViewer = (AbstractTreeViewer) this.viewer;
for( final Map.Entry<Object,List<IResource>> entry : parentToResources.entrySet()) {
Runnable runnable = new Runnable() {
@Override
public void run() {
treeViewer.getControl().setRedraw( false );
if( entry.getKey() == null )
treeViewer.remove( entry.getValue().toArray());
else
treeViewer.remove( entry.getKey(), entry.getValue().toArray());
treeViewer.getControl().setRedraw( true );
}
};
this.runnables.add( runnable );
}
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #resourceChanged(org.eclipse.core.resources.IResourceDelta)
*/
@Override
public void resourceChanged( IResourceDelta delta ) {
if(( delta.getFlags() & IResourceDelta.TYPE ) != 0 )
this.runnables.add( getRefreshRunnable( delta.getResource()));
if(( delta.getFlags() & IResourceDelta.OPEN ) != 0 ) {
if( delta.getResource().isAccessible())
this.runnables.add( getUpdateRunnable( delta.getResource()));
else
this.runnables.add( getRefreshRunnable( delta.getResource()));
}
if(( delta.getFlags()
& (IResourceDelta.SYNC | IResourceDelta.DESCRIPTION)) != 0 )
this.runnables.add( getUpdateRunnable( delta.getResource()));
if(( delta.getFlags() & IResourceDelta.REPLACED ) != 0 )
this.runnables.add( getRefreshRunnable( delta.getResource()));
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #resourceChanged(org.eclipse.core.resources.IResourceDelta)
*/
@Override
public void elementChanged( Object viewerObject ) {
this.runnables.add( getRefreshRunnable( viewerObject ));
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #markerChanged(org.eclipse.core.resources.IMarkerDelta[])
*/
@Override
public void markerChanged( IMarkerDelta[] markerDeltas ) {
for( IMarkerDelta delta : markerDeltas )
this.runnables.add( getMarkerRefreshRunnable( delta.getResource()));
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #prepareNotification()
*/
@Override
public void prepareNotification() {
// nothing
}
/*
* (non-Jsdoc)
* @see com.ebmwebsourcing.petals.common.internal.projectscnf.IPetalsProjectResourceChangeListener
* #terminateNotification()
*/
@Override
public void terminateNotification() {
if( this.viewer.getControl() == null
|| this.viewer.getControl().isDisposed()
|| this.runnables.isEmpty())
return;
// Duplicate the list to avoid concurrent modifications
final List<Runnable> myRunnables = new ArrayList<Runnable> ();
myRunnables.addAll( this.runnables );
// Synchronous execution - not asynchronous because otherwise, it is not
// possible to select resources at the end of a wizard (refresh runnables may be executed after the selection).
// And the selected element may not be visible at the time when it is tried to be selected.
this.viewer.getControl().getDisplay().syncExec( new Runnable(){
@Override
public void run() {
Control ctrl = PetalsProjectContentProvider.this.viewer.getControl();
if( ctrl != null && ! ctrl.isDisposed()) {
for( Runnable runnable : myRunnables )
runnable.run();
}
}
});
// We can clear them now
this.runnables.clear();
}
/**
* Return a runnable for refreshing a resource.
* @param element the element to refresh
* @return Runnable
*/
protected final Runnable getRefreshRunnable( final Object element ) {
return new Runnable(){
@Override
public void run() {
((StructuredViewer) PetalsProjectContentProvider.this.viewer).refresh( element, true );
}
};
}
/**
* Returns a runnable for refreshing the markers on a resource.
* @param resource
* @return Runnable
*/
protected final Runnable getMarkerRefreshRunnable( final IResource resource ) {
return new Runnable(){
@Override
public void run() {
IResource res = resource;
while( res != null && !( res instanceof IWorkspaceRoot )) {
Object toRefresh;
IJavaElement elt;
if( res instanceof IProject )
toRefresh = res;
else if(( elt = JavaCore.create( res )) != null )
toRefresh = elt;
else
toRefresh = res;
((StructuredViewer) PetalsProjectContentProvider.this.viewer).refresh( toRefresh, true );
res = res.getParent();
}
}
};
}
/**
* Return a runnable for updating a resource.
* @param resource
* @return Runnable
*/
protected final Runnable getUpdateRunnable( final IResource resource ) {
return new Runnable(){
@Override
public void run() {
((StructuredViewer) PetalsProjectContentProvider.this.viewer).update( resource, null );
}
};
}
/**
* Finds the children to display in <i>flat</i> mode.
* <p>
* This method is also in charge of filtering empty packages.
* </p>
*
* @param elt a package fragment or a package fragment root
* @param viewer the viewer
* @return a non-null list of children
* @throws JavaModelException
*/
private static List<Object> findFlatChildren( Object elt, CommonViewer viewer ) throws JavaModelException {
// Include empty packages is handled in the content viewer
boolean includeEmpty = true;
for( ViewerFilter filter : viewer.getFilters()) {
if( filter instanceof EmptyJavaPackageFilter )
includeEmpty = false;
}
// Get the elements to show
List<Object> children = new ArrayList<Object> ();
Object[] javaChildren = null;
if( elt instanceof IPackageFragment ) {
javaChildren = ((IPackageFragment) elt).getChildren();
Object[] nonJavaResources = ((IPackageFragment) elt).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
} else if( elt instanceof IPackageFragmentRoot ) {
javaChildren = ((IPackageFragmentRoot) elt).getChildren();
Object[] nonJavaResources = ((IPackageFragmentRoot) elt).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
} else {
throw new JavaModelException( new Exception( "Expected a package fragment or package fragment root." ), IStatus.ERROR );
}
// Filter the Java elements
if( includeEmpty ) {
children.addAll( Arrays.asList( javaChildren ));
} else {
for( Object o : javaChildren ) {
if( o instanceof IPackageFragment ) {
if(((IPackageFragment) o).getNonJavaResources().length > 0
|| ((IPackageFragment) o).getChildren().length > 0 )
children.add( o );
} else {
children.add( o );
}
}
}
return children;
}
/**
* Finds the children to display in <i>hierarchical</i> mode.
* <p>
* This method is also in charge of filtering empty packages.
* </p>
*
* @param elt a package fragment or a package fragment root
* @param viewer the viewer
* @return a non-null list of children
* @throws JavaModelException
*/
private static List<Object> findHierarchicalChildren( Object elt, CommonViewer viewer ) throws JavaModelException {
// Include empty packages is handled in the content viewer
boolean includeEmpty = true;
for( ViewerFilter filter : viewer.getFilters()) {
if( filter instanceof EmptyJavaPackageFilter ) {
includeEmpty = false;
break;
}
}
// Get the elements to show
List<Object> children = new ArrayList<Object> ();
List<Object> javaChildren = new ArrayList<Object> ();
if( elt instanceof IPackageFragment ) {
javaChildren.addAll( Arrays.asList(((IPackageFragment) elt).getChildren()));
javaChildren.addAll( JavaUtils.findDirectSubPackages( null, (IPackageFragment) elt ));
Object[] nonJavaResources = ((IPackageFragment) elt).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
} else if( elt instanceof IPackageFragmentRoot ) {
javaChildren.addAll( Arrays.asList(((IPackageFragmentRoot) elt).getChildren()));
Object[] nonJavaResources = ((IPackageFragmentRoot) elt).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
} else {
throw new JavaModelException( new Exception( "Expected a package fragment or package fragment root." ), IStatus.ERROR );
}
// Filter the Java elements
if( includeEmpty ) {
children.addAll( javaChildren );
List<IPackageFragment> subPackagesToHide = new ArrayList<IPackageFragment> (); // for fragment roots
for( Object child : javaChildren ) {
if( child instanceof IPackageFragment )
subPackagesToHide.addAll( JavaUtils.findDirectSubPackages( null, (IPackageFragment) child ));
}
children.removeAll( subPackagesToHide );
} else {
children.clear();
children.addAll( findNonEmptyHierarchicalChildren( elt ));
}
// Associate every package fragment with a wrapper
PetalsCnfPackageFragment parentFragment = null;
if( elt instanceof IPackageFragment )
parentFragment = PetalsProjectManager.INSTANCE.dirtyViewerMap.get( elt );
for( Object child : children ) {
if( child instanceof IPackageFragment ) {
PetalsCnfPackageFragment fragment = new PetalsCnfPackageFragment((IPackageFragment) child, parentFragment );
PetalsProjectManager.INSTANCE.dirtyViewerMap.put((IPackageFragment) child, fragment );
}
}
return children;
}
/**
* Determines if a package fragment can be displayed.
* <p>
* This method is associated with the hierarchical mode with the empty package filter enabled.
* </p>
* <ul>
* <li>Packages that contain non-Java resources or Java resources must be displayed.</li>
* <li>Packages that only have sub-packages may be displayed only if they have at least two children with resources.</li>
* <li>Other packages cannot be displayed.</li>
* </ul>
*
* @param fragment the fragment to look at
* @param isFragmentRoot true if the original parent is a fragment root
* @return true if the fragment can be displayed, false otherwise
* @throws JavaModelException
*/
private static List<Object> findNonEmptyHierarchicalChildren( Object root ) throws JavaModelException {
List<Object> children = new ArrayList<Object> ();
// Handle the root element
List<IPackageFragment> packagesToLook;
if( root instanceof IPackageFragment ) {
Object[] nonJavaResources = ((IPackageFragment) root).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
packagesToLook = JavaUtils.findDirectSubPackages( null, (IPackageFragment) root);
for( IJavaElement elt : ((IPackageFragment) root).getChildren()) {
if( !( elt instanceof IPackageFragment ))
children.add( elt );
}
} else if( root instanceof IPackageFragmentRoot ) {
packagesToLook = JavaUtils.findDirectSubPackages((IPackageFragmentRoot) root, null );
Object[] nonJavaResources = ((IPackageFragmentRoot) root).getNonJavaResources();
children.addAll( Arrays.asList( nonJavaResources ));
} else {
throw new JavaModelException( new Exception( "Expected a package fragment or package fragment root." ), IStatus.ERROR );
}
// Sub-packages are visible
while( ! packagesToLook.isEmpty()) {
IPackageFragment f = packagesToLook.iterator().next();
int pCpt = 0;
boolean hasOtherChildren = false;
List<Object> subChildren = findNonEmptyHierarchicalChildren( f );
for( Object o : subChildren ) {
if( o instanceof IPackageFragment )
pCpt ++;
else
hasOtherChildren = true;
}
// If a sub-package is visible, then count it
if( hasOtherChildren || pCpt > 1 )
children.add( f );
// Otherwise, try to see if one of its (sub-)sub-packages is visible
else
packagesToLook.addAll( JavaUtils.findDirectSubPackages( null, f ));
packagesToLook.remove( f );
}
return children;
}
}