Archive

Archive for July, 2009

Ants, Odd Behavior and Zombie-creating Parasites

July 30, 2009 1 comment

Ants are interesting creatures. Awful eye sight, great sense of smell, really strong in comparison to their body size and weight.

Ants also show up in literature. In The Once and Future King a young King Arthur is reduced to the size of an ant by Merlin so that he can learn all sorts of things like politics and leadership (who knew that giving in to Mordred, Arthur’s son/nephew by his sister Morgawse/Morgan Le Fey, when Mordred shows up looking for land whose inhabitants he can enslave could be a good thing. Neville Chamberlain fans take note).* Merlin transforms Arthur into a bird as well, but I just care about ants today.

In real life not only can ants lead lives of quiet desperation they also run the risk of becoming host to a nasty creature called the lancet fluke.

Fans of the movie Alien know that the nastiest thing about the alien was its gestation within a human host. The alien would go through an initial form that would impregnate a host and the bouncing baby alien would come out shortly after (hilarity ensues).

The lancet fluke is nowhere near as boring as the alien: impregnate the host, blow out its chest and run (yawn). In an interesting twist the lancet fluke does mind control (very high on the cool scale).

The terminal hosts are mammals like cows or goats, while the intermediate hosts are snails and ants. The terminal host excretes the fluke’s eggs during the process of…waste management, the eggs are promptly eaten by a snail (yes, I just used the words promptly and snail in the same phrase) where the eggs do a little bit of this and a little bit of that and eventually irritate the snails respiratory system enough to make the snail cough out the newly transformed fluke cysts into the outside world where they wait to be eaten by ants (who said the food chain was simple?).

Why would an ant eat the cysts? As it so happens the lancet fluke cysts coughed up by the snails are surrounded by a mucus that has pheromones that are appealing to the ants (yummy, tasty and sexy).

The interesting part is what happens after the ant eats the pheromone encrusted cyst. They become zombies. Literally, and only during the evenings (no coffins, however).

After an ant ingests the lancet fluke cysts the cysts open releasing flukes into various parts of the ants anatomy. A select group make their way to the ant’s nervous system and take over. During the day, the ant goes to work, tells jokes, has meals at home and pays her bills (males are typically around just to have sex with the queen and die. Oh, the good old days!).

At night, the ant finds that it has an uncontrollable desire to hang from the top of a blade of grass (why do I hear a Dane Cook joke in there?). It finds a comfy blade, climbs it and then locks its mandibles as close to the top as possible. And waits. What is it waiting for? The ant doesn’t know, but the lancet fluke does**: it is waiting to be eaten by some unlucky animal who will become the terminal host. Some cow, goat, or whomever, is going to come by, eat the grass, inadvertently eat the ant and by extension eat the fluke.

Why only at night? During the day, to paraphrase Ernest Hemingway, it is hot. The ant could die; the fluke cannot allow that to happen and so returns control of the ant’s brain back to the ant. The ant gets to run back to home and hearth away from the hot sun and make up excuses for missing dinner by admitting to fighting crime after work.

All that to ask the question: could there be an equivalent parasite in humans?

I first read about the lancet fluke in Daniel Dennett’s book Breaking the Spell: Religion as a Natural Phenomenon. Highly recommended.

You can read more about the lancet fluke at Damn Interesting, Suite101com and Wikipedia.

Update (7/12/12): an interesting study on suicide in women linked to a cat parasite: http://vitals.msnbc.msn.com/_news/2012/07/02/12529379-cat-parasite-linked-to-womens-suicide-attempts.

* The Once and Future King is a wonderful book. I highly recommend it even as it takes the legend of King Arthur in directions I am sure the original mythologists hadn’t considered: like pacifism.

** I use the word know loosely. The lancet fluke has no brain. It’s behavior is purely mechanical. The various forms of the lancet fluke are executing genetic behavior that has survived over time and has allowed them to be fruitful and multiply. Kind of like us, only we call if free will.

Writing an Eclipse Plug-in (Part 4): Create a Custom Project in Eclipse – New Project Wizard: the Behavior

July 26, 2009 50 comments

In a previous 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. Even I was surprised; so surprised that I did it 2 more times just to make sure I wasn’t cheating somehow.

One of my side goals was to write the least amount of code and to let the plug-in extensions handle most of the integration of the various components.

With the GUI displaying the minimum expected GUI behavior it is now time to add minimum project-creation behavior.

The GUI counts as the platform so I won’t test it unless there is some strange behavior that I can’t explain. The creation of the project itself needs to be tested as I am adding a folder structure and a nature and I want to make sure that works. The test will also make it easier to extend my project structure in a controlled way.

Here are the steps:

  1. Create a new plug-in project, I have named it customplugin.test.
  2. plugin.xml –> Dependencies: Add org.junit4. This is required by the runtime workbench. If you see the dreaded No Runnable Methods message then you forgot to do this.
  3. Download dom4j from http://sourceforge.net/project/showfiles.php?group_id=16035&package_id=14121&release_id=328664. Extract the zip someplace safe; you will need two of the jar files in the next step.
  4. Create a folder named lib directly under customplugin.test and copy dom4j-1.6.1.jar and jaxen-1.1-beta-6.jar into customplugin.test/lib. One of the tests will open the .project file and check that the nature has been added. Using dom4j will make that much easier.
  5. In the customplugin.test plugin.xml file:
    • Runtime –> Classpath: Add the two jar files in the lib folder.
  6. Open the project Properties dialog. In the Project References element put a check mark next to the customplugin project (if you have been downloading the zip files then put a check mark next to customplugin_1.0.0.3).

Now the fun part: what should I test? Well, to create the simplest project within Eclipse involves only two things:

  • The name of the project
  • The location of the project

Truth be told you only need the project name. If null is given as the location name Eclipse uses the default workspace as the project destination.

I came up with only 4 tests:

  • Good project name, default location (null)
  • Good project name, different location
  • Null project name
  • Empty project name

The good test, regardless of workspace location, has to check that:

  • The project returned is non-null
  • The Custom nature was added
  • The .project file was created properly
  • The custom folder structure was created

The concept of a custom nature has finally appeared. Though a nature is typically used to tie a builder together to a project type, natures are also flags. If you get an IProject object looking at its nature or natures is a great way to determine what kind of project you are dealing with.

Add a nature by:

  • Opening your plugin.xml file
  • Going to the Extensions tab
  • Clicking Add
  • Finding and selecting the org.eclipse.core.resources.natures
  • Clicking Finish

First, select org.eclipse.core.resources.natures and enter in the ID field customplugin.projectNature. Next, open the (runtime) node, select the (run) node and enter a class name of customplugin.natures.ProjectNature. Click on the class link and click Finish on the New Java Class dialog.

I added the nature id as a string constant to make it easier to use in various parts of the code that will be implemented.

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$

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

    @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
    }

}

For now, you don’t need more than that so feel free to close the Java editor on ProjectNature after you take a quick look at the generated code.

The following code went through a few iterations before it came to look like this, but it didn’t take that long; it took longer to strategize how I wanted to do it. It would take a long time to work through the mental steps I went through to create the CustomProjectSupport and CustomProjectSupportTest class. The code for the test is first, followed by the code that was slowly pulled together.

I decided that I was going to implement the code to create the project, add the nature and create my folder structure in a separate class to make it easier to test and insert into the wizard’s performFinish() method. It will be named CustomProjectSupport. The test class will be named CustomProjectSupportTest.

Add org.eclipse.core.resources to plugin.xml (well, really MANIFEST.MF) –> Dependencies or else the code won’t compile.
Here is the test code:

package customplugin.projects;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.CoreException;
import org.junit.Assert;
import org.junit.Test;

import customplugin.natures.ProjectNature;

public class CustomProjectSupportTest {
    @SuppressWarnings("nls")
    @Test
    public void testCreateProjectWithDifferentLocationArg() throws URISyntaxException, DocumentException, CoreException {
        String workspaceFilePath = "/media/disk/home/carlos/Projects/junit-workspace2";
        File workspace = createTempWorkspace(workspaceFilePath);

        String projectName = "delete-me"; //$NON-NLS-1$
        String projectPath = workspaceFilePath + "/" + projectName;
        URI location = new URI("file:/" + projectPath);

        assertProjectDotFileAndStructureAndNatureExist(projectPath, projectName, location);

        deleteTempWorkspace(workspace);
    }

    @Test
    public void testCreateProjectWithEmptyNameArg() {
        String projectName = " "; //$NON-NLS-1$
        assertCreateProjectWithBadNameArg(projectName);
    }

    @Test
    public void testCreateProjectWithNullNameArg() {
        String projectName = null;
        assertCreateProjectWithBadNameArg(projectName);
    }

    @SuppressWarnings("nls")
    @Test
    public void testCreateProjectWithGoodArgs() throws DocumentException, CoreException {
        // This is the default workspace for this plug-in
        String workspaceFilePath = "/media/disk/home/carlos/Projects/junit-workspace";
        String projectName = "delete-me";
        String projectPath = workspaceFilePath + "/" + projectName;

        URI location = null;
        assertProjectDotFileAndStructureAndNatureExist(projectPath, projectName, location);
    }

    @SuppressWarnings("nls")
    private void assertProjectDotFileAndStructureAndNatureExist(String projectPath, String name, URI location) throws DocumentException,
            CoreException {
        IProject project = CustomProjectSupport.createProject(name, location);

        String projectFilePath = projectPath + "/" + ".project";
        String[] emptyNodes = { "/projectDescription/comment", "/projectDescription/projects", "/projectDescription/buildSpec" };
        String[] nonEmptyNodes = { "/projectDescription/name", "/projectDescription/natures/nature" };

        Assert.assertNotNull(project);
        assertFileExists(projectFilePath);
        assertAllElementsEmptyExcept(projectFilePath, emptyNodes, nonEmptyNodes);
        assertNatureIn(project);
        assertFolderStructureIn(projectPath);

        project.delete(true, null);
    }

    @SuppressWarnings("nls")
    private void assertFolderStructureIn(String projectPath) {
        String[] paths = { "parent/child1-1/child2", "parent/child1-2/child2/child3" };
        for (String path : paths) {
            File file = new File(projectPath + "/" + path);
            if (!file.exists()) {
                Assert.fail("Folder structure " + path + " does not exist.");
            }
        }
    }

    private void assertNatureIn(IProject project) throws CoreException {
        IProjectDescription descriptions = project.getDescription();
        String[] natureIds = descriptions.getNatureIds();
        if (natureIds.length != 1) {
            Assert.fail("No natures found in project."); //$NON-NLS-1$
        }

        if (!natureIds[0].equals(ProjectNature.NATURE_ID)) {
            Assert.fail("CustomProject natures not found in project."); //$NON-NLS-1$
        }
    }

    private void assertAllElementsEmptyExcept(String projectFilePath, String[] emptyNodes, String[] nonEmptyNodes) throws DocumentException {
        SAXReader reader = new SAXReader();
        Document document = reader.read(projectFilePath);
        int strLength;
        for (String emptyNode : emptyNodes) {
            strLength = document.selectSingleNode(emptyNode).getText().trim().length();
            if (strLength != 0) {
                Assert.fail("Node " + emptyNode + " was non-empty!"); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }

        for (String nonEmptyNode : nonEmptyNodes) {
            strLength = document.selectSingleNode(nonEmptyNode).getText().trim().length();
            if (strLength == 0) {
                Assert.fail("Node " + nonEmptyNode + " was empty!"); //$NON-NLS-1$//$NON-NLS-2$
            }
        }
    }

    private void assertFileExists(String projectFilePath) {
        File file = new File(projectFilePath);

        if (!file.exists()) {
            Assert.fail("File " + projectFilePath + " does not exist."); //$NON-NLS-1$//$NON-NLS-2$
        }
    }

    private void assertCreateProjectWithBadNameArg(String name) {
        URI location = null;
        try {
            CustomProjectSupport.createProject(name, location);
            Assert.fail("The call to CustomProjectSupport.createProject() did not fail!"); //$NON-NLS-1$
        } catch (AssertionFailedException e) {
            // An exception was thrown as expected; the test passed.
        }
    }

    private void deleteTempWorkspace(File workspace) {
        boolean deleted = workspace.delete();
        if (!deleted) {
            Assert.fail("Unable to delete the new workspace dir at " + workspace); //$NON-NLS-1$
        }
    }

    private File createTempWorkspace(String pathToWorkspace) {
        File workspace = new File(pathToWorkspace);
        if (!workspace.exists()) {
            boolean dirCreated = workspace.mkdir();
            if (!dirCreated) {
                Assert.fail("Unable to create the new workspace dir at " + workspace); //$NON-NLS-1$
            }
        }

        return workspace;
    }

}

The CustomProjectSupport code looks like this:

package customplugin.projects;

import java.net.URI;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

import customplugin.natures.ProjectNature;

public class CustomProjectSupport {
    /**
     * For this marvelous project we need to:
     * - create the default Eclipse project
     * - add the custom project nature
     * - create the folder structure
     *
     * @param projectName
     * @param location
     * @param natureId
     * @return
     */
    public static IProject createProject(String projectName, URI location) {
        Assert.isNotNull(projectName);
        Assert.isTrue(projectName.trim().length() > 0);

        IProject project = createBaseProject(projectName, location);
        try {
            addNature(project);

            String[] paths = { "parent/child1-1/child2", "parent/child1-2/child2/child3" }; //$NON-NLS-1$ //$NON-NLS-2$
            addToProjectStructure(project, paths);
        } catch (CoreException e) {
            e.printStackTrace();
            project = null;
        }

        return project;
    }

    /**
     * Just do the basics: create a basic project.
     *
     * @param location
     * @param projectName
     */
    private static IProject createBaseProject(String projectName, URI location) {
        // it is acceptable to use the ResourcesPlugin class
        IProject newProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);

        if (!newProject.exists()) {
            URI projectLocation = location;
            IProjectDescription desc = newProject.getWorkspace().newProjectDescription(newProject.getName());
            if (location != null && ResourcesPlugin.getWorkspace().getRoot().getLocationURI().equals(location)) {
                projectLocation = null;
            }

            desc.setLocationURI(projectLocation);
            try {
                newProject.create(desc, null);
                if (!newProject.isOpen()) {
                    newProject.open(null);
                }
            } catch (CoreException e) {
                e.printStackTrace();
            }
        }

        return newProject;
    }

    private static void createFolder(IFolder folder) throws CoreException {
        IContainer parent = folder.getParent();
        if (parent instanceof IFolder) {
            createFolder((IFolder) parent);
        }
        if (!folder.exists()) {
            folder.create(false, true, null);
        }
    }

    /**
     * Create a folder structure with a parent root, overlay, and a few child
     * folders.
     *
     * @param newProject
     * @param paths
     * @throws CoreException
     */
    private static void addToProjectStructure(IProject newProject, String[] paths) throws CoreException {
        for (String path : paths) {
            IFolder etcFolders = newProject.getFolder(path);
            createFolder(etcFolders);
        }
    }

    private static void addNature(IProject project) throws CoreException {
        if (!project.hasNature(ProjectNature.NATURE_ID)) {
            IProjectDescription description = project.getDescription();
            String[] prevNatures = description.getNatureIds();
            String[] newNatures = new String[prevNatures.length + 1];
            System.arraycopy(prevNatures, 0, newNatures, 0, prevNatures.length);
            newNatures[prevNatures.length] = ProjectNature.NATURE_ID;
            description.setNatureIds(newNatures);

            IProgressMonitor monitor = null;
            project.setDescription(description, monitor);
        }
    }

}

For the above tests to run you need to export some of the packages from the customplugin project. In the customplugin plugin.xml Runtime tab add the following packages to the Exported Packages list:

  • customplugin.natures
  • customplugin.projects

Finally, let’s add CustomProjectSupport to the CustomProjectNewWizard:

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

        CustomProjectSupport.createProject(name, location);

        return true;
    }

One last thing: let’s set up the process of creating a new project to end with the opening of the Custom Plug-in Perspective.

Select the “Custom Project (wizard)” entry under org.eclipse.ui.newWizards.

  • finalPerspective: customplugin.perspective
  • Save the file

Add IExecutableExtension to CustomProjectNewWizard:

public class CustomProjectNewWizard extends Wizard implements INewWizard, IExecutableExtension {

Let the editor add the unimplemented (and empty) method setInitializationData().

Before you implement the method add the following field to hold the plug-in configuration information necessary to make the perspective change:

private IConfigurationElement _configurationElement;

The plug-in will call setInitializationData() to supply the plug-in with the information it needs to display the proper perspective when performFinish() is complete.

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

In performFinish() add the call to updatePerspective():

    @Override
    public boolean performFinish() {
        String name = _pageOne.getProjectName();
        URI location = null;
        if (!_pageOne.useDefaults()) {
            location = _pageOne.getLocationURI();
            System.err.println("location: " + location.toString()); //$NON-NLS-1$
        } // else location == null

        CustomProjectSupport.createProject(name, location);
        // Add this
        BasicNewProjectResourceWizard.updatePerspective(_configurationElement);

        return true;
    }

All done. Go create a project and check that the tests actually did their jobs. For extra points, open the Custom Project Navigator. It should show you the same thing as the Package Navigator or the plain vanilla Navigator view.

On the off chance I missed something or did not explain something properly, please let me know. I wrote this all down as I was doing it so I may have missed a step or 12.

Perhaps I should re-release my book….

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

July 19, 2009 4 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.

Writing an Eclipse Plug-in (Part 2): Creating a custom project in Eclipse – Adding to the New Project Wizard

July 11, 2009 19 comments

Last time I discussed this I mentioned that the steps to create the custom project will be:

  • Open the New Wizard dialog.
  • Open the Custom Project folder
  • Select the Custom Project item
  • Press Next
  • Enter the name of the project and a location in which to put it
  • Press Finish
  • A custom perspective will open displaying a custom navigator.

In this entry I will not be implementing any behavior. Soon. However, the GUI pieces leading to the desired workflow will be implemented.

Let’s get to work.

Assumptions: Eclipse 3.4.

1. Create a Plug-in Project

  • Name: customplugin
  • Eclipse version: 3.4
  • Click Next
  • Execution environment: JavaSE-1.6
  • Would you like to create a rich client application? No.
  • Click Finish

The only class that should have been generated was customplugin.Activator. It can be ignored for now.

2. In the Extension tab:

  • Click Add
  • Type new and select the org.eclipse.ui.newWizards extension (do not choose any available templates)
  • Click Finish.
  • Right click on org.eclipse.ui.newWizards and select New –> Category
  • With the “name (category)” selected enter an id of customplugin.category.wizards
  • Name: Custom Wizards
  • Save the file
  • Right click on org.eclipse.ui.newWizards and select New –> Wizard
  • With the “name (wizard)” selected enter:
    • id: customplugin.wizard.new.custom
    • name: Custom Project
    • class: customplugin.wizards.CustomProjectNewWizard
    • category: customplugin.category.wizards
  • Save the file

3. Create the CustomProjectWizard class

  • Click on the class link for customplugin.wizards.CustomProjectNewWizard to open the New Class Wizard.
  • In the New Class Wizard click Finish.
  • The CustomProjectNewWizard class will open in the workspace.

Believe it or not this is enough for a quick test of the plug-in:

  • Right click on the customplugin label in the Package Explorer and select Run As –> Run Configurations.
  • Right click on Eclipse Applications and select New
    • Name: customplugin
  • Click on the Plug-ins tab to the right
  • Launch with: Plug-ins selected below
  • Uncheck the Workspace folder
  • Check the customplugin project
  • Click Run

When the runtime workbench opens press Ctrl+N. The Custom Wizards folder should be under the General folder and the Custom Project item should be in the Custom Wizards folder.

Clicking Next will do nothing. That is fine; we will take care of that next. Quit the runtime workbench.

4. Add the WizardNewProjectCreationPage to the CustomProjectWizard

  • Add a private field to the CustomProjectWizard:
        private WizardNewProjectCreationPage _pageOne;
  • You will get a compile error. Return to the plugin.xml file and select the Dependencies tab.
  • Click Add and select org.eclipse.ui.ide. Click Finish.
  • Return to CustomProjectWizard. Press Ctrl+Shift+O to add any missing imports.
  • Save the file. The compile error should disappear.
  • Override addPages() (defined in the parent Wizard class):
    @Override
    public void addPages() {
        super.addPages();

        _pageOne = new WizardNewProjectCreationPage("From Scratch Project Wizard");
        _pageOne.setTitle("From Scratch Project");
        _pageOne.setDescription("Create something from scratch.");

        addPage(_pageOne);
    }
  • Have performFinish() return true.
  • Have the constructor set the title field.
  •     public CustomProjectNewWizard() {
            setWindowTitle(WIZARD_NAME);
        }
    
  • Save the file.

Time for a manual test:

  1. Start the runtime workbench.
  2. Press Ctrl+N.
  3. Open the Custom Project folder.
  4. Select the Custom Project item and click Next.
  5. Enter a project name and click Finish.
  6. Whatever perspective you started with will still be there.
  7. Quit the runtime workbench.

Not bad for one extension with two entries and two Java files that were mostly (except for the wizard file) untouched by us.

Next, create a custom navigator using the Common Navigator framework. The Common Navigator View template will create a fully functioning navigator which will be ideal for our purposes.

In the Extension tab:

  • Click Add, click the Extension Wizards tab and select the Common Navigator View template.
  • Click Next
  • Enter the following:
    • View Id: customplugin.navigator
    • View Name: Custom Plug-in Navigator
    • Uncheck Add to Resource Perspective
  • Click Finish.
  • If a dialog opens requesting to save changes click Yes. Two extensions have been added as well as three dependencies.

Run the runtime workbench again. From the main menu select Window –> Show View. When the Show View dialog opens select the Other folder. The Custom Plug-in Navigator is found there. If you select it, the navigator will open in the current perspective. If you want to see something displayed in this navigator you can create a general project and create an empty file. Close the navigator before you exit the runtime workbench.

Time to create a custom perspective.

1. In the Extensions tab:

  • Add the perspectives extension
  • Click Add
  • Type “pers” (no quotes) and select the org.eclipse.ui.perspectives extension (do not choose any available templates)
  • Click Finish.
  • Enter the following:
    • id: customplugin.perspective
    • name: Custom Plug-in Perspective
    • class: customplugin.perspectives.Perspective
  • Save the plugin.xml
  • Click the class link and click Finish in the New Java Class wizard. Ignore the generated code (or better yet, close the Java editor)
  • Add the perspectiveExtensions extension
    • Click Add
    • Type “pers” (no quotes) and select the org.eclipse.ui.perspectiveExtensions extension
    • Click Finish.
  • Select the “* (perspectiveExtension)
  • targetID: customplugin.perspective
  • Save the file
  • Right click on “customplugin.perspective (perspectiveExtension)” and select New –> View
    • id: customplugin.navigator
    • relationship: left
    • relative: org.eclipse.ui.editorss
    • ratio: 0.25

Open the runtime workbench. Select Window –> Open Perspective –> Other –> Custom Plug-in Perspective. The perspective should have the custom navigator open and over to the left.

Cool, isn’t it? Open the Resource perspective before exiting the runtime workbench. When we add behavior Eclipse will automatically open our new Custom perspective.

This was not that hard; that is the point of of the Eclipse plug-in architecture. The problem is that Eclipse is large and non-trivial and more than half the battle is knowing what is already available to get things done.

Next time: adding testable behavior to the plug-in to create a custom project.

Writing an Eclipse Plug-in (Part 1)- What I’m going to do

July 8, 2009 6 comments

(The next few posts are a cheat. I have been slowly amassing these blogs for the last few weeks/months.)

So you want to write an Eclipse plug-in and don’t know where to start? Join the crowd. I wrote a great book on Eclipse (well, at least I liked it) and still find myself at a loss when it comes to implementing a standard feature set of functionality within a plug-in (sample chapters here).

I am going to document (read: heavily edit) what I did to implement a plug-in I am building to relearn all the things I wrote about in my book, but never really got a chance to use in this ever changing economy.

This blog will act as my requirements document as well as my FAQ. This way, when I forget how I did something I can just come here.

Where to Start?

Let’s say I have a software tool that is in desperate need of an IDE to help automate a number of silly tasks. That means a few things:

  • I want to aggregate the tasks
  • I want to aggregate any files involved in configuring the tool
  • I want to aggregate any source code involved in extending the tool

In addition, I want to use Mylyn to help me keep track of my tasks.

The list of things that can be done in Eclipse can be rather daunting. At a high-level this plug-in needs to accomplish a stardard list of things:

  • Create a project
  • Create some custom format XML files
  • Manage files within the project
  • Edit some XML files using a form-based approach rather than just editing the XML directly
  • Deploy files to a target directory

When it comes to adding functionality the best place to start is with use cases. Here are the ones I used:

  • Create a custom project
  • Edit a custom XML file
  • Deploy the files
  • Monitor how the deployed files have affected

The actor for all of the use cases will be a developer.

Create a custom project

The steps to create the custom project will be:

  1. Open the New Wizard dialog.
  2. Open the Custom Project folder
  3. Select the Custom Project item
  4. Press Next
  5. Enter the name of the project and a location in which to put it
  6. Press Finish
  7. A custom perspective will open

The custom perspective that will only display projects of my type. The project will contain custom categories where files will be displayed. The physical location of the files and the displayed location of the files in the navigator will be different. For example, the files may be located in:

root/
  folder1/
    filetype1-1.xml
  folder2/
  folder3/
    filetype2-1.xml
    filetype3-1.xml

The custom navigator will display the files in an almost flat structure:
My Project
  Category 1
    fileType1-1.xml
  Category 2
    fileType2-1.xml
  Category 3
    fileType3-1.xml

If the user wants to see the actual folder structure they can look at the project in the Navigator view.

The custom perspective will display:

  • A custom navigator
  • The Navigator view
  • The Outline view
  • A log file view
  • Some yet-to-be-announced views
  • Editors as appropriate

The custom navigator will display individual images next to the workspace, project, categories and various file types that are displayed.

The custom navigator will allow the standard Eclipse behaviors from a popup:

  • Cut/copy/paste
  • Rename
  • Refresh
  • Workspaces
  • New
  • Project
  • Other

Edit a Custom XML file

There are a few XML files that need to be edited in a consistent and reliable way. The user will interact with a custom editor that will let them modify the XML file through a form or as XML text. The editor will have multiple form pages and one text page for direct XML editing.

Deploy the files

Once the files are in place, copy the files to the proper locations in the target software.

Right-click on the workspace or project will deploy the entire project.
Right-click on a particular category will deploy only the contents of that category
Right-click on a particular file will deploy just that file

In Addition

The above is all well and good, but the plug-in should also adhere to best practices whenever possible and that includes testing, putting strings in separate property files and flagging other strings as ignorable, caching images and disposing them.

What about testing? Testing is certainly interesting in the development of an Eclipse plug-in. The Eclipse Plug-ins book explains how to test a plug-ins, but there are a few things to bear in mind when testing anything:

  • Don’t test the platform
  • Test new functionality
  • Test before fixing a bug

I don’t enjoy having to change the API of a class to support testing, but sometimes there aren’t a lot of ways to test an API without retrofitting Eclipse to support dependency injection.

Therefore I will not be writing tests when creating Wizards or perspectives, but I will be writing tests on the behavior that the various GUI components will execute when they are told to do something. For example, the GUI flow for the use case Create A Custom Project will consist of:

  • Opening the New Wizard dialog
  • Displaying one or more Wizard pages
  • Clicking Finish causes the custom perspective to open
  • Clicking Cancel leaves everything the way it was

The flow, as a first phase of implementation, can work with little or no code. Whatever code needs to be written will just be glue code. Once those pieces come together then behavior can be written:

  • Create a custom project
  • Display the custom project in a custom navigator

Bear in mind that the Eclipse Plug-in Wizard has a number of templates that you can select from to create most of what you need to implement the first phase of a plug-in. The problem is that some of the things I needed were not there so I thought it better to assemble it piece by piece and blog about it.

Sorry for all the talking and not implementing anything. And BTW, I might never finish. Hope that is not a problem.

Personal Wiki: A Brief Opinionated Review of MoinMoin

July 3, 2009 1 comment

I have just started using MoinMoin as a personal wiki on my Kubuntu/Dell notebook. Why would I do such a preposterous thing?

I have been reading the book Pragmatic Thinking and Learning by Andy Hunt. I will have to review it one day, but suffice it to say that I am on my third reading*.

In Pragmatic Thinking and Learning, Andy Hunt mentions using a wiki to rapidly collect information in a connected, but non-linear, way. The second time I read the book I decided to learn deliberately and when I got to page 221 and read about wikis I started to research what wikis were available, what technology they used and how easy they were to use (wikis by defintion are easy to use, but you never know).**

After much hand-wringing and comparison shopping I decided to download MoinMoin. It is Python-based and does not rely on an external web server or database to work. Installing it was as easy as extracting the archive into my local bin directory and running the Python file wikiserver.py. It starts its own little web server sitting on port 8080 and running Firefox on http://localhost:8080 brought up the home page.

Just to be paranoid I created an account (why would I need to do that on my own local box? Did I mention I was paranoid?) and started creating pages. It was so easy a caveman user could do it.

If you know what a wiki is you don’t need much more information than this to convince you that I enjoyed using MoinMoin. Its use of Python, its ease of installation and ease of use made it almost a no-brainer. I am a Java guy from way back, and as much as I disagree with Python’s use of indentation as a measure of scope, I am quite happy with Python in general. MoinMoin made me even happier.

Give it a shot. Your ideas will thank you.

* Yes, third. The last book I read that many times was Domain-Driven Design by Eric Evans. That is another book that did such a great job of joining ideas that had been floating in my head for years that I recommend it to everyone every chance I get (my mother is still trying to understand what he is talking about, but I am sure she will get it one day. After all, she is 85.).

** Andy Hunt ended the section on wikis by recommending the use of an iPhone with a Ruby-based web server with wiki software to make the collecting of ideas as frictionless as possible. Four out of four dinosaurs disagree.