Archive

Archive for November, 2009

Writing an Eclipse Plug-in (Part 12): Common Navigator: Keeping the Tree Open When a New Resource is Added

November 18, 2009 3 comments

Welcome back, boys and girls. In this installment of Writing an Eclipse Plug-in we add the phenomenally simple behavior of keeping the expanded tree nodes open when we add new projects.

The Use Case:

  1. Create a custom project.
  2. Open one of the nodes
  3. Create another custom project
  4. Both projects should appear and the first projects node should still be opened

In our usual test-driven behavior, try the above and see the navigator fail the test. Let’s fix that.

What to do

  1. Open the customnavigator ContentProvider.
  2. Change resourceChanged with the following bold code:
  3. ContentProvider.java

        @Override
        public void resourceChanged(IResourceChangeEvent event) {
            TreeViewer viewer = (TreeViewer) _viewer;
    
            TreePath[] treePaths = viewer.getExpandedTreePaths();
            viewer.refresh();
            viewer.setExpandedTreePaths(treePaths);
        }
    
  4. Add this field definition to the top of the file:
  5.     private Map<String, Object> _wrapperCache = new HashMap<String, Object>();
    
  6. Make the following changes to createCustomProjectParents():
  7.     private Object[] createCustomProjectParents(IProject[] projects) {
            Object[] result = null;
    
            List<Object> list = new ArrayList<Object>();
            for (int i = 0; i < projects.length; i++) {
                Object customProjectParent = _wrapperCache.get(projects[i].getName());
                if (customProjectParent == null) {
                    customProjectParent = createCustomProjectParent(projects[i]);
                    _wrapperCache.put(projects[i].getName(), customProjectParent);
                }
    
                if (customProjectParent != null) {
                    list.add(customProjectParent);
                } // else ignore the project
            }
    
            result = new Object[list.size()];
            list.toArray(result);
    
            return result;
        }
    
  8. Start the runtime workbench, create a project, open one of the tree nodes, and create another project. The first project should look the same (the node you opened should still be opened)

Take a bow.

Why Did We Do That?

When you play with the custom navigator you can’t help but notice how many things the navigator does not do including maintain its look when a new project is added. Since my current goal in life is to scratch my CNF itch here is why the steps above work.

Change resourceChanged() with the following bold code:

ContentProvider.java

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        TreeViewer viewer = (TreeViewer) _viewer;

        TreePath[] treePaths = viewer.getExpandedTreePaths();
        viewer.refresh();
        viewer.setExpandedTreePaths(treePaths);
    }

In order to get the above behavior to work we could have also used TreeViewer.getExpandedElements()/setExpandedElements() instead of TreeViewer.getExpandedTreePaths()/setExpandedTreePaths(). For whatever reason, TreeViewer.getExpandedElements()/setExpandedElements() works just as well as TreeViewer.getExpandedTreePaths()/setExpandedTreePaths(). One day (I’ll care enough to) I’ll figure out why.

If you recall, in order to get the navigator to update itself we have to call viewer.refresh(). Well, in order to get the viewer to display whatever it is we opened we have to ask it what was opened before the refresh(); hence the call to viewer.getExpandedTreePaths(). After we refresh the viewer then we have to tell it which nodes to reopen as it closes all of them on a refresh; hence the call to viewer.setExpandedTreePaths(treePaths).

Pretty easy. Start the runtime workbench and create a project, open a node and create a new project. Hmm. Still doesn’t work.

The reason why the code above doesn’t work is because of the way the resource elements are wrapped; every time getChildren() is called we create new wrappers. New wrappers means new object references. New object references means new values returned from hashCode(). New hashCode() values means that equals() will behave as if the same project is really a new project. Nothing confuses the viewer more than giving it the impression that new nodes need to be displayed. To fix this we need to cache the wrappers around the projects and return them when we can and create new ones when needed. The current code is straightforward and, in the current situation, wrong.

It is time to clean up obviously sloppy code (especially since we need it to behave differently. It didn’t look so bad at the time).

    private Map<String, Object> _wrapperCache = new HashMap<String, Object>();

    ...

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = _wrapperCache.get(projects[i].getName());
            if (customProjectParent == null) {
                customProjectParent = createCustomProjectParent(projects[i]);
                _wrapperCache.put(projects[i].getName(), customProjectParent);
            }

            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

        result = new Object[list.size()];
        list.toArray(result);

        return result;
    }

The code above checks the cache for the project wrapper. If it’s not there, create it. Return the existing or new object. This is much more efficient than what we had before, but we didn’t need it until now (that’s my story and I’m sticking to it).

Now try the use case steps.

The cat should be alive.

What Just Happened?

Oh, c’mon! Didn’t you just execute the steps right from the beginning? You asked the viewer for the expanded nodes, refreshed the viewer, and then told it which nodes to reopen.

The code to cache the wrappers was pretty simple. I am proud of how little code we continue to write.

customnavigator: 13 code files and 1 properties file
customplugin: 11 code files and 2 properties file

Bear in mind that most of the code isn’t very complicated. I’ll try harder next time.

Rock on.

Code

/**
 * Coder beware: this code is not warranted to do anything.
 * Copyright Oct 17, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;

import customplugin.natures.ProjectNature;

/**
 * @author carlos
 */
public class ContentProvider implements ITreeContentProvider, IResourceChangeListener {

    private static final Object[]   NO_CHILDREN = {};
    private Map<String, Object> _wrapperCache = new HashMap<String, Object>();
    private Viewer _viewer;

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    @Override
    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
     */
    @Override
    public Object getParent(Object element) {
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
     */
    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

        if (IWorkspaceRoot.class.isInstance(element)) {
            hasChildren = ((IWorkspaceRoot)element).getProjects().length > 0;
        } else if (ICustomProjectElement.class.isInstance(element)) {
            hasChildren = ((ICustomProjectElement)element).hasChildren();
        }
        // else it is not one of these so return false

        return hasChildren;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
     */
    @Override
    public Object[] getElements(Object inputElement) {
        // This is the same as getChildren() so we will call that instead
        return getChildren(inputElement);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#dispose()
     */
    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    /*
     * (non-Javadoc)
     * @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) {
        _viewer = viewer;
    }

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        TreeViewer viewer = (TreeViewer) _viewer;
        TreePath[] treePaths = viewer.getExpandedTreePaths();
        viewer.refresh();
        viewer.setExpandedTreePaths(treePaths);
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = _wrapperCache.get(projects[i].getName());
            if (customProjectParent == null) {
                customProjectParent = createCustomProjectParent(projects[i]);
                _wrapperCache.put(projects[i].getName(), customProjectParent);
            }

            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

        result = new Object[list.size()];
        list.toArray(result);

        return result;
    }

}

References

http://www.eclipsezone.com/eclipse/forums/t107049.html

Writing an Eclipse Plug-in (Part 11): Common Navigator: Displaying Custom Resources or Refresh Or Die or The Magic of navigatorContent

November 14, 2009 8 comments

Today’s tasks are:

  • Open the navigator in a perspective (doesn’t matter which one)
  • Create a Custom Project
  • Create a non-custom project. It should not appear in the custom navigator.

In breaking with tradition I am going to simply tell you what needs to be done (What to Do) and then I will rationalize the implementation for the work of art that it is (Why did we do it?).

What to Do

Let’s configure customnavigator’s plugin.xml:

  1. customnavigator –> plugin.xml –> org.eclipse.ui.views –> Custom Plug-in Navigator
  2. Change customnavigator.navigator.CustomNavigator to org.eclipse.ui.navigator.CommonNavigator
  3. Delete CustomNavigator.java (YAGNI for the foreseeable future)
  4. Change org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> (or) –> customnavigator.navigator.CustomProjectWorkbenchRoot to org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> org.eclipse.core.resources.IWorkspaceRoot

Change the ContentProvider code to reflect the following changes:

ContentProvider.getParent()

    @Override
    public Object getParent(Object element) {
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

ContentProvider.hasChildren()

    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

        if (IWorkspaceRoot.class.isInstance(element)) {
            hasChildren = ((IWorkspaceRoot)element).getProjects().length > 0;
        } else if (ICustomProjectElement.class.isInstance(element)) {
            hasChildren = ((ICustomProjectElement)element).hasChildren();
        }
        // else it is not one of these so return false

        return hasChildren;
    }

ContentProvider.getChildren()/createCustomProjectParents()/createCustomProjectParent()

    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

        result = new Object[list.size()];
        list.toArray(result);

        return result;
    }

ContentProvider.inputChanged()/ContentProvider constructor/dispose()

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        _viewer = viewer;
    }

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

ContentProvider.resourceChanged()

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

Why did we do it?

So, as we saw in a previous post, we can display a custom project that has specialized child nodes based on the kind of project we create; in this case a project with a nature of customplugin.projectNature.

Today’s task (a repeat of what I listed above) was:

  • Open the navigator in a perspective (doesn’t matter which one)
  • Create a Custom Project
  • Create a non-custom project. It should not appear in the custom navigator.

The challenge is: how to configure the Custom Navigator so that it will only display custom projects. In my never ending quest to write as little code as possible I will show you what to configure in plugin.xml and what code to add to ContentProvider to make it happen.

Supremely obvious point #1: without content a common navigator doesn’t display anything.
Supremely obvious point #2: you can configure a resource-based CNF navigator to do one of three things (if you can think of more, let me know or better yet write it up):

  1. Display only certain resources,
  2. Display resources in a custom way or
  3. Some combination of 1 and 2.

We want to do #3: the navigator should only display custom projects and resources related to custom projects should be displayed in a custom way.

If we had wanted to display all of the top level resources in the navigator we would just define a viewerContentBinding of org.eclipse.ui.navigator.resourceContent. Since that content binding is already defined our work would be done. That however would be too easy and wrong for what we want to do (it is amazing how often easy and wrong go together).

We know that we can display custom projects and related resources in a custom way (we did that, remember?). Now we want to make sure that no matter how many projects we create the navigator will only display our projects our way (like fast food without the fat).

In order to do this we are going to take a few steps back and a few steps forward. We are going to:

  • Delete our existing navigator class (back one step)
  • Replace the existing navigator class with a CommonNavigator (back another step)
  • Attach a content binding to the viewer (which is actually already there, but we are changing it; back another step)
  • Configure a navigatorContent (which is also already there)
    • Define an object type to send into the content provider (called the triggerPoint): IWorkspaceRoot
    • Implement the content provider code to wrap our project and its children
    • Register a resource change listener in the content provider to update the viewer when new projects are added

First the ending:
Let’s configure customnavigator’s plugin.xml:

  1. Go to customnavigator –> plugin.xml –> org.eclipse.ui.views –> Custom Plug-in Navigator
  2. Change customnavigator.navigator.CustomNavigator to org.eclipse.ui.navigator.CommonNavigator
  3. Delete CustomNavigator.java (YAGNI)
  4. Change org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> (or) –> customnavigator.navigator.CustomProjectWorkbenchRoot to org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> org.eclipse.core.resources.IWorkspaceRoot

Why change the triggerPoint from CustomProjectWorkbenchRoot to IWorkspaceRoot? Believe it or not, the CustomProjectWorkbenchRoot is overkill for what we want. In fact, using CustomProjectWorkbenchRoot will keep the navigator from working properly so in the tradition of YAGNI, we got rid of it. We may live to regret it depending on how complex this project gets, but for now out with the trash.

And now for the ContentProvider code:
ContentProvider.getParent()

    @Override
    public Object getParent(Object element) {
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

What is this code doing?

  • If an IWorkspaceRoot comes in return null as it has no parent.
  • If an IProject comes in, return the Workspace root.
  • If a node of type ICustomProjectElement, and all of the custom project wrappers are this type, ask the object for its parent. The project will return the Workspace root, the child nodes will return the project or its custom parent node.
  • For all others return null (no parent)

Since the “all others” and IWorkspaceRoot return null we just need to check for the IProject and ICustomProjectElement.

ContentProvider.hasChildren()

    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

        if (IWorkspaceRoot.class.isInstance(element)) {
            hasChildren = ((IWorkspaceRoot)element).getProjects().length > 0;
        } else if (ICustomProjectElement.class.isInstance(element)) {
            hasChildren = ((ICustomProjectElement)element).hasChildren();
        }
        // else it is not one of these so return false

        return hasChildren;
    }

So who has children? The only two types I care about are IWorkspaceRoot and ICustomProjectElement, otherwise return null.

  • If an IWorkspaceRoot return true if there are any projects. I know, I know, I should check if any of the projects have the custom project nature, but that is overhead I will safely ignore for now.
  • If an ICustomProjectElement comes in ask if it has any children.

Stay on target.

ContentProvider.getChildren()/createCustomProjectParents()/createCustomProjectParent()

    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

        result = new Object[list.size()];
        list.toArray(result);

        return result;
    }

In getChildren() all I care about is:

  • Returning wrapped custom project resources
  • Returning the children of a custom project
  • Returning an empty array If I don’t recognize the incoming element .

The createCustomProjectParents()/createCustomProjectParent() methods wrap my custom project IProjects; all other projects will be ignored.

ContentProvider.inputChanged()/ContentProvider constructor/dispose()

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        _viewer = viewer;
    }

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

The order of the methods above are out of sequence for the class, but in sequence for this explanation.

In inputChanged() someone has done something that we need to pay attention to. In this case, a new project was added (we haven’t coded for deletions yet). Just save the viewer reference; we’ll need it later. However, if the input has changed we need a way to determine what that change was. Enter resource change listeners.

[If you don’t know anything about event handling in Java, much less Eclipse, the following explanation is probably not going to make a lot of sense.]

For now, the easiest place to register a listener is in the ContentProvider constructor so I placed it there. Of course, whatever we register we should unregister so in dispose() we remove the resource change listener.

Which leads us to the resource change listener itself.

ContentProvider.resourceChanged()

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

Almost too easy. Our simple navigator just needs to be told to refresh itself when a change occurs.

To summarize: when our input changes we save the viewer; when a resource changes we call resourceChanged() which refreshes the viewer.

Life is beautiful.

What Just Happened?

Now it is time for me to admit something: I hate beets (that is: the vegetable).

Well, more significantly, I have to admit it took a while for me to figure out how simple this truly was. I looked at examples, read posts, drank plenty of Guinness. Finally, I woke up one morning and thought Oh! Is that all it is?

What I found was that the examples did not do what I was trying to do (don’t you hate when that happens?). They always seem to use the pre-existing resourceContent binding in addition to other stuff which didn’t help me at all (well, it did actually help, but kept me confused for too many nights).

The moral of the story: use resourceContent if you need to show the existing resources in a standard way or with exceptions.

Don’t use resourceContent if you want to take full control over the navigator.

That might be too general and all encompassing, but, what the hell, what isn’t?

And of course all this may change as I add more functionality to fill the custom project with custom content. Stay tuned, boys and girls.

Next time: keeping the nodes opened or closed when we add a resource.

References and Thanks

Building a Common Navigator based viewer, Part I: Defining the Viewer

Code

plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <category
            id="customnavigator.category"
            name="%category.name">
      </category>
      <view
            allowMultiple="false"
            category="customnavigator.category"
            class="org.eclipse.ui.navigator.CommonNavigator"
            icon="icons/navigator.png"
            id="customnavigator.navigator"
            name="%view.name">
      </view>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.viewer">
      <viewerActionBinding
            viewerId="customnavigator.navigator">
         <includes>
            <actionExtension
                  pattern="org.eclipse.ui.navigator.resources.*">
            </actionExtension>
         </includes>
      </viewerActionBinding>
      <viewerContentBinding
            viewerId="customnavigator.navigator">
         <includes>
            <contentExtension
                  pattern="customnavigator.navigatorContent">
            </contentExtension>
         </includes>
      </viewerContentBinding>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.navigatorContent">
      <navigatorContent
            activeByDefault="true"
            contentProvider="customnavigator.navigator.ContentProvider"
            id="customnavigator.navigatorContent"
            labelProvider="customnavigator.navigator.LabelProvider"
            name="%navigatorContent.name">
         <triggerPoints>
            <instanceof
                  value="org.eclipse.core.resources.IWorkspaceRoot">
            </instanceof>
         </triggerPoints>
         <commonSorter
               class="customnavigator.sorter.SchemaCategorySorter"
               id="customnavigator.sorter.schemacategorysorter">
            <parentExpression>
               <or>
                  <instanceof
                        value="customnavigator.navigator.CustomProjectSchema">
                  </instanceof>
               </or>
            </parentExpression>
         </commonSorter>
      </navigatorContent>
   </extension>

</plugin>
ContentProvider.java
/**
 * Coder beware: this code is not warranted to do anything.
 * Copyright Oct 17, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;

import customplugin.natures.ProjectNature;

/**
 * @author carlos and Hidden Clause
 */
public class ContentProvider implements ITreeContentProvider, IResourceChangeListener {

    private static final Object[]   NO_CHILDREN = {};
    Viewer _viewer;
    private int _count = 1;

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    @Override
    public Object[] getChildren(Object parentElement) {
        System.out.println("ContentProvider.getChildren: " + parentElement.getClass().getName()); //$NON-NLS-1$
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
     */
    @Override
    public Object getParent(Object element) {
        System.out.println("ContentProvider.getParent: " + element.getClass().getName()); //$NON-NLS-1$
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
     */
    @Override
    public boolean hasChildren(Object element) {
        System.out.println("ContentProvider.hasChildren: " + element.getClass().getName()); //$NON-NLS-1$
        boolean hasChildren = false;

        if (IWorkspaceRoot.class.isInstance(element)) {
            hasChildren = ((IWorkspaceRoot)element).getProjects().length > 0;
        } else if (ICustomProjectElement.class.isInstance(element)) {
            hasChildren = ((ICustomProjectElement)element).hasChildren();
        }
        // else it is not one of these so return false

        return hasChildren;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
     */
    @Override
    public Object[] getElements(Object inputElement) {
        // This is the same as getChildren() so we will call that instead
        System.out.println("ContentProvider.getElements: " + inputElement.getClass().getName()); //$NON-NLS-1$
        return getChildren(inputElement);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#dispose()
     */
    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    /*
     * (non-Javadoc)
     * @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) {
        _viewer = viewer;
    }

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

        result = new Object[list.size()];
        list.toArray(result);

        return result;
    }

}