Skip to content

November 28, 2008

8

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!

Thanks for reading this post. Follow me on twitter here to be notified about updates and other posts I write. Or, subscribe to my RSS feed here

Fork me on GitHub
Read more from Conferences, Eclipse, MDSD
  • http://ekkes-corner.org ekke

    hi peter,
    I’m still using oaw 4.3 together with eclipse 3.4.1 (and 3.5M3) -
    now I see that I can also use MWE and XPAND als M3 download :-)

    do you think I can use it in a large project (development, not
    production) or is M3 too early ?

    I need Workflow EMF UML2 XPAND XTEND CHECK
    and also something from oaw utils

    you mentioned MWE and XPAND
    …and where do I find XTEND and CHECK ?

    have I to do refactorings to match the new projects , namespaces etc ?

    thx for tips

    ekke

  • http://www.peterfriese.de Peter

    Hej Ekke,

    although MWE and Xpand are tagged as 0.7M3, they are basically a slightly refactored copy of the respective versions from oAW 4.3.x. However, you’ll likely have to perform some refactorings on your code. Besides, we’re in the progress of getting oAW 4.3.1 out of the door which is meant to be used until Galileo is released.

    Peter

  • http://ekkes-corner.org ekke

    hi peter,

    thx….

    then I think its better to use 4.3.1 as next step
    and later switch to Galileo Milestones

    ekke

  • http://www.jevon.org Jevon

    Thanks for the very clear and straighforward post on how to use Eclipse Xpand in a project! It looks really interesting.

  • http://jwbito.ballardview.com/ John

    Hi Peter,

    This is a very helpful post. The means of manipulating the EMF meta-model is still a bit opaque for me, but you’ve clarified the use of the Xpand interfaces very nicely.

    Thanks!
    John

  • zakir

    Hello Peter,

    Thank you very much this post…it is helping me a lot..i had a very similar requirement of invoking expand templates with out using workflow…

    Thanks,

    Zakir

  • http://www.ralfebert.de/blog/ Ralf Ebert

    Thanks for this blog post, this was exactly what I was looking for.

    btw, I wrote a little utility bundle for my specific use case so I can set up XpandFacade easily & similar to a template engine:

    XpandTemplateEngine engine = new XpandTemplateEngine();
    engine.addPackages(SomePackage.eINSTANCE);
    engine.generate(this.getClass(), “MyTemplate::main”, myEmfObject, new File(“/tmp/output”));

    http://github.com/ralfebert/de.ralfebert.xpand.engine

  • LanPhan

    Hello Peter,

    Thanks for your tip. I use your code to create my example with a little change as following:
    1/ Your plugin is first plugin, and I create 2 other plugins named com.xpand.test1 and com.xpand.test2:

    content of com.xpand.test1 only have 1 class:
    public class XpandRequestInfo {
    String definitionName;
    Object target;
    MetaModel metamodel;
    // getter + setter for 3 attributes

    }

    content of com.xpand.test2 only have 1 class:
    public class CodeGeneration {
    public static void generate(XpandRequestInfo request) {
    ….
    XpandExecutionContextImpl execCtx = new XpandExecutionContextImpl(output, null, globalVarsMap, null, null);
    execCtx.registerMetaModel(request.getMetamodel());

    XpandFacade facade = XpandFacade.create(execCtx);
    facade.evaluate(request.getDefinitionName(), request.getTargetObject());
    }
    }

    Your plugin refers to com.xpand.test1 and com.xpand.test2. com.xpand.test2 refers to com.xpand.test1.

    Code in your step 12 just change to call static method generate() in com.xpand.test2.

    (In general, my code just differs with yours in 1 point: template file and code to run XpandFacade are in 2 different plugins)
    But when I run it, I got error like this:
    EvaluationException occured when invoking code from plugin “com.xpand.test2″: No Definition template::BuildSpecification::main for Object could be found.

    It seems that the template file is requested in the same plug-in with invoking to XpandFacade methods. But I want to separate template files with XpandFacade invokes, how can I solve it?

    Thanks in advanced.