SourceForge.net Logo
(C) Copyright Vova Grechka 2004. All Rights Reserved

Introduction

Why use all those complicated MDAs, XMIs, EMFs, UMLs and other buzz stuff if You only need to generate some code based on Your model defined in simple XML file? I was tired trying to find just right code generation framework for my everyday needs. Some existing frameworks want me to spend hours drawing stupid UML diagrams, some are strictly specialized and don't allow me to define new generation patterns, some introduce new syntaxes and languages, etc, etc, etc... So, I finally did it myself.

Get it

Download lates QuiT version here. Sources are included. You'll need Eclipse 3 M6 or higher to use this plugin (it was developed using M7 version but should run on M6 too). Send comments and wishes to into_box@mail.ru. Visit SourceForge project page.

How to define a generation pattern

Let's create new pattern which will generate JavaBeans. First, there is pattern repository root. You specify it in Window->Preferences->QuiT->Repository. I recommend to add Your repository root into Eclipse workspace as simple project, so You'll be able to edit Your patterns while working on other projects. (1) Create new folder under repository root and name it bean.

(2) Create new Java project for which You'll generate beans. I assume source folder is src (of course it could be anything other but in this case You'll have to slightly modify code of this sample). Create folder quit under Java project and place beans.xml file there, this file is XML model of Your beans.

(3) Now it's time to develop structure of our XML model and edit beans.xml file according to this structure. Suppose we want to be able to define several packages each containing several beans. Each bean is described by it's name and collection of properties (property has a name and a type). So, our XML could be something like this:

<beans _quit="bean" dir="src">
	<package name="org.into.demo">
		<bean name="User">
			<property name="username" type="String"/>
			<property name="password" type="String"/>
			<property name="enabled" type="boolean"/>
			<property name="permissions" type="Permission[]"/>
		</bean>
		
		<bean name="Permission">
			<property name="name" type="String"/>
			<property name="level" type="int"/>
		</bean>
	</package>
	
	<package name="org.into.another.demo">
		<bean name="MyBean">
			<property name="foo" type="java.lang.Boolean"/>
			<property name="bar" type="boolean"/>
		</bean>
	</package>
</beans>

Notice that root element has attribute _quit which is set to "bean". This is only requirement of QuiT framework on Your model files: _quit attribute of root element should specify pattern which will be used for code generation based on the model. On the contrary dir attribute is not required by framework, this attribute will be used later in our pattern to tell generator where to put generated files.

(4) Create control script. Control script defines file generation process. It is called control.bsh and has to be placed in pattern folder (i.e. QuitRepository/bean/control.bsh in our case). If You don't know what BeanShell is go to www.beanshell.org and read tutorial. BeanShell is nice Java scripting language in which You'll define Your control scripts and scripts in template files. I have chosen BeanShell cause if You know Java (and You do, don't You?) You don't have to learn anything new, BeanShell's syntax is actually Java with some handy simplifications. Another thing You should know here is small subset of JDOM API (www.jdom.org). JDOM is convenient Java wrapper around DOM and You'll use it to access XML model from scripts. OK, let's look at our control.bsh source:

dir = rt.attr("/dir");

for (pack : rt.els("/package")) {
	for (bean : pack.getChildren("bean")) {
		fname = dir + "/" + ju.packageToDir(pack.getAttributeValue("name"))
			+ "/" + bean.getAttributeValue("name") + ".java";
		
		rt.merge("bean.quit", fname, bean);
	}
}

As You can see QuiT exposes runtime object called rt to the script. This runtime object has some methods to retrieve XML model information and do some other things. I'll go through the script and describe it's functionality.

  • In first line we get dir attribute from our XML model's root element and assign it to dir variable. In BeanShell You don't have to declare types of variables so we just write dir = rt.attr("/dir") (though it is still possible to use strict Java syntax, i.e. String dir = rt.attr("/dir")). Usage of attr() method is strightforward, e.g. rt.attr("/foo/bar/baz") will return attribute named baz which belongs to element bar which is child of element foo which is child of root element.
  • Next, we iterate through package elements which are children of root element. We retrieve elements using rt.els("/package") which returns List of org.jdom.Element. Notice for (:) syntax used in script, actually this syntax is borrowed from Java 1.5 (see corresponding JSRs), BeanShell supports such convenience iteration. Of course You can still use old boring iterators like for (Iterator it = rt.els("/package"); it.hasNext();) { pack = it.next(); ... }.
  • Same iteration is going on in nested for loop. But now we get child elements through JDOM API cause pack variable is actually instance of org.jdom.Element.
  • Computing the name of the file which should be generated is easy. It would be <root dir>/<package with dots replaced to slashes>/<class name>.java. Notice another runtime object exposed to script: ju. This object contains several convenient methods used when generating Java sources.
  • Actual creation of generated file is done using rt.merge(template, file, element) call. First argument is name of template used to generate file (creation of template is discussed in next section). Second argument is generated file path relative to project root, all neccessary directories will be created in case they don't exist. Third argument is JDOM element which should be passed to template. Inside template script can retrieve XML elements using both absoulte paths from root and relative to element passed in merge(), see example of this in next section.

(5) Create template file. Our project will have only one template file though it can contain any number of templates (appropriate templates to merge are defined in control script). Create file named bean.quit inside bean folder. Here is it's source:

/*
 * DO NOT EDIT!
 *
 * This file is generated by QuiT Code Generator
 * (C) Copyright Vova Grechka 2004. All Rights Reserved
 */
package <%= rt.attr("../name") %>;

/**
 * @author Vova Grechka
 */
public class <%= rt.attr("name") %> {
<%
	for (p : rt.els("property")) {
		type = p.getAttributeValue("type");
		name = p.getAttributeValue("name");
		capName = ju.capitalize(name);
		getPrefix = (type.equals("boolean") || type.equals("Boolean")
			|| type.equals("java.lang.Boolean")) ? "is" : "get";
		
		%>
			private <%= type %> <%= name %>;
			
			public <%= type %> <%= getPrefix %><%= capName %>() {
				return <%= name %>;
			}
			
			public void set<%= capName %>(<%= type %> <%= name %>) {
				this.<%= name %> = <%= name %>;
			}
			
		<%
	}
%>
}

Template is quite simple but I'll comment it anyway.

  • Syntax is very JSP-like. Supported tags are scripts (<% %>), substitutions (<%= %>) and declarations (<%! %>). All code inside special tags is BeanShell code, so You can use all it's conveniences unlike usual JSP where You have to use strict Java.
  • Runtime objects rt and ju are available in templates in same way as in control scripts.
  • Remember element argument which we passed in rt.merge() call in control script. XML paths can now be derived relative to this element if we omit leading slash, e.g. rt.els("property") works from bean element instead of root element (which would be the case if we used rt.els("/property")).

(6) Finally it's time to make it work all together. Simply choose QuiT->Generate from context menu which appears when right clicking on beans.xml file. That's all. Watch magic appearing of new generated files in Your project:

That was really easy, wasn't it? And consider full power of Java which You can use in Your control scripts and templates :-).