Archive

Posts Tagged ‘refactor’

Writing an Eclipse Plug-in (Part 23): Common Navigator: Rewriting History

August 14, 2010 3 comments


[I am now using Git for my source control using the EGit plug-in. Of course it is only partially working. One of my projects has fully committed and the others say they are in staging no matter how many times I tell EGit to Commit. Sigh.

Also, starting with this post I am also going to make the code in this convoluted journey available for download in each post as well as the Missing Zip Files page. It will always be available in the format of whatever Eclipse I happen to be using at the time (7/18/10: Eclipse 3.6 Helios Release) so don’t blame me if you are using an older version and something doesn’t work the way I describe it. If you follow me you walk the edge. Of course, in switching to EGit I have no idea where the code for Parts 21 and 22 have gone. I hate when that happens.

Don’t forget to add your favorite plug-ins: in my case that means EGit, EclEmma, Eclipse-CS, and UMLet]

[Woo hoo! Eclipse 3.6 is finally released! I can’t wait to be one of the first to download it! Hey! Where is everybody? Oh, it was released June 23? Really? I hate when I miss an opening party…by almost two months…but it was because I was busy…in Miami…meeting with Michael Westin…]

Well, long time no hear! Yes, I am trying to write these posts a little more often than I have been, but it is amazing how real life gets in the way…what with the cat coming in and out of the box and the squirrels distracting me to no end (don’t get me started on the platypus). I guess I may be stuck with only one post per month (maybe less).

I promise not to beat myself up over it.

Speaking of which: when I started this post the sun was out scorching everything, and I was doing everything I could to stay out of its path. After a failed attempt at getting back into running (you know, diet and exercise will help you live forever, unless you exercise wrong thereby screwing up your leg muscles making it almost impossible to walk), but after a successful attempt to eat better (salad and seafood, anyone?) it is time to pay attention to the things that keep us getting up in the morning and make life worth living (no, not sex, drugs, and rock and roll, though they help): Eclipse plug-ins.

I was going to write a post on genetic programming, but I suspect the cat hid my Koza book because it thought I was going to write a fitness function to force it to choose one state or another. I’ll do that on my next visit to Copenhagen.

What I will post about is, well, fixing the past. Usually, that is quite difficult, but we will make an exception and pretend that we can fix what we did, not because it was wrong, but because our needs have changed (that’s my story and I’m sticking to it).
Read more…

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

December 13, 2009 6 comments

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>

Writing an Eclipse Plug-in (Part 3): Create a custom project in Eclipse – New Project Wizard: Time to Refactor

July 19, 2009 3 comments

In my last post I showed how to get the GUI aspect of a New Wizard for the creation of a new project type up and running rather quickly. In my never ending attempt to be a good developer citizen it is now time to stop and refactor.

You might think we haven’t actually written enough code to refactor (we have 3 Java files in total). That would be true except for the three strings in our wizard code. I could wait until there is more to do, but why not refactor now when it will begin to develop muscle memory for that particular task?

First change:

    _pageOne = new WizardNewProjectCreationPage("Custom Plug-in Project Wizard");

to:

    _pageOne = new WizardNewProjectCreationPage(PAGE_NAME);

and put the string at the top of the class:

    private static final String PAGE_NAME = "Custom Plug-in Project Wizard"; //$NON-NLS-1$

Next change:

    public CustomProjectNewWizard() {
        setWindowTitle("New Custom Plug-in Project");
    }

to:

    public CustomProjectNewWizard() {
        setWindowTitle(WIZARD_NAME);
    }

and put the new constant at the top of the class:

    private static final String WIZARD_NAME = "New Custom Plug-in Project"; //$NON-NLS-1$

The other two strings should be replaced by using the Eclipse Externalize Strings mechanism. With the CustomProjectNewWizard.java open in the editor select Source –> Externalize Strings (or move the cursor to one of the strings and press Ctrl+1).

When the Externalize Strings dialog opens check the box for Use Eclipse’s String Externalization Mechanism. If you like the key value then leave them alone, but I prefer to have a variable name that conveys some level of information so I changed the keys to match the strings; so the CustomProjectNewWizard_0 becomes CustomProjectNewWizard_Custom_Plugin_Project and CustomProjectNewWizard_1 becomes CustomProjectNewWizard_Create_something_custom.

In addition, click Configure which is next to the Accessor Class dropdown field. Change the Class Name from Messages to NewWizardMessages. Click OK to finalize your decision.

Press Next in the Externalize Strings dialog to take a look at what the change will look like. What you should see are the strings replaced with a reference to a new class that just got generated.

    _pageOne.setTitle(NewWizardMessages.CustomProjectNewWizard_Custom_Plugin_Project);
    _pageOne.setDescription(NewWizardMessages.CustomProjectNewWizard_Create_something_custom);

Look in the Package Explorer for the new class. It should be in the same package as the Wizard code. The magic? At runtime Eclipse reads the property file named messages.properties and fills in the static fields with the values in the file. You can add to the file through the Externalize Strings dialog as you add more strings that need to be externalized.

That is the extent of the refactoring based on what has been done so far.

Refactoring can sometimes seem trivial, but it will pay many dividends later when a string needs to be changed, or internationalized, and the only thing that needs to be done is to create a new properties file.