Home > Eclipse development, Refactoring, Software Development > Writing an Eclipse Plug-in (Part 23): Common Navigator: Rewriting History

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



[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).

If I don’t post this soon blame the local cafe. I swear I did not eat that donut.

What (are we doing?)

Way back in the sands of time, I defined the default paths in this project to be:

\clause
\clause\java
\clause\java\source
\clause\java\source\hidden-clause
\deployment-files
\deployment-files\java
\schema

Now, with the wisdom of age to guide me, I see that I really just want:
\schema
\stored-procedures

Short, sweet and a direct reflection of my laziness new found attraction to simplicity.

While having the various folder types was an inspired idea (I guess I meant to put Java files in some of them) I am not feeling so inspired right now (actually, that second donut has left me feeling a little nauseous and is probably affecting my ability to make decisions. I hope not to have any strange women knocking on my door. Who know what could happen).

How do we fix this? We start by correcting the tests. You remember the tests, don’t you? Our tests are located in the customplugin.test project (created in Part 4: New Project Wizard: The Behavior).

Once the tests are fixed, and the code they test is fixed, we continue with the clean-up: modify the deployment files and convert them to support the stored procedure efforts. Next time.

Let’s time travel.

How (are we doing it?)

  1. Update the folder tests to reflect the new folder structure.
    Update assertProjectDotFileAndStructureAndNatureExist and assertFolderStructureIn (check for the good folders and the bad/unwanted folders) to only create the folders we want.

    CustomProjectSupportTest.java

        @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" };
    
            try {
                Assert.assertNotNull(project);
                assertFileExists(projectFilePath);
                assertAllElementsEmptyExcept(projectFilePath, emptyNodes, nonEmptyNodes);
                assertNatureIn(project);
                assertFolderStructureIn(projectPath);
            } finally {
                // always do this before returning
                project.delete(true, null);
            }
        }
    
        @SuppressWarnings("nls")
        private void assertFolderStructureIn(String projectPath) {
            String[] goodPaths = {
                    "schema", //$NON-NLS-1$
                    "stored-procedures" }; //$NON-NLS-1$
            for (String path : goodPaths) {
                File file = new File(projectPath + "/" + path);
                if (!file.exists()) {
                    Assert.fail("Folder structure " + path + " does not exist.");
                }
            }
    
            String[] badPaths = {
                    "deployment-files",
                    "clause"}; //$NON-NLS-1$
            for (String path : badPaths) {
                File file = new File(projectPath + "/" + path);
                if (file.exists()) {
                    Assert.fail("Folder structure " + path + " does exist.");
                }
            }
        }
    
  2. Update the CustomProjectSupport code to reflect the new folder structure.

    CustomProjectSupport.java

        /**
         * 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 = {
                        "schema", //$NON-NLS-1$
                        "stored-procedures"}; //$NON-NLS-1$
                addToProjectStructure(project, paths);
            } catch (CoreException e) {
                e.printStackTrace();
                project = null;
            }
    
            return project;
        }
    
  3. Start the runtime workbench and from the Resource perspective confirm there are only 2 folders under the project: schema and stored-procedures.
  • Change the deployment code/files to support stored procedures.

    Convert the following:

    • CustomProjectNewDeploymentFile.java
      • Refactor: Rename the class CustomProjectNewDeploymentFile to CustomProjectNewStoredProcedureFile
      • Change the wizard name from
            private static final String WIZARD_NAME = "New Deployment File"; //$NON-NLS-1$
        

        to

            private static final String WIZARD_NAME = "New Stored Procedure File"; //$NON-NLS-1$
        
    • WizardDeploymentNewFileCreationPage.java
      • Refactor: Rename the class WizardDeploymentNewFileCreationPage to WizardStoredProcedureNewFileCreationPage
      • Open the Externalize String Wizard (open the file, right click and select Source –> Externalize Strings) and change the string Deployment to StoredProcedure
      • Change the page name from
            private static final String PAGE_NAME = "Custom Plug-in Deployment File Wizard"; //$NON-NLS-1$
        

        to

            private static final String PAGE_NAME = "Custom Plug-in Stored Procedure File Wizard"; //$NON-NLS-1$
        
    • messages.properties – this gets repaired as a side-effect of the Externalize Strings step.
    • NewWizardMessages.java – this gets repaired as a side-effect of the Externalize Strings step.
    • plugin.xml – Change the following 4 key names
      • %wizard.name.deployment to %wizard.name.storedprocedure
      • %content-type.name.deployment to %content-type.name.storedprocedure
      • %deployment.label to %storedprocedure.label
      • %deployment.tooltip to %storedprocedure.tooltip
    • bundle.properties – change the following 4 strings to match the changes above and to update the value they reference.
      • Change
        content-type.name.deployment = Hidden Clause Deployment Definition
        ...
        wizard.name.deployment = Deployment File
        ...
        deployment.label = New Deployment File
        deployment.tooltip = New Deployment File
        

        to

        content-type.name.storedprocedure = Hidden Clause Stored Procedure Definition
        ...
        wizard.name.storedprocedure = Stored Procedure File
        ...
        storedprocedure.label = New Stored Procedure File
        storedprocedure.tooltip = New Stored Procedure File
        
    • plugin.xml – Change the following 4 key names (look for the HERE comments)
      ...
            <wizard
                  category="customplugin.category.wizards"
                  class="customplugin.wizards.CustomProjectNewStoredProcedureFile"
                  descriptionImage="icons/deployment-file_32x32.png"
                  icon="icons/deployment-file_16x16.png"
                  id="customplugin.wizard.file.storedprocedure"  <!-- HERE -->
                  name="%wizard.name.storedprocedure">
            </wizard>
      ...
            <content-type
                  base-type="org.eclipse.core.runtime.xml"
                  file-extensions="xml"
                  id="customplugin.contenttype.storedprocedure"  <!-- HERE -->
                  name="%content-type.name.storedprocedure"
                  priority="normal">
               <describer
                     class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
                  <parameter
                        name="element"
                        value="hc-deployment">
                  </parameter>
               </describer>
            </content-type>
      ...
               <newWizardShortcut
                     id="customplugin.wizard.file.storedprocedure">  <!-- HERE -->
               </newWizardShortcut>
      ...
                  <command
                        commandId="org.eclipse.ui.newWizard"
                        icon="icons/deployment-file_16x16.png"
                        label="%storedprocedure.label"
                        style="push"
                        tooltip="%storedprocedure.tooltip">
                     <parameter
                           name="newWizardId"
                           value="customplugin.wizard.file.storedprocedure">  <!-- AND HERE -->
                     </parameter>
                     <visibleWhen
                           checkEnabled="false">
                        <with
                              variable="activeWorkbenchWindow.activePerspective">
                           <equals
                                 value="customplugin.perspective">
                           </equals>
                        </with>
                     </visibleWhen>
                  </command>
      ...
      
    • deployment-template.xml
      • Refactor: rename the file to stored-procedures-template.xml
      • Refactor: rename the XML element <hc-deployment> to <hc-stored-procedures>
    • plugin.xml – Match up the change made in the last step so the plug-in will recognize the XML file type (look for the HERE comments again)
      ...
            <content-type
                  base-type="org.eclipse.core.runtime.xml"
                  file-extensions="xml"
                  id="customplugin.contenttype.storedprocedure"
                  name="%content-type.name.storedprocedure"
                  priority="normal">
               <describer
                     class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
                  <parameter
                        name="element"
                        value="hc-stored-procedures">  <!-- HERE -->
                  </parameter>
               </describer>
            </content-type>
      ...
      

    Open the Custom Plug-in Perspective in the runtime workbench and check the main menu and toolbar.

  • Why (did we do it that way?)

    Fixing the Past

    1. Update the folder tests to reflect the new folder structure.
      Since we only want to test the minimum number of things that can go right and wrong we will check that the folders we expect are there and that the folders we don’t expect are not there. This means we have to update assertProjectDotFileAndStructureAndNatureExist (clean-up no matter what happens) and assertFolderStructureIn (check for the good folders and the bad/unwanted folders). When they fail we update CustomProjectSupport.createProject() to only create the folders we want.

      CustomProjectSupportTest.java

          @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" };
      
              try {
                  Assert.assertNotNull(project);
                  assertFileExists(projectFilePath);
                  assertAllElementsEmptyExcept(projectFilePath, emptyNodes, nonEmptyNodes);
                  assertNatureIn(project);
                  assertFolderStructureIn(projectPath);
              } finally {
                  // always do this before returning
                  project.delete(true, null);
              }
          }
      
          @SuppressWarnings("nls")
          private void assertFolderStructureIn(String projectPath) {
              String[] goodPaths = {
                      "schema", //$NON-NLS-1$
                      "stored-procedures" }; //$NON-NLS-1$
              for (String path : goodPaths) {
                  File file = new File(projectPath + "/" + path);
                  if (!file.exists()) {
                      Assert.fail("Folder structure " + path + " does not exist.");
                  }
              }
      
              String[] badPaths = {
                      "deployment-files",
                      "clause"}; //$NON-NLS-1$
              for (String path : badPaths) {
                  File file = new File(projectPath + "/" + path);
                  if (file.exists()) {
                      Assert.fail("Folder structure " + path + " does exist.");
                  }
              }
          }
      
    2. Update the CustomProjectSupport code to reflect the new folder structure.

      Nothing really to explain here: update the creation of the paths to reflect the new reality.

      CustomProjectSupport.java

          /**
           * 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 = {
                          "schema", //$NON-NLS-1$
                          "stored-procedures"}; //$NON-NLS-1$
                  addToProjectStructure(project, paths);
              } catch (CoreException e) {
                  e.printStackTrace();
                  project = null;
              }
      
              return project;
          }
      

      Start the runtime workbench and check your handiwork by looking at the project from the Resource perspective; There should only be 2 folders under the project: schema and stored-procedures.

      If you still see the old folder structure don’t fret (that’s for guitars); open the Launch configuration window:

      1. Select the launch configuration for your customplugin
      2. Select the Plug-ins tab
      3. Uncheck and check the Target Platform item so that all of the Eclipse platform plug-ins are selected. Make sure that under the Workspace node only the customnavigator and customplugin are checked.

        That was nice; but in the same way that dotting your i’s and crossing your t’s doesn’t give you the warm and fuzzies, this didn’t do much for me. However, the above was necessary to baseline our work.

    3. Change the deployment code/files to support stored procedures.

      In the normal course of development, we create files to help indirect things like localization.

      While indirection and abstraction are awesome in practice and look really cool when all the pieces are in place, they also introduce a level of rework that can be annoying. For example, since I had originally thought of having a deployment section in my project I created the infrastructure to support the creation of the deployment file in total disregard to the fact that I change my mind more often than the cat changes state.

      In order to implement the deployment wizard and the code that creates the file the following was created:

      • CustomProjectNewDeploymentFile.java
      • WizardDeploymentNewFileCreationPage.java
      • messages.properties
      • bundle.properties
      • deployment-template.xml

      Also, customplugin plugin.xml contains entries for the Custom Plug-in Perspective main and toolbar menus. We’ll have to change those entries to properly reflect my ever-changing tastes in features.

      To convert them we do the following:

      • CustomProjectNewDeploymentFile.java
        • Refactor: Rename the class CustomProjectNewDeploymentFile to CustomProjectNewStoredProcedureFile
        • Change the wizard name from
              private static final String WIZARD_NAME = "New Deployment File"; //$NON-NLS-1$
          

          to

              private static final String WIZARD_NAME = "New Stored Procedure File"; //$NON-NLS-1$
          
      • WizardDeploymentNewFileCreationPage.java
        • Refactor: Rename the class WizardDeploymentNewFileCreationPage to WizardStoredProcedureNewFileCreationPage
        • Open the Externalize String Wizard (open the file, right click and select Source –> Externalize Strings) and change the string Deployment to StoredProcedure
        • Change the page name from
              private static final String PAGE_NAME = "Custom Plug-in Deployment File Wizard"; //$NON-NLS-1$
          

          to

              private static final String PAGE_NAME = "Custom Plug-in Stored Procedure File Wizard"; //$NON-NLS-1$
          
      • messages.properties – this gets repaired as a side-effect of the Externalize Strings step.
      • NewWizardMessages.java – this gets repaired as a side-effect of the Externalize Strings step.
      • plugin.xml – Change the following 4 key names
        • %wizard.name.deployment to %wizard.name.storedprocedure
        • %content-type.name.deployment to %content-type.name.storedprocedure
        • %deployment.label to %storedprocedure.label
        • %deployment.tooltip to %storedprocedure.tooltip
      • bundle.properties – change the following 4 strings to match the changes above and to update the value they reference.
        • Change
          content-type.name.deployment = Hidden Clause Deployment Definition
          ...
          wizard.name.deployment = Deployment File
          ...
          deployment.label = New Deployment File
          deployment.tooltip = New Deployment File
          

          to

          content-type.name.storedprocedure = Hidden Clause Stored Procedure Definition
          ...
          wizard.name.storedprocedure = Stored Procedure File
          ...
          storedprocedure.label = New Stored Procedure File
          storedprocedure.tooltip = New Stored Procedure File
          
      • plugin.xml – Change the following 4 key names (look for the HERE comments)
        ...
              <wizard
                    category="customplugin.category.wizards"
                    class="customplugin.wizards.CustomProjectNewStoredProcedureFile"
                    descriptionImage="icons/deployment-file_32x32.png"
                    icon="icons/deployment-file_16x16.png"
                    id="customplugin.wizard.file.storedprocedure"  <!-- HERE -->
                    name="%wizard.name.storedprocedure">
              </wizard>
        ...
              <content-type
                    base-type="org.eclipse.core.runtime.xml"
                    file-extensions="xml"
                    id="customplugin.contenttype.storedprocedure"  <!-- HERE -->
                    name="%content-type.name.storedprocedure"
                    priority="normal">
                 <describer
                       class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
                    <parameter
                          name="element"
                          value="hc-deployment">
                    </parameter>
                 </describer>
              </content-type>
        ...
                 <newWizardShortcut
                       id="customplugin.wizard.file.storedprocedure">  <!-- HERE -->
                 </newWizardShortcut>
        ...
                    <command
                          commandId="org.eclipse.ui.newWizard"
                          icon="icons/deployment-file_16x16.png"
                          label="%storedprocedure.label"
                          style="push"
                          tooltip="%storedprocedure.tooltip">
                       <parameter
                             name="newWizardId"
                             value="customplugin.wizard.file.storedprocedure">  <!-- AND HERE -->
                       </parameter>
                       <visibleWhen
                             checkEnabled="false">
                          <with
                                variable="activeWorkbenchWindow.activePerspective">
                             <equals
                                   value="customplugin.perspective">
                             </equals>
                          </with>
                       </visibleWhen>
                    </command>
        ...
        
      • deployment-template.xml
        • Refactor: rename the file to stored-procedures-template.xml
        • Refactor: rename the XML element <hc-deployment> to <hc-stored-procedures>
      • plugin.xml – Match up the change made in the last step so the plug-in will recognize the XML file type (look for the HERE comments again)
        ...
              <content-type
                    base-type="org.eclipse.core.runtime.xml"
                    file-extensions="xml"
                    id="customplugin.contenttype.storedprocedure"
                    name="%content-type.name.storedprocedure"
                    priority="normal">
                 <describer
                       class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
                    <parameter
                          name="element"
                          value="hc-stored-procedures">  <!-- HERE -->
                    </parameter>
                 </describer>
              </content-type>
        ...
        

      If we open the Custom Plug-in Perspective in the runtime workbench we should see the following in the main menu:

      and the toolbar:

      Now the bad news: while this gets us closer to what we are supposed to have it is not there yet. I just wanted to get us a few steps closer. What the plug-in should have in the main menu and the toolbar are the same items that are shown in the popup. Once the popups are done we will go back and fix the main menu and toolbar. They are mostly configuration anyway.

      Okay! We are now at a point where we can move forward again (did I mention we would do that next time?).

    What Just Happened?

    Well, we updated the tests to guarantee our project folder structure was ready to accept our project resources and we renamed various files to support our stored procedure XML file and remove the deployment file support. Certainly was a lot of steps, but that is part of the refactoring game.

    Time travel is so much fun. Next trip: refactor out my grandfather.

    As usual: if you have a better way of doing something described above, let me know! Eclipse is a moving target; even though I miss it all the time my aim is getting better.

    References

    EGit

    EGit/User Guide
    EGit/Git For Eclipse Users
    Git With Eclipse
    Git Community Book

    Vampires

    Vampire Domestication – http://www.rifters.com/real/progress.htm

    Code

    Download this post’s code from the Eclipse Kick Start web site.

    CustomProjectSupportTest.java

    /**
     * Coder beware: this code is not warranted to do anything.
     *
     * Copyright Oct 31, 2009 Carlos Valcarcel
     */
    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 = "/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 = "/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" };
    
            try {
                Assert.assertNotNull(project);
                assertFileExists(projectFilePath);
                assertAllElementsEmptyExcept(projectFilePath, emptyNodes, nonEmptyNodes);
                assertNatureIn(project);
                assertFolderStructureIn(projectPath);
            } finally {
                // always do this before returning
                project.delete(true, null);
            }
        }
    
        @SuppressWarnings("nls")
        private void assertFolderStructureIn(String projectPath) {
            String[] goodPaths = {
                    "schema", //$NON-NLS-1$
                    "stored-procedures" }; //$NON-NLS-1$
            for (String path : goodPaths) {
                File file = new File(projectPath + "/" + path);
                if (!file.exists()) {
                    Assert.fail("Folder structure " + path + " does not exist.");
                }
            }
    
            // This can probably be removed as any paranoia we might have of
            // additional folders would best be served by logic that would check for
            // just the existence of the folders we care about and complain about
            // any other folders that were found.
            String[] badPaths = {
                    "deployment-files",
                    "clause"}; //$NON-NLS-1$
            for (String path : badPaths) {
                File file = new File(projectPath + "/" + path);
                if (file.exists()) {
                    Assert.fail("Folder structure " + path + " does 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;
        }
    
    }
    

    CustomProjectSupport.java

    /**
     * Coder beware: this code is not warranted to do anything.
     *
     * Copyright Oct 31, 2009 Carlos Valcarcel
     */
    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 = {
                        "schema", //$NON-NLS-1$
                        "stored-procedures"}; //$NON-NLS-1$
                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);
            }
        }
    
    }
    

    CustomProjectNewStoredProcedureFile.java

    /**
     * Coder beware: this code is not warranted to do anything.
     * Copyright Oct 31, 2009 Carlos Valcarcel
     */
    package customplugin.wizards;
    
    
    /**
     * @author carlos
     */
    public class CustomProjectNewStoredProcedureFile extends CustomProjectNewFile {
        private static final String WIZARD_NAME = "New Deployment File"; //$NON-NLS-1$
    
        public CustomProjectNewStoredProcedureFile() {
            super(WIZARD_NAME);
        }
    
        @Override
        public void addPages() {
            super.addPages();
    
            _pageOne = new WizardDeploymentNewFileCreationPage(_selection);
    
            addPage(_pageOne);
        }
    
    }
    

    WizardDeploymentNewFileCreationPage.java

    /**
     * Coder beware: this code is not warranted to do anything.
     *
     * Copyright Oct 31, 2009 Carlos Valcarcel
     */
    package customplugin.wizards;
    
    import java.io.IOException;
    import java.io.InputStream;
    
    import org.eclipse.jface.viewers.IStructuredSelection;
    import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
    
    import customplugin.Activator;
    
    /**
     * @author carlos
     *
     */
    public class WizardDeploymentNewFileCreationPage extends WizardNewFileCreationPage {
    
        private static final String PAGE_NAME = "Custom Plug-in Stored Procedure File Wizard"; //$NON-NLS-1$
    
        public WizardDeploymentNewFileCreationPage(IStructuredSelection selection) {
            super(PAGE_NAME, selection);
            
            setTitle(NewWizardMessages.WizardStoredProcedureNewFileCreationPage_StoredProcedure_File_Wizard);
            setDescription(NewWizardMessages.WizardStoredProcedureNewFileCreationPage_Create_a_StoredProcedure_File);
            setFileExtension(NewWizardMessages.WizardStoredProcedureNewFileCreationPage_StoredProcedure_File_Extension);
        }
    
        @Override
        protected InputStream getInitialContents() {
            String templateFilePath = NewWizardMessages.WizardStoredProcedureNewFileCreationPage_StoredProcedure_Template_Location;
            InputStream inputStream = null;
            try {
                inputStream = Activator.getDefault().getBundle().getEntry(templateFilePath).openStream();
            } catch (IOException e) {
                // send back null
            }
    
            return inputStream;
        }
    
    }
    

    messages.properties

    CustomProjectNewWizard_Create_something_custom=Create something custom.
    CustomProjectNewWizard_Custom_Plugin_Project=Custom Plug-in Project
    WizardStoredProcedureNewFileCreationPage_Create_a_StoredProcedure_File=Create a Deployment File
    WizardStoredProcedureNewFileCreationPage_StoredProcedure_File_Extension=xml
    WizardStoredProcedureNewFileCreationPage_StoredProcedure_File_Wizard=Deployment File Wizard
    WizardStoredProcedureNewFileCreationPage_StoredProcedure_Template_Location=/templates/deployment-template.xml
    WizardSchemaNewFileCreationPage_Create_a_Schema_File=Create a Schema File
    WizardSchemaNewFileCreationPage_Schema_File_Extension=xml
    WizardSchemaNewFileCreationPage_Schema_File_Wizard=Schema File Wizard
    WizardSchemaNewFileCreationPage_Schema_Template_Location=/templates/schema-template.xml
    

    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.CustomProjectNewStoredProcedureFile"
                descriptionImage="icons/deployment-file_32x32.png"
                icon="icons/deployment-file_16x16.png"
                id="customplugin.wizard.file.storedprocedure"
                name="%wizard.name.storedprocedure">
          </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.storedprocedure"
                name="%content-type.name.storedprocedure"
                priority="normal">
             <describer
                   class="org.eclipse.core.runtime.content.XMLRootElementContentDescriber2">
                <parameter
                      name="element"
                      value="hc-stored-procedures">
                </parameter>
             </describer>
          </content-type>
       </extension>
       <extension
             point="org.eclipse.ui.perspectiveExtensions">
          <perspectiveExtension
                targetID="customplugin.perspective">
             <newWizardShortcut
                   id="customplugin.wizard.new.custom">
             </newWizardShortcut>
             <newWizardShortcut
                   id="customplugin.wizard.file.schema">
             </newWizardShortcut>
             <newWizardShortcut
                   id="customplugin.wizard.file.storedprocedure">
             </newWizardShortcut>
          </perspectiveExtension>
       </extension>
       <extension
             point="org.eclipse.ui.menus">
          <menuContribution
                locationURI="toolbar:org.eclipse.ui.main.toolbar">
             <toolbar
                   id="customplugin.toolbar">
                <command
                      commandId="org.eclipse.ui.newWizard"
                      icon="icons/project-folder.png"
                      label="%customproject.label"
                      style="push"
                      tooltip="%customproject.tooltip">
                   <parameter
                         name="newWizardId"
                         value="customplugin.wizard.new.custom">
                   </parameter>
                   <visibleWhen
                         checkEnabled="false">
                      <with
                            variable="activeWorkbenchWindow.activePerspective">
                         <equals
                               value="customplugin.perspective">
                         </equals>
                      </with>
                   </visibleWhen>
                </command>
                <command
                      commandId="org.eclipse.ui.newWizard"
                      icon="icons/schema-file_16x16.png"
                      label="%schema.label"
                      style="push"
                      tooltip="%schema.tooltip">
                   <parameter
                         name="newWizardId"
                         value="customplugin.wizard.file.schema">
                   </parameter>
                   <visibleWhen
                         checkEnabled="false">
                      <with
                            variable="activeWorkbenchWindow.activePerspective">
                         <equals
                               value="customplugin.perspective">
                         </equals>
                      </with>
                   </visibleWhen>
                </command>
                <command
                      commandId="org.eclipse.ui.newWizard"
                      icon="icons/deployment-file_16x16.png"
                      label="%storedprocedure.label"
                      style="push"
                      tooltip="%storedprocedure.tooltip">
                   <parameter
                         name="newWizardId"
                         value="customplugin.wizard.file.storedprocedure">
                   </parameter>
                   <visibleWhen
                         checkEnabled="false">
                      <with
                            variable="activeWorkbenchWindow.activePerspective">
                         <equals
                               value="customplugin.perspective">
                         </equals>
                      </with>
                   </visibleWhen>
                </command>
             </toolbar>
          </menuContribution>
       </extension>
       <extension
             point="org.eclipse.ui.actionSets">
          <actionSet
                id="customplugin.toolbar"
                label="%toolbar.actionSet.label">
          </actionSet>
       </extension>
    
    </plugin>
    
    About these ads
    1. August 15, 2010 at 2:52 pm

      For your EGit issues, make sure you are using the latest nightly builds. They are much more complete functionality wise than the 0.8 release that came with Helios.

      • cvalcarcel
        August 15, 2010 at 7:36 pm

        What? And risk being on the bleeding edge?

        What was I thinking? I haven’t given enough blood to my profession yet so of course I will try that!

        I have updated the EGit Eclipse Update Site path and upgraded to 0.9.0.

        Hmm. I forced the last commit back in and it still has a status of staging. Perhaps next time….

    2. David
      January 10, 2013 at 1:07 pm

      Hi man,

      This is the best eclipse plugin tutorial I have ever read. Eclipse should give you an award as they did to Lars Vogel. It really helped new biginners in eclipse plugin development like me.

      I have some questin for you: Can I add some custom fields in the project creatino wizard (such as using Struts or JFS? if JFS, create some JSF files. If Struts, create some Struts files, etc.). If yes, how can i do it?

      Thanks you so much!

    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

    Follow

    Get every new post delivered to your Inbox.

    Join 3,182 other followers

    %d bloggers like this: