Framework 101: Building XCF – Lesson 04: Interpreting XML

December 23rd, 2006

It is inevitable that at some point in time, I write again about technology. So I return you to our framework. I began by building a core set of classes. I followed that with a set of classes that will be used to model controller behavior. In the last lesson, I introduced you to a service class that will greatly simplify configuration and object creation. What I have not yet covered, however, is a mechanism to use to create the objects. That is what this lesson is for.

NOTE: You can download the source code for today’s lesson here: Lesson 04 Source Code

Or, you can get the most up to date version of XCF available on sourceforge.

What you will be building today

Today I show you a mechanism that can be used to create objects. I will not, however, actually cover the actual method of object creation. That is for next lesson. The mechanism is simple: use xml to define the high level object composition. For this mechanism to work, you will need a set of classes that you can use to easily interpret xml syntaxes. When this lessons is done, you will create a test class that successfully interprets the following xml:



	
		com.eternal.saxTest
	
	
	
	  
	  
	


Interpreter Pattern

You use the interpreter pattern when there is a language to interpret and you can represent statements in the language as an abstract syntax tree. It is also well suited to situations where the grammar is simple and where efficiency is not a critical concern. It is well suited for XCF’s needs because XCF uses XML (which has a simple grammar) to specify configuration information which is interpreted once to build the system facade.

By using this pattern:

  • It’s easy to change and extend the grammar: You can extend the grammar by simply adding additional interpreter objects to the syntax tree. Because interpreting is based on this dynamic data structure, the grammar has the ability to change as interpreteting is processing.
  • Implementing the grammar is a simple and repeatable process: The implementation rule is very simple. Create a interpreter class for each XML tag in your grammar.

SAX Interpreter Design

XCF’s interpreter engine is an extension of the Simple API for XML (SAX). In the design, there are two basic types of classes: the handler and interpreter objects. The job of the handler is to adapt the SAX API to the interpreter design pattern. The interpreter objects are used to represent the syntax tree of the XML language to interpret.

For each interpreting run, there will only be once instance of SAX_Handler. That class is a final class and contains a reference to a ‘root’ SAX_Element (SAXEL), a stack of interpreter objects, and a generic hash space that SAXELs can put and get values. The SAXELs are the interpreter objects. In general, there will be a subclass of SAXEL for each XML tag needing interpreting.

The job of the handler is very simple; forward the SAX event to the interpreter object that is on the top of the stack. SAXELs do the job of handling the event (which may result in pushing a new SAXEL on the stack or popping itself off the stack).

SAX Interpreter Implementation

SAX_Handler

The SAX_Handler is a final class whose job is to pass the incomming SAX event to the SAXEL on the top of the stack. When creating an interpreter, you will use an instance of this class and a tree of SAXEL objects to interpret the XML. With XCF, the tree of SAXELs is typically built as the XML file is being interpreted.

SAX_Handler contains two primary data structures:

  • the hash: a general purpose tagged dataspace that SAXELs can use to pass information to each other in a decoupled fashion.
  • the stack: contains the current execution branch of the interpreter tree. SAX events are routed to the top of the stack.

SAX_Handler has four type of methods:

  • general accessors: provides access to core objects. The most important and used accessor is the getFacade method.
  • hash helpers: exposes the critical hash access methods.
  • stack helpers: exposes the critical stack methods.
  • SAX event handlers: adapts SAX events to SAXEL methods.

import java.util.Hashtable;
import java.util.Stack;

import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

import com.eternal.xcf.core.XCFFacade;

/**
 * SAX Handler
* Acts as the SAX content and error handler for all XCF XML Interpreters. *

*
    *
  1. Define your XML language as a context free grammar *
     For example, a command language could be defined as: *
      *
    • COMMAND ::= <command>NAME(PARAMETER)*</command>
    • *
    • PARAMTER ::= <parameter>NAME VALUE</parameter>
    • *
    • NAME ::= <name>literal</name>
    • *
    • VALUE ::= <value>literal</value>
    • *
    *
    An example of a stream in this language would be *
    <command> *
      <name>run-simulation</name> *
      <parameter> *
        <name>iterations</name> *
        <value>1000</value> *
      </parameter> *
    </command> *
  2. *
  3. For each expression in the language, create a subclass of SAX_Element *
    Override startInterpreting to start interpreting the expression *
    Override endInterpreting to finish up interpreting of the expression. *
  4. *

* Creation date: (9/6/2001 2:38:23 PM) * @author: Sonjaya Tandon */ public final class SAX_Handler extends DefaultHandler { private Stack interpreterStack; private Hashtable context; private SAX_Element interpreter; private XCFFacade facade; /** * Creates an instance of an interpreter handler. * @param start root interpreter for xml language * @param facade * @see SAX_Element */ public SAX_Handler(SAX_Element start, XCFFacade facade) { super(); context = new Hashtable(); interpreterStack = new Stack(); interpreter = start; this.facade = facade; interpreter.setHandler(this); interpreter.build(); } /////////////////////////////////////////////////////// // Usefull accessors /////////////////////////////////////////////////////// /** * Return the subsystem facade */ public XCFFacade getFacade() { return facade; } /** * Return the root interpreter * Creation date: (9/13/2001 4:28:01 PM) * @return com.dataskill.xcf.message.InterpreterElement root interpreter */ public SAX_Element getInterpreter() { return interpreter; } /////////////////////////////////////////////////////// // Hash helpers /////////////////////////////////////////////////////// /** * Puts an object onto the context. Associates the object with the "key" * Creation date: (9/7/2001 10:09:41 AM) * @param key java.lang.Object * @param value java.lang.Object */ public void put(Object key, Object value) { context.put(key, value); } /** * Returns the data associated with "key". * Creation date: (9/7/2001 10:11:11 AM) * @return java.lang.Object an object associated with the "key" * @param key java.lang.Object a string used to reference an object in the context */ public Object get(Object key) { return context.get(key); } /** * Returns the data associated with "key". Removes that data from the context. * For example, *
if (interpreterTag.equals(EL_NAME)) { *
   String cmdName = (String)handler.consume(EL_NAME); *
   Command cmd = CommandManager.s().lookup(cmdName); *
} * Creation date: (9/20/2001 1:45:20 PM) * @param key java.lang.String used to identify an object on the context. */ public Object consume(String key) { Object val = context.get(key); if (val != null) { context.remove(key); } return val; } /////////////////////////////////////////////////////// // Stack helper methods /////////////////////////////////////////////////////// /** * Pops an interpreter off the stack. * Creation date: (9/6/2001 5:13:27 PM) * @param element com.dataskill.xcf.message.InterpreterElement */ public SAX_Element pop() { return (SAX_Element)interpreterStack.pop(); } /** * Pushes an interpeter element onto the stack. * Creation date: (9/6/2001 5:13:27 PM) * @param element com.dataskill.xcf.message.InterpreterElement */ public void push(SAX_Element element) { interpreterStack.push(element); } /** * Returns the SAXEL that is on the top of the stack. * Creation date: (9/6/2001 5:13:27 PM) * @param element com.dataskill.xcf.message.InterpreterElement */ public SAX_Element tos() { return interpreterStack.peek(); } /////////////////////////////////////////////////////// // SAX Event handlers /////////////////////////////////////////////////////// /** * Called when the parser starts reading the xml stream. * Creation date: (9/6/2001 5:28:35 PM) */ public void startDocument() { push(interpreter); } /** * Called when the parser sees a new tag. This call is passed onto the SAXEL * that is on the top of the stack. * Creation date: (9/6/2001 2:47:38 PM) * @param uri java.lang.String * @param localName java.lang.String * @param rawName java.lang.String * @param attributes org.xml.sax.Attributes */ public void startElement(String uri, String localName, String rawName, Attributes attributes) { localName = rawName; tos().handleStartChildElement(uri, localName, rawName, attributes); } /** * This method is invoked to pass the character content data to * the interpreter element that is on the top of the stack. * * Creation date: (9/6/2001 4:50:28 PM) * @param ch char[] the entire character buffer * @param start int the starting position in the buffer * @param length int the ending position in the buffer. */ public void characters(char[] ch, int start, int length) { tos().handleCharacters(ch, start, length); } /** * Called whenever an end-tag is reached. This event is passed to the * SAXEL at the top of the stack. * Creation date: (9/6/2001 4:47:12 PM) * @param uri java.lang.String * @param localName java.lang.String * @param qName java.lang.String */ public void endElement(String uri, String localName, String qName) { localName = qName; tos().endInterpreting(uri, localName, qName); if (localName.equals(tos().tag)) { pop(); tos().handleEndChildElement(localName); } } /** * Called when the end of the XML stream is reached. * Creation date: (9/6/2001 5:28:45 PM) */ public void endDocument() { pop(); } }

SAX_Element

SAX_Element is the base class for all SAXEL objects, the objects responsible for interpreting XML tags. It is an abstract class, and you typically create a decendent class for each XML tag you need to interpret.

SAX_Element has four type of methods:

  • Creational methods: methods used to create the SAXEL and the interpreter tree.
  • Accessor methods: methods used to access useful fields.
  • SAX handlers: methods used to handle specific SAX events
  • Error handlers: helper methods used to handle error conditions.

package com.eternal.xcf.sax;

import org.xml.sax.Attributes;


/**
 * Base Interpreter class used for SAX parsing.  Create a concrete extension of this class for each
 * XML tag you wish to interpret.  Child classes are reffered to as SAXELs
 * 
 * Each concrete extension MUST implement a null constructor.  That constructor should call 
 *   super([default tag name])
 * 
 * Creation date: (9/6/2001 5:06:03 PM)
 * @author: Sonjaya Tandon
 */
public abstract class SAX_Element {
	protected String tag;
	protected SAX_Handler handler;
	
	///////////////////////////////////////////////////////
	// Creational methods
	///////////////////////////////////////////////////////
	/**
	 * Creates a new SAX element and sets the tag name
	 */
	public SAX_Element(String tag) {
		super();
		this.tag = tag;
	}
	
	/**
	 * Override this method to create any child interpret SAXELs
	 *
	 */
	public void build() {}
	
	///////////////////////////////////////////////////////
	// Usefull accessors
	///////////////////////////////////////////////////////
	/**
	 * Sets the SAXELs handler
	 * Creation date: (9/6/2001 6:15:31 PM)
	 * @param newHandler SAX handler 
	 */
	public void setHandler(SAX_Handler newHandler) {
		handler = newHandler;
		assert handler != null;
	}
	
	/**
	 * Returns the SAXELs handler
	 * Creation date: (9/6/2001 6:15:31 PM)
	 * @return SAX_Handler
	 */
	public SAX_Handler getHandler() {
		return handler;
	}
	
	/**
	 * Gets the tag
	 * @return Returns a String
	 */
	public String getTag() {
		return tag;
	}
	
	/**
	 * Sets the tag
	 * @param tag The tag to set
	 */
	public void setTag(String tag) {
		this.tag = tag;
	}
	
	///////////////////////////////////////////////////////
	// SAX handler methods
	///////////////////////////////////////////////////////
	/**
	 * This method is invoked when the tag associated with this SAXEL is encountered 
	 * by the handler.
	 * @param uri
	 * @param name
	 * @param rawName
	 * @param attributes
	 */
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
	}
	
	/**
	 * This method is invoked by the handler.  It is called to capture the text in the body of an xml tag.
	 * 
	 * Creation date: (9/6/2001 5:24:27 PM)
	 * @param ch char[]
	 * @param offset int
	 * @param length int
	 */
	public void handleCharacters(char[] ch, int offset, int length) {}
	
	/**
	 * Called when the handler sees a new child tag.  Override if the SAXEL has child tags that need interpreting.
	 * When you see a tag that represents a sub-expression, push the appropriate SAXEL on the stack.
	 * 
	 * Creation date: (9/6/2001 5:18:34 PM)
	 * @param uri java.lang.String
	 * @param name java.lang.String
	 * @param rawName java.lang.String
	 * @param attributes org.xml.sax.Attributes
	 */
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {}
	
	/**
	 * Called after a child element is done interpreting.
	 * 
	 * Creation date: (9/19/2001 12:16:34 PM)
	 * @param interpreterTag the tag name of the child interpreter
	 */
	public void handleEndChildElement(String interpreterTag) {}
	
	/**
	 * Called when the end tag for this SAXEL is encountered.  After invoking this method, the handler will
	 * pop the SAXEL from the interpreter stack.
	 * 
	 * Creation date: (9/6/2001 5:18:34 PM)
	 * @param uri java.lang.String
	 * @param name java.lang.String
	 * @param qName java.lang.String
	 */
	public void endInterpreting(String uri, String name, String qName) {}
	
	///////////////////////////////////////////////////////
	// Error handling methods
	///////////////////////////////////////////////////////
	/**
	 * Handle's an error by logging a message
	 * Creation date: (9/13/2001 11:46:44 AM)
	 * @param tag java.lang.String
	 */
	public final void handleError(String msg) {
		handler.getFacade().logError(msg);
	}
	
	/**
	 * Handles the exception by logging to the error and debug logger
	 * Creation date: (9/13/2001 11:46:44 AM)
	 * @param tag java.lang.String
	 */
	public final void handleException(Exception e) {
		handler.getFacade().logError(e.getLocalizedMessage());
		handler.getFacade().logDebug(e);
	}
	
	/**
	 * Log a message indicating that the XML file contains a tag we don't know how to handle
	 * Creation date: (9/13/2001 11:46:44 AM)
	 * @param tag java.lang.String
	 */
	public final void handleUnexptectedTag(String name) {
		handler.getFacade().logError("Unexpected [" + name + "] tag under [" + tag + "] tag");
	}
}

SAXEL_Root

Every tree needs a root and SAXEL_Root fills this role for every interpreter tree. It is a hidden class and you never reference it directly. It is automatically created when you interpret an XML file using these classes. Prior to the interpreter being invoked, the root interpreter’s tag name is set to the root tag of the XML file you are interpreting. When the handler is configured, it is passed the root interpreter.

When interpreting starts, the only SAXEL on its interpreter stack is the root element. Thus, all SAX events initially go to this root interpreter. The only thing this interpreter does is push a SAXEL_Composite onto the interpreter stack (whose behavior is described below). This provides all the bootstrapping needed to allow the interpreting of the rest of the XML.


package com.eternal.xcf.sax;

import org.xml.sax.Attributes;

/**
 * Root interperter for an xml syntax.
 * 
 * Creation date: (11/25/2002)
 * @author: standon
 */
public final class SAXEL_Root extends SAX_Element {
	
	/**
	 * INTR_Root.
	 */
	public SAXEL_Root() {
		super("");
	
	}
	
	/**
	 * This is the first interpreter called by the handler.  Bootstrap the whole process
	 * by pushing the intial interpret (a SAXEL_Composite) onto the interpreter stack
	 */
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {
		if (name.equals(getTag())) {
			getHandler().push(getInitialInterpreter());
		} else {
			handleUnexptectedTag(name);
		}
	}
	
	/**
	 * Create the initial interpreter
	 * @return
	 */
	SAX_Element getInitialInterpreter() {
		SAXEL_Composite server = new SAXEL_Composite(getTag());
		server.setHandler(getHandler());
		return server;
	}

}

Modification to SERVICE_FlyweightFactory

Before I discuss SAXEL_Composite, I need to introduce some changes to SERVICE_FlyweightFactory. The main reason for this class was to allow the XML languages to self configure their interpreter tree. Ok, ok, I know … what the heck does that mean? What that means is that when interpreting, if the interpreter encounters a tag and there isn’t a SAXEL to interpret that tag, SERVICE_FlyweightFactory will be used to locate a SAXEL and that SAXEL will be added to the interpreter tree dynamically. Thus, when interpreting starts, the interpreter tree only contains the SAXEL_Root and a SAXEL_Composite (as a child of that root). But then as interpreting proceeds, SAXELs are added on an as-needed basis.

Of course, for this to work properly, SERVICE_FlyweightFactory needs a convention added for “saxel” elements:


	/**
	 * Start out the service by defining some conventions
	 * and add com.eternal.xcf.common to the search path
	 */
	public void start() throws XCFException {
		addConvention("validator", ".request.parameter.VALIDATOR_");
		addConvention("setter", ".request.parameter.SETTER_");
		addConvention("saxel", ".builder.SAXEL_");
		
		pushScope();
		addPackage("com.eternal.xcf.common");
	}

SAXEL_Composite

You will typically extend SAXEL_Composite when you create your own SAXELs. SAXEL_Composite contains the key logic (which I just described above) that allows you to make your interpreter self configuring. SAXEL_Composite requires SERVICE_FlyweightFactory to be in the system facade.

When extending SAXEL_Composite it is important to understand the order of the methods called. This will assist you in determining what to override and what logic to use:

  • build: this method is invoked right after an instance of this class is added to an interpreter tree. Use it to add additional SAXELs to the interpreter tree. When designing your SAXELs, be careful with this method as you can inadvertently create an infinite recursive loop by adding a SAXEL class that is already an ancestor in the interpreter tree.
  • startInterpreting: this method is invoked when the start tag the SAXEL is responsible for is encountered in the XML file. Most of the time, this is the method you override to provide your interpreter behavior.
  • handleCharacters: it is rare that a composite class needs to override this method as much of the information you need is in the tags. Override this class if you need to capture text between the tags.
  • handleStartChildElement: usually, the implementation in SAXEL_Composite provides all the behavior necessary when a child tag is encountered. It is rare to override this method. Sometimes, however, you need to do prep work prior to the child interpreter executing.
  • handleEndChildElement: it is also rare that you will override this method. Sometimes it is useful to know when a child tag finished interpreting. Override this method in those cases.
  • endInterpreting: this method is invoked when the end tag the SAXEL is responsible for is encountered in the XML file. It is fairly common to override this method as you use it to finalize any interpreting behavior you have been providing.

package com.eternal.xcf.sax;

import java.util.HashMap;
import java.util.StringTokenizer;

import org.xml.sax.Attributes;

import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFException;

/**
 * Acts as the base class for any SAXEL used to interpret composite expressions.
 * Composite expressions are represented by XML tags which will have child tags.
 * 
 * You will typically override build, startInterpreting, and endInterpreting.
 * If you override startChildElement OR endChildElement make sure to call the super method.
 * 
 * Creation date: (9/6/2001 5:29:29 PM)
 * 
 * @author: Sonjaya Tandon
 */
public class SAXEL_Composite extends SAX_Element {
	private static String EL_ADD_TAG = "add-tag";
	private static String EL_INCLUDE_PACKAGES = "include-packages";
	
	private static SAXEL_Literal ADD_TAG = new SAXEL_Literal(EL_ADD_TAG);
	private static SAXEL_Literal INCLUDE_PACKAGES_TAG = new SAXEL_Literal(EL_INCLUDE_PACKAGES);
	
	protected HashMap children = new HashMap();
	protected SERVICE_FlyweightFactory flyweightFactory;
	
	///////////////////////////////////////////////////////
	// Creational methods
	///////////////////////////////////////////////////////
	/**
	 * Default constsructor
	 * @param tag java.lang.String
	 * @param parser com.dataskill.standon.xml.message.XmlMessageParser
	 */
	public SAXEL_Composite(String tag) {
		super(tag);
	}
	
	/**
	 * Hooks onto the setHandler method to grab the flyweightFactory
	 */
	public final void setHandler(SAX_Handler handler) {
		super.setHandler(handler);
		assert handler.getFacade() != null;
		flyweightFactory = (SERVICE_FlyweightFactory)handler.getFacade().getService(SERVICE_FlyweightFactory.XCF_TAG);
		assert flyweightFactory != null;
	}
	
	/**
	 * Associates an xml tag with the SAXEL that will interpret it
	 * @param tag
	 * @param el
	 */
	public final void addTag(String tag, SAX_Element el) {
		el.setTag(tag);
		el.setHandler(getHandler());
		children.put(tag, el);
	    el.build();
	}
	
	///////////////////////////////////////////////////////
	// Accessor methods
	///////////////////////////////////////////////////////
	/**
	 * Returns the children.
	 * @return HashMap
	 */
	public final HashMap getChildren() {
		return children;
	}

	/**
	 * Sets the children.
	 * @param children The children to set
	 */
	public final void setChildren(HashMap children) {
		this.children = children;
	}
	///////////////////////////////////////////////////////
	// SAX handler methods
	///////////////////////////////////////////////////////
	/**
	 * Finds the SAXEL associated with the tag, pushes it onto the interpreter stack and 
	 * calls startInterpreting for that SAXEL
	 * 
	 * There are two reserved tags:
	 *   add-tag: in this case, a literal is pushed onto the stack to grab the class name in the tag body
	 *            that SAXEL class is then assocated to the tag name
	 *   include-packages: in this case a literal is pushed onto the stack to grab the package list
	 *            those packages are then added to the flyweight factory.
	 *            
	 * If there is no SAXEL associated with the tag, attempt to find one using the flyweight factory
	 * If the flyweight factory returns a SAXEL, associate it witht the tag, push it on the stack
	 * and startInterpreting.  If not, nothing more can be done, tag is unexpected.
	 * 
	 * Creation date: (9/6/2001 5:47:32 PM)
	 * @param uri java.lang.String
	 * @param name java.lang.String
	 * @param rawName java.lang.String
	 * @param attributes org.xml.sax.Attributes
	 */
	public void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) {
		// handle the reserved tags first
		if (name.equals(EL_ADD_TAG)) {
			// tag is "add-tag" -- name attribute is the tag we are adding
			// body of the tag is the class name to bind to that tag
			String tagName = attributes.getValue("name");
			
			// push a literal to grab the class name
			handler.put("tag-name", tagName);
			ADD_TAG.setHandler(getHandler());
			handler.push(ADD_TAG);		
			return;
		} else if (name.equals(EL_INCLUDE_PACKAGES)) {
			// tag is include packages, push a literal to grab the package list
			INCLUDE_PACKAGES_TAG.setHandler(getHandler());
			handler.push(INCLUDE_PACKAGES_TAG);		
			return;			
		} else {
			// check to see if we have a SAXEL for this tag
			SAX_Element el = (SAX_Element)children.get(name);
			
			if (el == null) {
				// we don't, use the flyweightFactory to get one
				try {
					el = (SAX_Element)flyweightFactory.getInstance("saxel", name);
					if (el != null) {
						addTag(name, el);
					}
				} catch (XCFException e) {
					handleException(e);
				}
			}
			
			// if we have a SAXEL for the tag, push it and start interpreting
			if (el != null) {
				handler.push(el);
				el.startInterpreting(uri, name, rawName, attributes);
			} else {
				handleUnexptectedTag(name);
			}
		}
	}
	
	/**
	 * Invoked whenever a child tag completes interpreting.
	 * 
	 * Handles the post-processing for the two reserved tags: add-tag and include-packages
	 */
	public void handleEndChildElement(String interpreterTag) {
		
		if (interpreterTag.equals(EL_ADD_TAG)) {
			// grab the tag name and tag SAXEL class name off the handler
			// instantiate the class and bind it to the tag
			String tagName = (String)handler.consume("tag-name");
			String tagClass = (String)handler.consume(EL_ADD_TAG);
			
			try {
				Class intrClass = Class.forName(tagClass);
				SAX_Element el = (SAX_Element)intrClass.newInstance();
				addTag(tagName, el);
			} catch (Exception e) {
				handleException(e);
			}
		} else if (interpreterTag.equals(EL_INCLUDE_PACKAGES)) {
			// grab the include list off the handler and add each 
			// package in the list to the flyweight factory's search path
			String includeList = (String)handler.consume(EL_INCLUDE_PACKAGES);
			StringTokenizer iterator = new StringTokenizer(includeList);
			while (iterator.hasMoreTokens()) {
				String includePackage = iterator.nextToken();
				flyweightFactory.addPackage(includePackage);
			}
		}
	}
}

SAXEL_Literal

Sometimes all you need to do for interpreting is to grab the text in between the tags. SAXEL_Literal is a utility SAXEL that does just that. Any time you simply need to grab the text between tags, simply add this SAXEL to your interpreter tree. For an example of SAXEL_Literal in action, examine the code for SAXEL_Composite and see how the “add-tag” and “include-packages” tags are handled.


package com.eternal.xcf.sax;

import org.xml.sax.Attributes;

/**
 * Utility class used to interpret xml expressions of the form:
 * 
<tag>literal</tag> *
where tag is the value passed in to the contructor. For example, to create * an interpreter element that will parse the following expression: *
<name>literal</name> *
create an instance of SAXEL_Literal as follows: *
new SAXEL_Literal("name"); *
*
SAXEL_Literal's endElement will put a the string value of the literal * on the context using it's tag as the key. *
To get the value, override the interpreterDone of the interpreter element * that pushed the SAXEL_Literal onto the interpreter stack and have it consume the tag * off the context. * Creation date: (9/6/2001 5:29:29 PM) * @author: Sonjaya Tandon */ public final class SAXEL_Literal extends SAX_Element { private StringBuffer contents = new StringBuffer(); /** * SAXEL_Literal default constructor * @param tag java.lang.String * @param parser com.dataskill.standon.xml.message.XmlMessageParser */ public SAXEL_Literal(String tag) { super(tag); } /** * Assumes the characters represent the literal value of the expression. * Creation date: (9/6/2001 5:57:05 PM) * @param ch char[] * @param offset int * @param length int */ public final void handleCharacters(char[] ch, int offset, int length) { if (length != 0) { contents.append(ch, offset, length); } } /** * Puts the literal onto the context. Uses it's tag as the key. * Creation date: (9/6/2001 5:47:32 PM) * @param uri java.lang.String * @param name java.lang.String * @param rawName java.lang.String * @param attributes org.xml.sax.Attributes */ public final void endInterpreting(String uri, String name, String qName) { if (contents != null) { handler.put(tag, contents.toString().trim()); contents.setLength(0); } } /** * Generates an error. Literals aren't supposed to interpret any additional tags. * Creation date: (9/6/2001 5:47:32 PM) * @param uri java.lang.String * @param name java.lang.String * @param rawName java.lang.String * @param attributes org.xml.sax.Attributes */ public final void handleStartChildElement(String uri, String name, String rawName, Attributes attributes) { handleUnexptectedTag(name); } }

UTIL_Helper

UTIL_Helper does the work of assembling and launching the interpreter. You simply pass in the file name (and a reference object that will be used to locate the filename, the name of the root tag, and the system facade. UTIL_Helper assumes that the XML file is packaged in the same jar that the reference object’s class exists.


package com.eternal.xcf.sax;

import java.io.IOException;
import java.io.InputStream;

////////////////////////////////////
// SAX IMPORTS for XML INTERPRETER
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

////////////////////////////////////
// XCF IMPORTS
import com.eternal.xcf.common.service.SERVICE_FlyweightFactory;
import com.eternal.xcf.core.XCFException;
import com.eternal.xcf.core.XCFFacade;


/**
 * A set of static convinience methods for SAX parsing.
 * @author standon
 *
 */
public class UTIL_Helper {
	
	/**
	 * Checks to see that the flyweight factory exists.  If it doesn't, it creates one and adds it to the facade
	 * @param facade
	 * @throws XCFException
	 */
	private static void assertFlyweightFactoryExists(XCFFacade facade) throws XCFException {
		SERVICE_FlyweightFactory flyweightFactory = (SERVICE_FlyweightFactory)facade.getService(SERVICE_FlyweightFactory.XCF_TAG);
		
		if (flyweightFactory == null) {
			flyweightFactory = new SERVICE_FlyweightFactory();
			facade.putService(SERVICE_FlyweightFactory.XCF_TAG, flyweightFactory);
		}
	}
	
	/**
	 * Interprets an XML input source using the handler passed in.
* * @param InputSource xmlInput * @param InterpreterHandler handler */ private static void interpretInputSource(InputSource xmlInput, SAX_Handler handler) throws XCFException { try { assertFlyweightFactoryExists(handler.getFacade()); SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser = factory.newSAXParser(); parser.parse(xmlInput, handler); } catch (SAXException e1) { throw new XCFException(e1, "Error parsing: " + e1.getLocalizedMessage()); } catch (IOException e3) { throw new XCFException(e3, "Problem accessing: " + e3.getLocalizedMessage()); } catch (Exception e4) { throw new XCFException(e4, "Problem accessing: " + e4.getLocalizedMessage()); } } /** * Interpret an XML file * @param refObj the object used to locate the file. * @param fileName the name of the file. It is assumed the path name of the file is relative to the location of refObj * @param server the xcf server * @param rootTag the name of the root tag in the xml file * @throws Exception */ public static final void interpretLocalFile(Object refObj, String fileName, XCFFacade facade, String rootTag) throws XCFException { InputStream stream = null; // make sure we have a flyweight factory assertFlyweightFactoryExists(facade); try { // open a stream to the xml file stream = refObj.getClass().getResourceAsStream(fileName); if (stream == null) throw new XCFException("Unable to locate: " + fileName); InputSource input = new InputSource(stream); // Set up the interpreter SAX_Element root = new SAXEL_Root(); root.setTag(rootTag); SAX_Handler handler = new SAX_Handler(root, facade); // Interpret the xml file UTIL_Helper.interpretInputSource(input, handler); } finally { // clean up if (stream != null) { try { stream.close(); } catch (IOException e) { throw new XCFException("Unable to close stream for " + fileName + ": " + e.getMessage()); } } } } }

Building your own interpreter

At the start of this article, I said you would test out the framework by interpreting the following XML:



	
		com.eternal.saxTest
	
	
	
	  
	  
	


Based on the design, you will need to create two classes. One for each tag that you need to interpret: SAXEL_FirstTag and SAXEL_SecondTag. Create these classes in the package: com.eternal.saxTest.builder. All the SAXELs need to is log a simple message. By adding the package com.eternal.saxTest to the XML, you have told the interpreter all it needs to find the SAXELs.

The implementation, then, for SAXEL_FirstTag is:


package com.eternal.saxTest.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.sax.SAXEL_Composite;

public class SAXEL_FirstTag extends SAXEL_Composite {
	
	public SAXEL_FirstTag() {
		super("first-tag");
	}

	@Override
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		super.startInterpreting(uri, name, rawName, attributes);
		
		getHandler().getFacade().logInfo("In first-tag");
	}
}

And the implementation for SAXEL_SecondTag is:


package com.eternal.saxTest.builder;

import org.xml.sax.Attributes;

import com.eternal.xcf.sax.SAXEL_Composite;

public class SAXEL_SecondTag extends SAXEL_Composite {
	
	public SAXEL_SecondTag() {
		super("second-tag");
	}

	@Override
	public void startInterpreting(String uri, String name, String rawName, Attributes attributes) {
		super.startInterpreting(uri, name, rawName, attributes);
		
		getHandler().getFacade().logInfo("In second-tag");
	}
}

Testing it out

To test all this out, create the following class:


package com.eternal.xcf.sax;

import com.eternal.xcf.core.XCFFacade;

import junit.framework.TestCase;

public class TestCoreSax extends TestCase {
	
	public void testSimpleFile() throws Exception {
		XCFFacade facade = new XCFFacade();
		UTIL_Helper.interpretLocalFile(this, "simple.xml", facade, "facade");
	}
}

Also create an XML file called: simple.xml. Put this file in the same directory of the .class of the class you just created. The contents of the XML is the example XML from the last section.

Now, step through the execution of the interpreter you just built. When interpreting starts, the first tag encountered is “facade”. At this point in time, the interpreter stack contains SAXEL_Root. The handler will invoke “startChildElement” for that class. This will result in an instance of SAXEL_Composite being pushed onto the stack.

The next tag encountered is “include-packages”. The handler will use the top of stack object (an instance of SAXEL_Composite) to handle the tag. SAXEL_Composite will handle this by pushing a SAXEL_Literal onto the stack.

The interpreter then encounters the text between the “include-packages” tag. This text is sent to the top of stack, which is SAXEL_Literal.

The interpreter then encounters the close tag for “include-packages”. This results in SAXEL_Literal being popped from the stack and a handleEndChildElement being called on the new top of stack object, which is an instance of SAXEL_Composite. That class, in turn, will grab the literals text and use it to add packages into the search space for the flyweight factory.

The next tag encounted is the start tag for “first-tag”. This will be passed on to the SAXEL_Composite. The SAXEL_Composite will use the flyweight factory to create and instance of SAXEL_FirstTag and push that instance on the stack. It will then invoke the startInterpreter method on that instance which will result in a log message being output to the console.

The next tag encountered is the start tag for “second-tag”. This will be passed on to the SAXEL_First tag instance sitting on the top of the stack. Because SAXEL_FirstTag is a subclass of SAXEL_Composite, it uses the same logic to get an instance of SAXEL_SecondTag on the stack. SAXEL_SecondTag‘s startInterpreter method is invoked which results in a log message to the console.

Next, the interpreter encounters the close tag for “second-tag” which results in SAXEL_SecondTag being popped from the stack.

Next, the interpreter encounters the close tag for “second-tag” which results in SAXEL_FirstTag being popped from the stack.

Next, the interpreter encounters the close tag for “second-tag” which results in SAXEL_Composite being popped from the stack.

Conclusion

Congratulations! You have now created a very powerful and flexible mechanism that supports configuration through convention. In the next lesson I will finally show you how to use all the objects I have introduced to build a system facade.

Entry Filed under: Software Development,XCF

Trackbacks

4 Comments Add your own

Leave a Comment

You must be logged in to post a comment.

Trackback this post  |  Subscribe to the comments via RSS Feed


Calendar

December 2006
M T W T F S S
« Nov   Jan »
 123
45678910
11121314151617
18192021222324
25262728293031

Most Recent Posts