Version: 0.1, Revision: $Revision: 1.9 $
$Date: 2002/05/07 06:16:11 $
Table of Contents
CGimlet aids you when generating arbitrary code with java. The codegeneration process is similar to that of a compiler: some input language is read, processed and an output in a different language is produced. So we distinguish two steps:
Collecting all information necessary to generate the code. This is done by reading some files written in the so called source language. CGimlet suggests and provides special support for XML as its source language but is not restricted to it.
Generating the code. Code is generated for a target language. Any object-oriented, imperative language may be used as target language. CGimlet provides an abstraction of this step, so the same java code may be used to generate code for different languages.
CGimlet supports you managing these two steps and it also provides some glue that holds things together; this means, you can easily write a codegeneration application without worrying about how to invoke the codegeneration, how to write the generated code to a file or how to compile the produced files.
Abstract
This section gives a short overview in what ways CGimlet helps you to collect information from XML.
CGimlet uses dom4j for parsing and accessing XML files. You should be familiar with the API of dom4j before using CGimlet.
CGimlet lets you easily define and use variables in your XML files. The variables are called properties. The syntax of defining and using these properties is similar to the syntax found in Ant (http://jakarta.apache.org/ant). For further information and examples on how to use properties have a look at the API documentation of the class org.cantaloop.cgimlet.PropertyParser.
Information is often stored in key-value-pairs. Such key-value-pairs are called options. CGimlet provides support for reading and accessing such options. Reading is done by the org.cantaloop.cgimlet.OptionReader which can be configured with an instance of org.cantaloop.cgimlet.OptionReaderConfigurator. The class org.cantaloop.cgimlet.Options lets you access the options. It also has a mechanism for providing default values for options that have not been set in the XML file. See the API documentation for further information and examples.
The class org.cantaloop.cgimlet.XMLUtils provides various helper methods for common task such as parsing of xml files.
Abstract
This section gives a short overview in what ways CGimlet helps you to generate to code for the target language.
CGimlet provides an abstraction of the target language. Using this abstraction layer has two advantages:
The generated code always is syntactically correct. You don't have to write raw statements and expression, instead you use methods of various template classes to write the code.
The same piece of java code may be used to generate code for different target languages.
Example 1. Using CGimlet's abstract template library
Suppose you want to write a method called initOptions that takes a map and puts some key-value pairs into this map. The generated java code should look like this:
public void initOptions(java.util.Map map) { map.put("foo", getFoo()); map.put("bar", getBar()); }
Without the abstraction layer, the code that generates the method would look like this:
code = "public void initOptions(java.util.Map map) {"; code += "map.put(\"foo\", getFoo());"; code += "map.put(\"bar\", getBar());"; code += "};
Using the abstraction layer seems to become more complex but in fact it is less erroneous because syntax errors are avoided:
LanguageFactory lang = JLanguageFactory.getInstance(); CGUtils utils = lang.getUtils(); MethodTemplate method = lang.getMethodTemplate("initOptions"); method.addParameter(lang.getMapType(), "map"); method.appendToBody(utils.mapStrPut("foo", utils.invokeExpr("getFoo"))); method.appendToBody(utils.mapStrPut("bar", utils.invokeExpr("getBar")));
By replacing the class JLanguageFactory in the first line with a class that implements the interface org.cantaloop.cgimlet.lang.LanguageFactory for a different programming language, a semantically equivalent method would be generated for this programming language.
Currently, only an implementation for the java programming language is available. If you write a implementation for another language we would be glad to include it in CGimlet. If you have questions about writing such an implementation you should contact the developers mailing list.
Using the abstraction layer is easy: All you need is an instance of a class that implements org.cantaloop.cgimlet.lang.LanguageFactory for the programming language you want to use. This instance gives you everything needed for writing code in that language:
A class implementing org.cantaloop.cgimlet.lang.CGUtils. This interface defines a lot of methods very useful for generating code. Be sure to read its API documentation.
Various templates: ClassTemplate , MethodTemplate , FieldTemplate and ConstructorTemplate . These templates allow you to write classes at a high abstraction level.
Predefined abstract types hide the concrete implementation of these types in different programming languages. See Type .
An implementation of org.cantaloop.cgimlet.lang.CodeWriter. Writing the code to a file is done here. You only need to set the base directory the generated code should be placed in. If you want, the code gets even pretty printed.
A interface to a compiler or interpreter for the programming language the code is generated for. This gives you to possibility to check the generated code for syntactically correctness. See CodeCompiler .
CGimlet helps you to run the codegeneration process and to manage the codegeneration for a larger project. It provides a commandline entry point and an ant task. The class that contains the main-method is org.cantaloop.cgimlet.Main. An example of using CGimlet with Ant can be found in the file templates/ant-taskdef.xml. CGimlet also has a built-in logging mechanism (using log4j) and provides support for storing and retrieving application-wide configuration options.
If you want to use CGimlet for running and managing your codegeneration, you have to provide a project file. This file is usually called project.xml. An example for a project file can be found in templates/project.xml. A project file contains
global settings (like the base directory for the whole project)
definitions of handlers. A handler is a class that implements org.cantaloop.cgimlet.ComponentHandler. It is instantiated and initialized by the CGimlet framework and then called to generate the code.
configuration sections for the handlers
content sections for the handlers
Every handler has to be defined in the
<prj:handlerdef>section of the project file (the prefix `prj' refers to the namespace http://www.cantaloop.org/xmlns/cgimlet/project). This is done with the
<prj:handler>element. This element has two attributes:
The attributes of the <prj:handler> element
the name of the handler. It must match the name of the element the handler should be applied to.
the fully qualified classname of the handler. The class must implement the interface org.cantaloop.cgimlet.ComponentHandler.
CGimlet instantiates the handler and calls its init method.
The
<prj:configuration>element might contain a configuration section for every defined handler. The root element of the configuration section for a handler must have the same name as given in the handler definition. CGimlet does not care about the content of this element, instead it passes it to the configure method of the handler. The configuration section should be used the specify options like input and output directories or package names.
When all handlers have been configured, CGimlet reads the content of the
<prj:content>element. This element contains a section for every handler that should be called to generate code. Again, these sections must have the same name as given in the handler definition. CGimlet passes the root element of every section to the generate method of the appropriate handler.
As an example, suppose we want to write a generator for java beans. A java bean contains various properties, get/set methods for every property and a no-argument constructor. Additionally, the generated bean should have constructors that take the value(s) of one or more properties and initialize the properties accordingly. Such a java bean may look like this: example/sample-output.java
You can find the files used in this example in the subdirectory example. To run the example, just invoke the appropriate shell script (build.sh or Build.bat). The shell script starts Ant (the buildfile is as usually called build.xml) from which CGimlet is run.
This is the XML file from which the information to generate the bean should be taken: example/beans.xml
We first define a property called `pkg'. Its value is the name of the package the generated bean(s) should be placed in. A bean is generated for every `java-bean' element. The attribute `name' of this element specifies the classname of the generated bean, the attribute `package' its package. Note the usage of the previously defined property `pkg' in this attribute.
The content of the `java-bean' element should be rather self-explanatory. It's more interesting to look at the project file example/project.xml.
First you should notice the declaration of the two distinct namespaces: The project namespace (with the prefix `prj') is used to mark those elements that are read by CGimlet. The beangen namespace (with the prefix `bg') is used by our bean-generator.
The attribute `basedir' of the root element is very important; the location of every file is resolved against the directory specified by this attribute. The value of basedir itself is resolved against the location of the project file. If no value is given for `basedir', the directory of the project file is taken.
The next thing we do is to define two properties which specify the input and output directories. These properties are used in the `configuration' and `content' section of the project file. The `handlerdef' section associates a handler with a element in the configuration and content section. Every handler must be set up with an appropriate `handler' element. The `name' attribute specifies the name of the element in the configuration and content section and the `class' attribute must contain the fully qualified name of the class implementing the org.cantaloop.cgimlet.ComponentHandler interface.
It is important to include the directories and jar files that contain the handler classes in the classpath when running the codegeneration, because CGimlet loads these classes dynamically.
It's not difficult to understand how CGimlet invokes the handlers. First, it reads the children of `prj:configuration'. For every sub-element, the configure method of the handler whose name matches the local-name of this element is called, passing the element as a parameter to this method. After all sub-elements of `prj:configuration' have been worked through, CGimlet applies the same procedure to the `prj:content' section. Instead of configure, CGimlet now calls the generate method with the appropriate element as parameter.
You should now have a look at the sourcecode of org.cantaloop.cgimlet.handler.BeanHandler to see how the different part works together.
Enjoy CGimlet!
We would be glad to have some feedback from the users of CGimlet.
http://www.cantaloop.org