Random musings from my awakening dementia...
06.12.2004  
Loosely Coupled Components, Part 2
 

I'm quite interested in the concept of software components and how those ideas can be applied to Java code. Thoughts or ideas I have on this subject get dropped here for the benefit of humanity and my own hubris.

© 2004-2005, Howard Abrams



Except where otherwise noted, all original content is licensed under a Creative Commons License.
See details.

In Part 1, I discussed the idea of combining components together in a loose fashion by using Interfaces, but before I start talking about more advanced ideas, I thought I would give an example... a plugin architecture.

By way of example, I'm going to describe the inner workings of my Ant task, WebTemplate. When I first designed this beast, I settled on StringTemplate as the template engine, however, that decision would limit the tasks usefulness. Granted, I could have created a number of different tasks for each kind of template engine I might want to use or even a massive if..then statement, but that is pretty inelegant.

So I created a simple "plugin" style architecture where I could add template engines to the task without needing access to the source code of the task. In the Ant build.xml file, you call my task with something like the following:

<webtemplate src="htmldata" dest="html"
   template="toptemplate" engine="StringTemplate"
   includes="**/*.htm"  ext=".html" />

So, I added a keyword called engine that would specify the engine to use, and this parameter is passed to my task as a String. Sure I could have put an if..then statement in my task that went though every possible value, but I don't want to have to edit and recompile my task. I don't even want a "plugin author" to need access to the WebTemplate task code. With the plugin pattern, you can extend my task without having the WebTemplate task know about the extension.

The plugin isn't the template engine, but would actually be a wrapper around the actual template engine code. This plugin would act kinda like glue code to bind the behavior of my task code with the different APIs of the template engines.

Step one then would be to create an interface that each template engine plugin would have to implement. My task could then treat them all similarly:

public interface WebTemplatePlugin 
{
   public void convert(File srcParent, File destFile, 
       String template, Properties attributes) 
     throws BuildException;
}

Each plugin would have its convert method called with the source directory and the destination file as well as the name of the template that was specified and the data values to substitute. The plugin would then do any marshaling of the template engine to create the new file.

In my WebTemplate.java file, I just need to locate the plugin class. The name of the plugin was given to me by the build.xml file, and I just append the word "Plugin" to that text. In the following code, engine is a String containing the text from the build.xml file's parameter:

// We need to get a plugin that matches the engine
// the user specified. The plugin must implement
// the WebTemplatePlugin interface.
//
WebTemplatePlugin plugin;
try {
   if (engine.indexOf('.') == -1)
     engine = "org.howardism.ant." + engine;
   engine = engine + "Plugin";
   //
   // Get the plugin class and instantiate it...
   plugin = (WebTemplatePlugin) 
         Class.forName(engine).newInstance();
} 
catch (Exception e) 
{
   throw new BuildException("Can't instantiate " +
             "plugin: engine="+engine, e);
}

In order to find the class, I need the "full" classname, that is, a class name with a package. Since I really wanted to just say things like "FreeMarker" or "StringTemplate", I made the assumption that if no package is specified, I can just prepend my own.

But the real beauty is the next line. First we get a reference to the class by calling Class.forName(engine) and then with that class object, we call the newInstance() method. This instantiates the object.

Remember how I said every plugin would have to implement the WebTemplatePlugin interface? This means that I can simply cast it to that, and then I can call its convert method directly. Slick, no?

The other nice feature is that when you use my task, you only need access to the template engine library that you actually use. If you want to use FreeMarker, you just need freemarker.jar in the classpath and you don't need any of the jars for StringTemplate.