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