Home > Eclipse development, Refactoring, Software Development, Testing > Writing an Eclipse Plug-in (Part 14): Common Navigator: Refactoring the Children

Writing an Eclipse Plug-in (Part 14): Common Navigator: Refactoring the Children


Well, it is late on a Sunday afternoon and I really want to refactor the children of the navigator. There are about 22 warnings and I hate warnings (almost as much as I hate errors).

This is probably going to be a short posting with lots of code. Go with it.

What (are we doing?)

The tasks are as usual:

  • Refactor common code
  • Add annotations as needed
  • Fix build path problems
  • Externalize strings

Let’s refactor.

Refactor common code

The first thing we can safely say about the various node wrappers is that they all contain a reference to:

  • Their parent
  • Any children
  • An image

That means that at the very least we can centralize the defintion of those three references. That means, using the refactor capability of Eclipse I pushed the following methods into a new parent class named CustomProjectElement:

  • getText()
  • getImage()
  • getParent()
  • getProject()

CustomProjectElement.java

package customnavigator.navigator;

import org.eclipse.core.resources.IProject;
import org.eclipse.swt.graphics.Image;

import customnavigator.Activator;

public abstract class CustomProjectElement implements ICustomProjectElement {

    private Image _image;
    private String _name;
    private String _imagePath;
    private ICustomProjectElement _parent;
    private ICustomProjectElement[] _children;

    public CustomProjectElement(ICustomProjectElement parent, String name, String imagePath) {
        _parent = parent;
        _name = name;
        _imagePath = imagePath;
    }

    @Override
    public String getText() {
        return _name;
    }

    @Override
    public Image getImage() {
        if (_image == null) {
            _image = Activator.getImage(_imagePath);
        }
    
        return _image;
    }

    @Override
    public ICustomProjectElement getParent() {
        return _parent;
    }

    @Override
    public IProject getProject() {
        return getParent().getProject();
    }

    @Override
    public ICustomProjectElement[] getChildren() {
        if (_children == null) {
            _children = initializeChildren(getProject());
        }
        // else we have already initialized them

        return _children;
    }

    @Override
    public boolean hasChildren() {
        if (_children == null) {
            _children = initializeChildren(getProject());
        }
        // else we have already initialized them
        return _children.length > 0;
    }

    protected abstract ICustomProjectElement[] initializeChildren(IProject project); 
}

Centralizing the methods actually cut the warnings down to 12 and each class is significantly smaller having passed the trivial responsibility of the getter methods to the parent class.

Of course, now that we have refactored all that code we have to run some tests to show that the system still works, but we never wrote any tests for the wrapper classes.

What to do? What to do?

Oh, yeah. The wrapper classes come from the ContentProvider. We have plenty of tests for that one.

Run the customnavigator.test.

Oh oh! Three of the tests fail! Another perfect day! Time to debug. The first method to check: testGetChildrenForIWorkspaceRootWithOneCustomProject().

Ah! I needed to have another call to project.getName(). How interesting is that? The test code was wrong! I hate when that happens.

    @Test
    public void testGetChildrenForIWorkspaceRootWithOneCustomProject() throws CoreException {
        IProject [] projects = new IProject[1];
        IProject project = EasyMock.createStrictMock(IProject.class);
        
        projects[0] = project;
        
        IWorkspaceRoot workspaceRoot = EasyMock.createStrictMock(IWorkspaceRoot.class);
        workspaceRoot.getProjects();
        EasyMock.expectLastCall().andReturn(projects);
        
        String projectName = "custom project"; //$NON-NLS-1$
        project.getName();
        EasyMock.expectLastCall().andReturn(projectName);
        
        project.getNature(CUSTOMPLUGIN_PROJECT_NATURE);
        EasyMock.expectLastCall().andReturn(EasyMock.createMock(IProjectNature.class));

        project.getName();
        EasyMock.expectLastCall().andReturn(projectName);

        project.getName();
        EasyMock.expectLastCall().andReturn(projectName);
        
        EasyMock.replay(workspaceRoot, project);
        
        Object [] actual = _contentProvider.getChildren(workspaceRoot);
        Assert.assertNotNull(actual);
        Assert.assertTrue(actual.length == 1);
        Assert.assertEquals(project, ((CustomProjectParent)actual[0]).getProject());
        
        EasyMock.verify(workspaceRoot, project);
    }

Good. A green bar again.

Add annotations as needed

Already added as I refactored to a new parent class. Code below.

Fix build path problems

Open build.properties. There is a yellow mark next to bin.includes; press Ctrl+1 and select Add OSGI-INF/ to bin.includes Build Entry. Add it, please. That actually takes care of two warnings. Something about killing two warnings with one quick fix.

Open MANIFEST.MF. There is a yellow mark next to the Export-Package entry. Press Ctrl+1 and select Add Missing Packages. For some reason the Quick Fix did not work quite right so the Export-Package entry should look like this:

Export-Package: customnavigator.navigator,
 customnavigator.test,
 org.easymock,
 org.easymock.internal,
 org.easymock.internal.matchers

Externalize strings

Opening the Externalize Wizard on each of the node wrappers allowed me to externalize the image path strings. Now they are independent of the code in case we need to move them. Not likely, but what the hell.

The last 6 warnings need the following fixed:

  • Potential null pointer access (which we can’t control since Eclipse calls the methods) – add @SuppressWarning(null) to ProjectNature
  • Externalize a label in plugin.xml – Ctrl+1 at the offending line automatically takes care of that
  • CoreException is not thrown in the methods we overrode in ProjectNature – add @SuppressWarnings(“unused”) to the three offending methods (two in CustomNature and one in CustomProjectNewWizard)

What Just Happened?

Lots of boring stuff was just done, but it was all necessary. The code is below.

Woo hoo. I think I will leave the cat alone in the box until next time.

Code

package customnavigator.navigator;

import org.eclipse.core.resources.IProject;
import org.eclipse.swt.graphics.Image;

import customnavigator.Activator;

public abstract class CustomProjectElement implements ICustomProjectElement {

    private Image _image;
    private String _name;
    private String _imagePath;
    private ICustomProjectElement _parent;
    private ICustomProjectElement[] _children;

    public CustomProjectElement(ICustomProjectElement parent, String name, String imagePath) {
        _parent = parent;
        _name = name;
        _imagePath = imagePath;
    }

    @Override
    public String getText() {
        return _name;
    }

    @Override
    public Image getImage() {
        if (_image == null) {
            _image = Activator.getImage(_imagePath);
        }
    
        return _image;
    }

    @Override
    public ICustomProjectElement getParent() {
        return _parent;
    }

    @Override
    public IProject getProject() {
        return getParent().getProject();
    }

    @Override
    public ICustomProjectElement[] getChildren() {
        if (_children == null) {
            _children = initializeChildren(getProject());
        }
        // else we have already initialized them

        return _children;
    }

    @Override
    public boolean hasChildren() {
        if (_children == null) {
            _children = initializeChildren(getProject());
        }
        // else we have already initialized them
        return _children.length > 0;
    }

    protected abstract ICustomProjectElement[] initializeChildren(IProject project); 
}
/**
 * Coder beware: this code is not warranted to do anything.
 * Copyright Oct 17, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.core.resources.IProject;


/**
 * @author carlos
 */
public class CustomProjectParent extends CustomProjectElement {

    private IProject _project;

    public CustomProjectParent(IProject iProject) {
        super(null, iProject.getName(), Messages.CustomProjectParent_Project_Folder);
        
        _project = iProject;
    }

    @Override
    public IProject getProject() {
        return _project;
    }

    @Override
    protected ICustomProjectElement[] initializeChildren(IProject project) {
        ICustomProjectElement[] children = {
                new CustomProjectSchema(this),
                new CustomProjectStoredProcedures(this)
        };

        return children;
    }

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

import org.eclipse.core.resources.IProject;


/**
 * @author carlos
 *
 */
public class CustomProjectSchema extends CustomProjectElement {

    public static final String NAME = "Schema"; //$NON-NLS-1$

    public CustomProjectSchema(ICustomProjectElement parent) {
        super(parent, NAME, Messages.CustomProjectSchema_Project_Schema);
    }

    @Override
    protected ICustomProjectElement[] initializeChildren(IProject iProject) {
        ICustomProjectElement[] children = new ICustomProjectElement [] {
                new CustomProjectSchemaTables(this),
                new CustomProjectSchemaViews(this),
                new CustomProjectSchemaFilters(this)
        };
        
        return children;
    }

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

import org.eclipse.core.resources.IProject;

/**
 * @author carlos
 *
 */
public class CustomProjectSchemaFilters extends CustomProjectElement {

    public static final String NAME = "Filters"; //$NON-NLS-1$

    public CustomProjectSchemaFilters(ICustomProjectElement parent) {
        super(parent, NAME, Messages.CustomProjectSchemaFilters_Project_Schema_Filters);
    }

    /* (non-Javadoc)
     * @see customnavigator.navigator.ICustomProjectElement#getChildren()
     */
    @Override
    protected ICustomProjectElement[] initializeChildren(IProject iProject) {
        ICustomProjectElement[] children = new ICustomProjectElement [0];
        
        return children;
    }
}
/**
 * Coder beware: this code is not warranted to do anything.
 *
 * Copyright Oct 18, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.core.resources.IProject;

/**
 * @author carlos
 *
 */
public class CustomProjectSchemaTables extends CustomProjectElement {

    public static final String NAME = "Tables"; //$NON-NLS-1$

    public CustomProjectSchemaTables(ICustomProjectElement parent) {
        super(parent, NAME, Messages.CustomProjectSchemaTables_Project_Schema_Tables);
    }

    @Override
    protected ICustomProjectElement[] initializeChildren(IProject iProject) {
        ICustomProjectElement[] children = new ICustomProjectElement [0];
        
        return children;
    }
}
/**
 * Coder beware: this code is not warranted to do anything.
 *
 * Copyright Oct 18, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.core.resources.IProject;

/**
 * @author carlos
 *
 */
public class CustomProjectSchemaViews extends CustomProjectElement {

    public static final String NAME = "Views"; //$NON-NLS-1$

    public CustomProjectSchemaViews(ICustomProjectElement parent) {
        super(parent, NAME, Messages.CustomProjectSchemaViews_Project_Schema_Views);
    }

    @Override
    protected ICustomProjectElement[] initializeChildren(IProject iProject) {
        ICustomProjectElement[] children = new ICustomProjectElement [0];
        
        return children;
    }
}
/**
 * Coder beware: this code is not warranted to do anything.
 *
 * Copyright Oct 18, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.core.resources.IProject;

/**
 * @author carlos
 *
 */
public class CustomProjectStoredProcedures extends CustomProjectElement {

    public static final String NAME = "Stored Procedures"; //$NON-NLS-1$

    public CustomProjectStoredProcedures(ICustomProjectElement parent) {
        super(parent, NAME, Messages.CustomProjectStoredProcedures_Project_Stored_Procedures);
    }

    @Override
    protected ICustomProjectElement[] initializeChildren(IProject iProject) {
        ICustomProjectElement[] children = new ICustomProjectElement [0];
        
        return children;
    }
}
/**
 * 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)
     */
    @SuppressWarnings("null")
    @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;
    }

    private int _count = 1;
    
    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        TreeViewer viewer = (TreeViewer) _viewer;
        TreePath[] treePaths = viewer.getExpandedTreePaths();
        viewer.refresh();
        viewer.setExpandedTreePaths(treePaths); 
        System.out.println("ContentProvider.resourceChanged: completed refresh() and setExpandedXXX()"); //$NON-NLS-1$
    }

    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]);
                if (customProjectParent != null) {
                    _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;
    }

}
package customplugin.natures;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.runtime.CoreException;

public class ProjectNature implements IProjectNature {

    public static final String NATURE_ID = "customplugin.projectNature"; //$NON-NLS-1$

    @SuppressWarnings("unused")
    @Override
    public void configure() throws CoreException {
        // TODO Auto-generated method stub

    }

    @SuppressWarnings("unused")
    @Override
    public void deconfigure() throws CoreException {
        // TODO Auto-generated method stub

    }

    @Override
    public IProject getProject() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public void setProject(IProject project) {
        // TODO Auto-generated method stub

    }

}
package customplugin.wizards;

import java.net.URI;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExecutableExtension;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.Wizard;
import org.eclipse.ui.INewWizard;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.dialogs.WizardNewProjectCreationPage;
import org.eclipse.ui.wizards.newresource.BasicNewProjectResourceWizard;

import customplugin.projects.CustomProjectSupport;

public class CustomProjectNewWizard extends Wizard implements INewWizard, IExecutableExtension {

    private static final String WIZARD_NAME = "New Custom Plug-in Project"; //$NON-NLS-1$
    private static final String PAGE_NAME = "Custom Plug-in Project Wizard"; //$NON-NLS-1$
    private WizardNewProjectCreationPage _pageOne;
    private IConfigurationElement _configurationElement;

    public CustomProjectNewWizard() {
        setWindowTitle(WIZARD_NAME);
    }

    @Override
    public boolean performFinish() {
        String name = _pageOne.getProjectName();
        URI location = null;
        if (!_pageOne.useDefaults()) {
            location = _pageOne.getLocationURI();
        } // else location == null
        
        CustomProjectSupport.createProject(name, location);
        BasicNewProjectResourceWizard.updatePerspective(_configurationElement);

        return true;
    }

    @Override
    public void addPages() {
        super.addPages();
        _pageOne = new WizardNewProjectCreationPage(PAGE_NAME);
        _pageOne.setTitle(NewWizardMessages.CustomProjectNewWizard_Custom_Plugin_Project);
        _pageOne.setDescription(NewWizardMessages.CustomProjectNewWizard_Create_something_custom);

        addPage(_pageOne);
    }

    @Override
    public void init(IWorkbench workbench, IStructuredSelection selection) {
        // TODO Auto-generated method stub

    }

    @SuppressWarnings("unused")
    @Override
    public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException {
        _configurationElement = config;
    }

}

plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
   <extension
         point="org.eclipse.ui.newWizards">
      <category
            id="customplugin.category.wizards"
            name="%category.name">
      </category>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewWizard"
            finalPerspective="customplugin.perspective"
            icon="icons/project-folder.png"
            id="customplugin.wizard.new.custom"
            name="%wizard.name">
      </wizard>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewSchemaFile"
            descriptionImage="icons/schema-file_32x32.png"
            icon="icons/schema-file_16x16.png"
            id="customplugin.wizard.file.schema"
            name="%wizard.name.schema">
      </wizard>
      <wizard
            category="customplugin.category.wizards"
            class="customplugin.wizards.CustomProjectNewDeploymentFile"
            descriptionImage="icons/deployment-file_32x32.png"
            icon="icons/deployment-file_16x16.png"
            id="customplugin.wizard.file.deployment"
            name="%wizard.name.deployment">
      </wizard>
   </extension>
   <extension
         point="org.eclipse.ui.perspectives">
      <perspective
            class="customplugin.perspectives.Perspective"
            icon="icons/perspective.png"
            id="customplugin.perspective"
            name="%perspective.name">
      </perspective>
   </extension>
   <extension
         point="org.eclipse.ui.perspectiveExtensions">
      <perspectiveExtension
            targetID="customplugin.perspective">
         <view
               id="customnavigator.navigator"
               minimized="false"
               ratio=".25"
               relationship="left"
               relative="org.eclipse.ui.editorss">
         </view>
      </perspectiveExtension>
   </extension>
   <extension
         id="customplugin.projectNature"
         point="org.eclipse.core.resources.natures">
      <runtime>
         <run
               class="customplugin.natures.ProjectNature">
         </run>
      </runtime>
   </extension>
   <extension
         point="org.eclipse.ui.ide.projectNatureImages">
      <image
            icon="icons/project-folder.png"
            id="customplugin.natureImage"
            natureId="customplugin.projectNature">
      </image>
   </extension>
   <extension
         id="customplugin.contenttype"
         point="org.eclipse.core.contenttype.contentTypes">
      <content-type
            base-type="org.eclipse.core.runtime.xml"
            file-extensions="xml"
            id="customplugin.contenttype.schema"
            name="%content-type.name.schema"
            priority="normal">
         <describer
               class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
            <parameter
                  name="element"
                  value="hc-schema">
            </parameter>
         </describer>
      </content-type>
      <content-type
            base-type="org.eclipse.core.runtime.xml"
            file-extensions="xml"
            id="customplugin.contenttype.deployment"
            name="%content-type.name.deployment"
            priority="normal">
         <describer
               class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
            <parameter
                  name="element"
                  value="hc-deployment">
            </parameter>
         </describer>
      </content-type>
   </extension>
   <extension
         point="org.eclipse.ui.menus">
      <menuContribution
            locationURI="popup:org.eclipse.ui.popup.any?after=additions">
         <menu
               label="%menu.label">
            <visibleWhen
                  checkEnabled="true">
            </visibleWhen>
         </menu>
      </menuContribution>
   </extension>

</plugin>
Advertisements
  1. runtime
    April 20, 2010 at 11:52 am

    Hello.
    Thanks for article.

    The method in class CustomProjectElement

    public boolean hasChildren() {
    if (_children == null) {
    _children = initializeChildren(getProject());
    }
    // else we have already initialized them
    return _children.length > 0;
    }

    can be rewritten

    public boolean hasChildren() {
    return !ArrayUtils.isEmpty(getChildren());
    }

    • cvalcarcel
      April 20, 2010 at 8:50 pm

      How true. I missed that one. I will update it in the next post where I mess around with the CustomProjectElement.

      Until then….LISTEN UP EVERYBODY: UPDATE YOUR CUSTOMPROJECTELEMENT.HASCHILDREN()!

      Don’t make me tell you again…

  2. runtime
    April 29, 2010 at 11:42 am

    Hello

    There is a little problem with customnavigator.navigator.ContentProvider#resourceChanged().

    1. I try to develop my own navigator (based on CNF).

    2. My navigator shows Java Projects in some unusual way.

    3. When I close some project in Project Explorer, I have an exception SWTException: Invalid thread access in resourceChanged() in viewer.getExpandedTreePaths(). Actually I don’t have any exception cause of getExpandedTreePaths() do job with org.eclipse.core.runtime.SafeRunner. So refresh doesn’t work properly.

    In org.eclipse.ui.internal.navigator.resources.workbench.ResourceExtensionContentProvider# processDelta() this problem fixed

    protected void processDelta(IResourceDelta delta) {

    Control ctrl = viewer.getControl();
    if (ctrl == null || ctrl.isDisposed()) {
    return;
    }

    //….

    //Are we in the UIThread? If so spin it until we are done
    if (ctrl.getDisplay().getThread() == Thread.currentThread()) {
    runUpdates(runnables);
    } else {
    ctrl.getDisplay().asyncExec(new Runnable(){
    /* (non-Javadoc)
    * @see java.lang.Runnable#run()
    */
    public void run() {
    //Abort if this happens after disposes
    Control ctrl = viewer.getControl();
    if (ctrl == null || ctrl.isDisposed()) {
    return;
    }

    runUpdates(runnables);
    }
    });
    }

    }

    So I use same technique

    public class ContentProvider implements ITreeContentProvider, IResourceChangeListener {

    private CommonViewer viewer;

    private final Runnable refreshViewer = new Runnable() {
    public void run() {
    if (isDisposed(viewer.getControl())) {
    return;
    }
    TreePath[] treePaths = viewer.getExpandedTreePaths();
    viewer.refresh();
    viewer.setExpandedTreePaths(treePaths);
    System.out.println(“ContentProvider.resourceChanged: completed refresh() and setExpandedXXX()”); //$NON-NLS-1$
    }
    };

    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    this.viewer = (CommonViewer) viewer;
    }

    public void resourceChanged(IResourceChangeEvent event) {
    if (isDisposed(viewer.getControl())) {
    return;
    }
    runWithCareAboutThreads(viewer.getControl().getDisplay(), refreshViewer);
    }

    private static void runWithCareAboutThreads(Display display, Runnable runnable) {
    if (display.getThread() == Thread.currentThread()) {
    runnable.run();
    } else {
    display.asyncExec(runnable);
    }
    }

    private static boolean isDisposed(Control control) {
    return control == null || control.isDisposed();
    }

    }

  3. David
    August 27, 2010 at 5:35 am

    Did you really mean CustomParentElement in the text?

    • cvalcarcel
      August 29, 2010 at 4:33 pm

      Of course I did! Do you think I would have accidentally typed CustomParentElement when CustomProjectElement was what I meant? Of course not! That name directly matches the code listed just…below that…oh wait…damn.

      Corrected. I hate when that happens.

      Thanks for spotting that!

  4. April 21, 2011 at 10:35 am

    I followed your excellent article and went out with a nice
    realization of a CNF! anyway, I would like to show also the content of leaf categories, namely the files created by the new file wizard presented later in your post. I did this fetching children files from the parent IFolder and creating instances of ICustomProjectElement that wraps the IFile. It works, but the viewer is not refreshed, files are only displayed when the app restarts; moreover I’m not able to open the file clicking on the displayed file (I guess I need some action provider for this…) can you briefly explain the main steps needed to complete the custom navigator implementation ? Thanks a lot!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: