Using Xpand in Your Eclipse Wizards

At ESE, I had a nice chat with Chris (actually, I had a lot of nice chats with a lot of nice people - it was quite a challenge to attend any of the sessions), who told me that he was looking into template engines. I leave it up to you to make any assumptions on why he's interested in template engines. I happen to know at least three template engines: Velocity (which I had the joy of using in my active days at AndroMDA.org), the template, erhm, ... mechanism being used in PDE (I once fixed a bug which had been caused by this engine) and - you might have guessed it - Xpand (which I am a committer on).

So, I gave a short demo of Xtext and Xpand to show Chris how easy it is to create (model-aware) code generation templates. Xpand comes with a nice editor which makes template editing a joy - it features code highlighting, hyperlink navigation, model-awareness and other editor goodness.

Instead of letting only Chris know how that works, here's a short guide on how to write your own Xpand template and use it in a wizard. To make the example more realistic, I chose to implement a wizard that creates an Ant build.xml file for a simple project (something which is missing in Eclipse, AFAIK). So, here we go.

Prerequisites:

How to do it

  1. Download and install the prerequisites.
  2. Create a new plug-in project named "de.peterfriese.antwizard"
  3. Add a New File Wizard to the project. I used the Custom plug-in Wizard to get me started.
  4. Brush up the wizard page and add some fields for project name, source folder and binary folder.
  5. We want to transfer the values entered in this wizard into our template, so we need to create an Ecore model that describes our data model. antwizard_metamodel
  6. Add org.eclipse.emf and org.eclipse.emf.edit to the dependencies of you plug-in.
  7. Create a genmodel and let EMF generate the model code for you.
  8. Add code to your wizard that transfers the values from the input fields to your model. One might consider using databinding for doing this, I used a straight forward read'n'write approach for the time being:
    public boolean performFinish() {
        final String containerName = page.getContainerName();
        final String srcDirName = page.getSrcDirName();
        final String binDirName = page.getBinDirName();
        // ...
    }
    private BuildSpecification createModel(IProject project, String srcDirName,
        String binDirName) {
        BuildSpecification buildSpecification = BuildspecificationFactory.eINSTANCE.createBuildSpecification();
        Project projectSpecification = BuildspecificationFactory.eINSTANCE.createProject();
        projectSpecification.setName(project.getName());
        projectSpecification.setBinaryFolder(binDirName);
        projectSpecification.setSourceFolder(srcDirName);
        buildSpecification.setProject(projectSpecification);
        return buildSpecification;
    }
  9. Create an Xpand template de.peterfriese.antwizard/src/template/BuildTemplate.xpt:

     

    Ant file template


    In case you wonder how to get those funky characters: make sure us create this file using the Xpand editor - it has code assist (CTRL + space) for those characters.

  10. Create an Xtend file de.peterfriese.antwizard/src/template/GeneratorExtensions.ext:

     

    Ant file extensions
  11. Add org.eclipse.xpand, org.eclipse.xtend and org.eclipse.xtend.typesystem.emf to the depencies of your plug-in.
  12. Finally, we need to provide some code to invoke the generator:
    private void generate(BuildSpecification buildSpec, IProgressMonitor monitor) throws CoreException {
    
        // get project root folder as absolute file system path
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IResource resource = root.findMember(new Path(buildSpec.getProject().getName()));
        String containerName = resource.getLocation().toPortableString();
    
        // configure outlets
        OutputImpl output = new OutputImpl();
        Outlet outlet = new Outlet(containerName);
        outlet.setOverwrite(true);
        output.addOutlet(outlet);
    
        // create execution context
        Map globalVarsMap = new HashMap();
        XpandExecutionContextImpl execCtx = new XpandExecutionContextImpl(output, null, globalVarsMap, null, null);
        EmfRegistryMetaModel metamodel = new EmfRegistryMetaModel() {
            @Override
            protected EPackage[] allPackages() {
                return new EPackage[] { BuildspecificationPackage.eINSTANCE, EcorePackage.eINSTANCE };
            }
        };
        execCtx.registerMetaModel(metamodel);
    
        // generate
        XpandFacade facade = XpandFacade.create(execCtx);
        String templatePath = "template::BuildTemplate::main";
        facade.evaluate(templatePath, buildSpec);
    
        // refresh the project to get external updates:
        resource.refreshLocal(IResource.DEPTH_INFINITE, monitor);
    }

That's it!

Taking it for a spin

  1. Launch a runtime workbench by selecting de.peterfriese.antwizard/META-INF/MANIFEST.MF and invoking Run As -> Eclipse Application
  2. Create a new Java project in the runtime workspace.
  3. Invoke your wizard by selecting File -> New -> Other... -> Ant -> Ant build file from existing project
  4. Enter the required information (project name, source folder, binariy folder)
  5. After clicking on finish, you should get a fresh Ant file for your project:

     

    Ant file
  6. Enjoy!

You can download the project here. Feedback and comments are welcome!

 

From http://www.peterfriese.de

0

Peter Friese is a software architect with itemis. He is a committer for the open source projects openArchitectureWare, Eclipse Modeling, FindBugs, and AndroMDA. As a software engineer and software architect, Peter has worked on a variety of industry projects in different domains such as banking, aerospace and transport. He is a regular speaker at various conferences and has authored a number of articles on topics like Eclipse, Spring, and model-driven software development. He can be contacted at peter.friese@itemis.de. Peter is a DZone MVB and is not an employee of DZone and has posted 13 posts at DZone.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Steffen Kobilke replied on Mon, 2009/04/06 - 11:23am

It works until protected regions are used.  How do you configure a resolver (if, like here, xpand is used programmaticly) ?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.