Last updated on:
Overwriting base class methods
boolean qualify(org.w3c.dom.Node node) throws SimObjectException;
void parseNode(org.w3c.dom.Node node) throws SimObjectException;
Apex WorldBuilder (the WorldBuilder) application was originally designed to identify and process HTML hotspots and image maps in a set of HTML storyboard files into initialize.lisp file understandable by Apex. One of the primary architectural concerns proposed for the WorldBuilder application was extensibility. To that end, the WorldBuilder has been designed to make it easy to add support for new types of Apex simulation objects (simobjects). This document will describe the process of adding support for translating new HTML tags into simobjects.
To be able to extend this application a developer needs to be familiar with the following technologies:
The basic task of extending Apex is to derive a new class from the existing base class and to overwrite its methods. The first part involves deriving a new class. It utilizes the OO principle of inheritance. This principle guarantees that the derived class will have access to all functionality of the base class and to its member variables. At the same time it allows the author of the new class to add, modify or suppress parts of the base class functionality. The second part of the task involves overwriting the methods of the base class. This utilizes the OO principle of polymorphism. The methods of the base class form a contract with the rest of the WorldBulider application. By overwriting the methods of the base class, the derived class can interact with the WorldBuilder according to the contract established by the base class.
The name of the base class is wb.simobjects.SimObject. It resides in SimObject.java file in wb\simobjects subfolder of the WorldBuilder project. The prefix wb stands for WorldBuilder and denotes the encompassing Java package. Notice that the name of the class matches the name of the object contained inside. This rule should be followed for any new classes (e.g. SimObjectFoo should reside in wb\simobjects\SimObjectFoo.java). New SimObject-derived classes should also reside in wb\simobjects subfolder and be declared as a part of wb.simobjects package. It is recommended that the name of the new class starts with SimObject.
To reiterate all of these requirements, consider a hypothetical new class wb.SimObjectFoo. To satisfy all of the above requirements wb.simobjects.SimObjectFoo should be implemented in SimObjectFoo.java file which resides in wb\simobjects subfolder of WorldBuilder project. From this point on, the object will simply be referred to as SimObjectFoo for brevity. The file containing SimObjectFoo code should start with the following code snippet:
// place this Java object
inside wb.simobjects package
package wb.simobjects;
/**
* Description: The class to parse and
create Apex simobject
* foo.
* Copyright: <Your copyright goes
here>
* Company: <Your company name goes
here>
* @author <Your name goes here>
* @version <Version information>
*/
// more imports may be
required
import wb.Qualifier;
import
wb.exceptions.SimObjectException;
import
java.util.StringTokenizer;
import
java.text.MessageFormat;
public class SimObjectFoo
extends SimObject {
...
}
There are five methods that SimObjectFoo should implement. These methods will be called by the internals of the WorldBuilder application.
This method will be called whenever the WorldBuilder needs to create a new object. This goal of the constructor is to initialize internal member variables. As implementation caveat below describes, SimObjectFoo will be created and destroyed several times in the course of processing HTML storyboards. Therefore it is recommended that no heavy processing is done in the constructor. A better place for heavy processing is in parseNode() and toString(). The following is an example of a constructor:
public SimObjectButton() {
m_x
= m_y = m_cx = m_cy = -1;
}
This method is called by SimObjectFactory to build the table of qualifiers. A qualifier is a record that establishes association between a tag along with its attributes and a class that processes this tag. register() method returns the Qualifier record for its class (SimObjectFoo).
The Qualifier record in the table consists of the following entries: type, HTML tag, property (or attribute) name, property (or attribute) value and handler class name. In HTML terminology, properties are referred to as attributes. This document refers to HTML tag attributes as properties. Property name and property value together form a property pair.
The possible types are: Qualifier.UNIQUE, Qualifier.PAIR, and Qualifier.FUNCTION.
When the type is Qualifier.UNIQUE, the class becomes solely responsible for processing all occurrences of the designated HTML tag encountered in all HTML storyboards. In this case, property pair values are ignored by the WorldBuilder and should be set to null. The following is an example of such record:
public Qualifier register() {
return new Qualifier(Qualifier.UNIQUE,
"area", null, null,
getClass().getName());
}
This method indicates that the implementing class (SimObjectFoo) will be solely/uniquely responsible for "area" HTML tags in all HTML storyboards. Notice that the two parameters for the property pair are null. getClass().getName() provides an automatic way to return "wb.simobjects.SimObjectFoo". Caveat: When classes register themselves in Qualifier's table of SimObjectFactory, they form a community where one class does not know what the other ones are processing. In this situation if one class registers itself as UNIQUE handler for a certain tag, it may preclude other SimObjects from getting a chance to participate in processing HTML storyboards. (Notice the difference in notation between SimObject and simobject. The former represents a SimObject-derived class, while the later defines Apex simulation object. These concepts while similar are not the same.) Since classes are registered in alphabetical order, in case of a collision, the first class in alphabetical order will prevail. Use UNIQUE qualifiers with caution and check all other SimObjects for collisions before you decide to do that. Currently, SimObjectButton implemented by the Borg team uses UNIQUE qualifier for gareah tag. If new classes are added that parse gareah tag, SimObjectButton.register() will need to be modified to differentiate itself from these new classes. One way to differentiate SimObjectButton is via PAIR qualifier, with gshapeh and grecth as property pair name and value respectively.
When the type is Qualifier.PAIR, the property pair is evaluated for the specified HTML tag and only tags that match the property pair criterion will be passed to the class for processing. It is obvious that this is less restrictive than Qualifier.UNIQUE and allows SimObject authors to become more precise about which types of tags they want to process. The following is an example of such record:
public
Qualifier register() {
return new Qualifier(Qualifier.PAIR,
"area", "shape", "rect",
getClass().getName());
}
This method indicates that the implementing class will only process "area" HTML tags in the form of <area ... shape="rect" ...>. This creates an opportunity for another class to become responsible for <area ... shape="circle" ...> tags. The idea behind this type of qualifiers was to add precision to the types of tags each SimObject can process. It also lowers the chance for collision characteristic for UNIQUE qualifiers. From the pragmatic standpoint, it can also simplify processing each SimObject has to do. For example, shape="rect" implies that there will be four coordinates as in <area ... shape="rect" coords="100,100,200,200">; whereas shape="circle" implies that there will be only three coordinates as in <area ... shape="circle" coords="100,100,200">. If "area" tags where handled through UNIQUE qualifier, the author of that SimObject would have to build in intelligence to distinguish between the two shape types. PAIR qualifier automates and thus makes such a task easier.
Qualifier.FUNCTION provides the ultimate degree of flexibility for deciding which tags to process. When this type of Qualifier is used, the WorldBuilder will call qualify() method in SimObjectFoo to ask the class whether it wants to process the tag or not. qualify() function will be described in greater detail later in this document. When FUNCTION qualifier is used, the author will be able to determine whether to process the tag based on an arbitrary number of property pairs or even query the properties of other tags in the HTML storyboard. The following is an example of FUNCTION qualifier:
public
Qualifier register() {
return new Qualifier(Qualifier.FUNCTION, "area", null, null,
getClass().getName());
}
Notice that the tag is still required for FUNCTION qualifier but both entries for property pair are set to null as they are not used.
This function works in conjunction with Qualifier.FUNCTION. It will only be invoked by SimObjectFactory if register() returned FUNCTION qualifier. You do NOT need to implement this function or feature it in your class in any way if your qualifier is not FUNCTION.
Since a Document Object Model (DOM) node is passed into the function, the author will be able to assess an arbitrary number of property pairs or even navigate to and check other tags in HTML storyboard. The tutorial on how to access arbitrary property pairs and perform navigation between nodes (a.k.a. the programming model for DOM) is beyond the scope of this document. The Borg team tried to simplify coding for most common scenarios as described in this best practices note. If your needs go beyond capabilities of these APIs refer to SimObjectFactory.java for examples or check http://msdn.microsoft.com/workshop/author/dom/domoverview.asp or http://java.sun.com/j2se/1.4/docs/api/org/w3c/dom/Node.html or http://www.w3.org/TR/2000/WD-DOM-Level-1-20000929/ for background and references. There are also a number of books published that explain programming DOM in details. For example Dynamic HTML: The Definitive Reference by Danny Goodman, O'Reilly, ISBN 1-56592-494-0.
Suppose that some HTML storyboard has two types of circle hotspots: small and large, where large hotspots have radius greater than 100 pixels. Furthermore, suppose that each type represents a different simobject and thus has its own LISP syntax. Per WorldBuilder architecture the most intuitive approach for processing these simobjects would be to have two SimObject-derived classes. The following is an example of qualify() function for large hotspots. As such it only accepts area tags shaped as a circle with a radius greater than 100 pixels. This type of qualification would be impossible to do with PAIR qualifier because two pairs are being assessed for their states. Additionally, this code snippet demonstrates the power of the APIs described in the best practices note.
public
boolean qualify(org.w3c.dom.Node node) throws SimObjectException {
String sValue;
// validate input parameter, bail out if
it's bad
if (null == node)
return false;
// first, check the value of the
"shape" attribute,
// bail out if it's not
"circle". use getAttributeString
// helper API to show how
easy this is.
sValue = getAttributeString(node,
gshapeh);
if (null == sValue || !sValue.equalsIgnoreCase("circle"))
return false;
// second, check the value
of the "coords" attribute,
// strip out the value for
radius.
// this is slightly harder
but provides greater
// flexibility.
NamedNodeMap nnmAttrs;
// get the list of all attributes node
has, bail out if there are none
nnmAttrs = node.getAttributes();
if (0 >= nnmAttrs.getLength())
return false;
sValue = nnmAttrs.getNamedItem("coords").getNodeValue();
// end of getting an attribute
// parse attribute value to
get the radius
StringTokenizer st;
st = new StringTokenizer(sValue,
",");
st.nextToken(); // skip x coordinate
st.nextToken(); // skip y coordinate
// return true if the radius is greater
than 100, false otherwise
return (100 < Integer.valueOf(st.nextToken()).intValue());
}
If an error occurs that deserves customerfs attention, you can throw the exception that will be reported to the user. Throwing an exception will terminate processing of HTML storyboards and customer will be given a choice to retry the processing or to exit WorldBuilder.
This API is the workhorse of every SimObject-derived class. Its purpose is to assess the state of tag property pairs and potentially even other tags and to initialize internal member variables so that LISP code can be produced. This API can initialize several member variables inherited from the base class (SimObject). These member variables are: m_sType, m_sLocale, and m_sName. m_sType holds the Apex type of the simobject. Currently, the base class constructor initializes m_sType to ginterface-objecth. m_sLocale holds the locale that Apex associates with the simobject. The base class constructor initializes m_sLocale to gworldh. Since these two values are hardcoded, and set in the default base class constructor, the derived class (SimObjectFoo) normally does not need to initialize them. m_sName holds the name by which Apex identifies the simobject. In contrast to the two previous member variables, m_sName is user supplied, not hardcoded. It is a good idea to initialize m_sName based on some attribute value for the tag (e.g. gtitleh) that simobject is responsible for.
m_sName is particularly important because it will be used by WorldBuilder to establish uniqueness of the object in the global object repository. The global object repository is used by WorldBuilder to produce the final LISP code understandable by Apex. Generally, it is a good idea to look at the implementation of SimObjectButton.parseNode() that resides in wb\imobjects\SimObjectButton.java for reference. Since SimObjectButton.parseNode() is implemented by the Borg team it reflects the latest thinking and trends in the WorldBuilder codebase evolution. The following is a sample implementation of parseNode():
public
void parseNode(org.w3c.dom.Node node) throws SimObjectException {
StringTokenizer st;
String sValue;
int x1, x2, y1, y2;
// initialize m_sName with
the value of the attribute gtitleg
sValue =
getAttributeString(node, "title");
if (null == sValue || 0
>= sValue.length())
throw new
SimObjectException(ERR_NO_TITLE);
m_sName = sValue;
// andrewgu: initialize
arguments for all error
// messages that can follow
Object[] rgoErrorArgs = {
m_sName };
// get "coords" attribute for
the tag
sValue =
getAttributeString(node, "coords");
if (null == sValue || 0
>= sValue.length())
throw new
SimObjectException(MessageFormat.format(ERR_NO_COORDS,
rgoErrorArgs));
// parse retrieved coordinates into 4
integers corresponding to the
// dimensions. the implicit assumption is that the area
tag has a
// rectangular form (this is
specified by gshape=recth attribute pair
// inside gareah tag)
st = new
StringTokenizer(sValue, ",");
x1 =
Integer.valueOf(st.nextToken()).intValue();
y1 =
Integer.valueOf(st.nextToken()).intValue();
x2 =
Integer.valueOf(st.nextToken()).intValue();
y2 =
Integer.valueOf(st.nextToken()).intValue();
// initialize m_x, m_y, m_cx, m_cy
member variables
// notice that m_x and m_y
are set to the centroid of the rectangle
// per Apex specification.
m_cx = x2 - x1;
m_cy = y2 - y1;
m_x = x1 + m_cx/2;
m_y = y1 + m_cy/2;
// bail out if there is an
error
if (0 > m_x || 0 > m_y
|| 0 > m_cx || 0 > m_cy)
throw new
SimObjectException(MessageFormat.format(ERR_BAD_COORDS,
rgoErrorArgs));
}
This method works in a pair with parseNode(). parseNode() accumulates information into classfs internal member variables. toString() is responsible for outputting LISP code base on these internal member variables. Result produced by this method will be included into initialize.lisp. Ideally, the output of this method should be formatted to make it human-readable. The following is a sample implementation of this method:
public
String toString() {
String sFormat =
"\t ({1}
(make-instance ''{2} :name ''{1} :locale {3}{0}" +
"\t\t:pos
''({4,number,0} {5,number,0}) " +
":dimensions
''({6,number,0} {7,number,0}))){0}";
Object rgoArgs[] = {
ms_sEOL,
// 0
m_sName,
// 1
m_sType,
// 2
m_sLocale,
// 3
new
Integer(m_x), // 4
new
Integer(m_y), // 5
new
Integer(m_cx), // 6
new
Integer(m_cy) // 7
}
return
MessageFormat.format(sFormat, rgoArgs);
}
The resultant LISP code may look something like this:
(MicrosoftWordItem (make-instance
'interface-object :name 'MicrosoftWordItem :locale world
:pos '(163 256)
:dimensions '(86 12)))
Notice that this API relies on a number of member variables: ms_sEOL, m_sName, m_sType, m_sLocale, m_x, m_y, m_cx, and m_cy. These variables with the exception of ms_sEOL were initialized in a sample implementation of parseNode() above.
ms_sEOL is a
static variable initialized only once by a the base class (SimObject). This variable hold a platform-agnostic
representation of a new line character(s).
By convention, it is always the first element of the arguments
array. By using ms_sEOL you will
ensure that initialize.lisp has
correct formatting with regard to line
Real SimObject-derived classes should follow the same concept of using local and inherited member variables. Impelementation Caveats section also provides more general information about the use of MessageFormat class.
a) The name of the archive has to be WorldBuilder.jar. If you choose to change this name you should update WorldBuilder.FILE_THISJAR constant to reflect the new name. Per naming specification outlined in the beginning of this document, the constant is located in wb\WorldBuilder.java file.
b)
The name
of the files storing the version number and potentially other customizable strings
is resources.txt. This name
is also hardcoded WorldBuilder.FILE_RESOURCES
constant to reflect the new name. This
constant is located in the same file as a).

The following
run-time view of the WorldBuilder application demonstrates component/connectors
and their temporal interactions. It
should be noted that this diagram is somewhat of an abstraction that omits some
mundate detail but still nicely captures the run-time aspect of the
system. The goal of this diagram is
to enhance general understanding of WorldBuilder by developers who are
extending it with more SimObject-derived classes.
The following is a
brief description of temporal actions:
(1)
(2)
(3)
(4)
HTMLOverview loads and parses HTMLOverview file. To accomplish this task, the object
relies on JTidy library and on DOM interfaces the library returns.
(5)
After
DOM representation is created for HTMLOverview file, HTMLOverview object parses
the file and creates agent and interface SimObjects.
(6)
Previously
created SimObjects are placed into the repository.
(7)
The
first link in HTMLOverview file serves as an entry point into
HTMLStoryboard processing. HTMLStoryboard
object is created and control passes to it.
(8)
HTMLOverview loads and parses HTMLOverview file. To accomplish this task, the object
relies on JTidy library and on DOM interfaces the library returns.
(9)
HTMLStoryboard get the collection of tags from the SimObjectFactory qualifiers
table. HTMLStoryboard parses
HTMLStoryboard file and gives control to the factory to process the
previously returned HTML tags of interest.
(10) SimObjectFactory creates SimObject-derived objects and gives
control to these objects to parse HTML tag information. This is when qualify() method might be called and parseNode() method will be called for your SimObject-derived
class.
(11) Previously created SimObjects are
placed into the repository.
(12)
(13) LispFile scans SimObjects repository and
generates a
(14) LispFile creates and saves initialize.lisp.
The processing of a
set of HTML storyboards ends.
|
Project Initiator: |
Professor |
|
Technical Advisor: |
Mike Freed, |
MSE Studio Team:
|
Mike Lewis |
Project Lead |
|
Andrew Guletsky |
Development Lead |
|
J. |
Planning Lead |
|
Chiharu Kawatake |
Quality/Process Lead |
|
Cmdr. |
Studio mentor |
|
Dr. Jim Tomayko |
Studio mentor |
Note: The version number is the version of this
file, not the application.
|
REVISION 1.0 |
Date: |
Updated By: Andrew Guletsky |
|
Reason: Created
the file and incorporated feedback from the Borg team members. |
||
|
|
||
|
REVISION 1.1 |
Date: |
Updated By: Andrew Guletsky |
|
Reason: Incorporated
more feedback. Added Best
practices section. Revised code
samples. Misc. other minor
content improvements. |
||
|
|
||
|
REVISION 1.2 |
Date: |
Updated By: Andrew Guletsky |
|
Reason: Incorporated
feedback from Irene Tollinger. Updated
the content to match version 1.1.6.
Added several new Best Practices (3-5) and Implementation Caveats
(5-9). Added How it works diagram and explanation. Made the document more Word-like after
bad round-tripping from HTML. |
||
|
|
||