/****************************************************************************** * 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; } }