15.6. Anatomy of SimpleSdt: the "wrapper class"

The SDT XML declaration provides an abstract definition of the structure of an SDT, but does not provide any implementation-specific details. The object wrapper class (whose name must be provided in the SDT definition as the value of the "wrapper" attribute) provides the concrete definition of an SDT entry instance. Example 15.3, “The org.hackystat.doc.simplesdt.SimpleSdt "wrapper" class” is a shortened version of the wrapper class definition for the SimpleSdt SDT, with import statements, JavaDocs, and certain methods omitted.

Example 15.3. The org.hackystat.doc.simplesdt.SimpleSdt "wrapper" class

package org.hackystat.doc.simplesdt;

public class SimpleSdt extends SensorDataEntry {

  public SimpleSdt (Map keyValStringMap, SensorDataType sdt) throws SensorDataException {
    super(keyValStringMap, sdt);
  }

  public String getFileName() {
    return this.getAttributeValue("fileName");
  }

  public int getElapsedTime() {
    return ((Integer)(this.getConvertedAttributeValue("elapsedTime"))).intValue();
  }
  
  public String getErrors() {
    String errorMessage = "";
    if (getFileName() == null) { errorMessage += "Invalid fileName; "; }
    if (getElapsedTime() <= 0) { errorMessage += "Invalid elapsed time; "; }
    return (errorMessage == "") ? null : errorMessage;
  }
  
  public static Integer getInteger(String intString) {
    return new Integer(intString);
  }

  public static String getDefaultElapsedTime() {
    return "100";
  }
  
  public List getFilePaths() {
    List filePaths = new ArrayList();
    filePaths.add(getFileName());
    return filePaths;
  }
}  

15.6.1. Why is it called a "wrapper" class, and why is it required?

Internally, the Hackystat system implements a "generic" Java class called "SensorDataEntry", which provides the core data structures and methods associated with all sensor data regardless of its SDT name. Hackystat needs this low-level abstraction because there are many kinds of operations (such as storage, retrieval, and transmission via Soap) that must be done on all sensor data regardless of its higher level SDT classification. One could, of course, simply require that developers manipulate these underlying SensorDataEntry instances directly, but that would lose the benefits of abstraction. It would be like saying that there exists an abstraction called "String", but as a developer you actually have to deal with an array of characters. No developer in their right mind would ever willingly submit to that kind of bogosity!

What we need is a way to connect the XML declaration of sensor data types to the actual Java class system, so that when you declare an SDT called "SimpleSdt", the instances of that sensor data are provided to you as instances of the SimpleSdt class. This is the basic purpose of the "wrapper" class---it forms an object-oriented wrapper around the SensorDataEntry instance that provides a higher-level abstraction of the sensor data that makes the data easier to manipulate by the analysis functions in the system.

All wrapper classes must extend org.hackystat.core.kernel.sensordata.SensorDataEntry, which has a couple of benefits. First, it means that each sensor data instance effectively has two identities: one being as an instance of SensorDataEntry, which is good for low-level Hackystat functions; the other being an instance of the wrapper class (such as SimpleSdt), which is the way that higher-level analysis functions in the system will want to manipulate the sensor data.

15.6.2. The constructor

When you write a wrapper class, you must provide a constructor in the following format:

  public SimpleSdt (Map keyValStringMap, SensorDataType sdt) throws SensorDataException {
    super(keyValStringMap, sdt);
  }

The constructor must accept a Map and a SensorDataType, and then invoke its superclass' constructor, passing these arguments along. The reason for this constructor is so that internal Hackystat functions can take sensor data, package it up, and use this constructor to generate an appropriately initialized instance of your wrapper class.

15.6.3. Accessor functions

The wrapper class should always provide accessor methods that enable clients to easily retrieve the required (and perhaps certain "commonly available" optional properties). SimpleSdt defines two entryattributes, "fileName" and "elapsedTime", and so the wrapper class defines two accessor functions:

  public String getFileName() {
    return this.getAttributeValue("fileName");
  }

  public int getElapsedTime() {
    return ((Integer)(this.getConvertedAttributeValue("elapsedTime"))).intValue();
  }

These accessor functions illustrate two important methods in the SensorDataEntry API that are used in wrapper classes. The first is called "getAttributeValue", and an illustration of its use occurs in the first accessor, getFileName(). The getAttributeValue method takes the name of an entry attribute, and returns its value in this sensor data instance as a String. Since fileNames are represented only as Strings, this is the way wrapper writers retrieve that representation.

The second accessor function, getElapsedTime, is more interesting in a couple of ways. First, you can see that it does not return the String representation of this entryattribute, nor does it return an Integer, which is the "typed" version of this attribute. It actually returns an int, which is the most useful representation of that entryattribute for manipulation by client code. This is a good example of how the wrapper class can provide an interface to sensor data that makes it much easier to manipulate by higher level analyses. Second, the way in which it produces the int representation is by calling the getConvertedAttributeValue method. This method takes the name of the entryattribute, and returns its value after applying the converter method to it. (If no converter method exists, then the String is returned.) This is a rather inefficient implementation since it creates an intermediate Integer instance each time the accessor method is invoked. If this impacts on performance, you could of course cache the int in the instance the first time it is created to avoid subsequent Integer instance creation.

15.6.4. The getErrors() method

Every wrapper class should implement a method called getErrors(), which is called once when the instance of this class is instantiated, and which should return null if the entryattribute values in the instance is valid and a string indicating the problem if the entryattribute values are not. For SimpleSdt, this methods looks like this:

  public String getErrors() {
    String errorMessage = "";
    if (getFileName() == null) { errorMessage += "Invalid fileName; "; }
    if (getElapsedTime() <= 0) { errorMessage += "Invalid elapsed time; "; }
    return (errorMessage == "") ? null : errorMessage;
  }

In the case of this SDT, we want to make sure that the fileName is not null, and that the elapsedTime is greater than 0. If either or both of these conditions are not met, then the String that is returned provides diagnostic information, and the system will throw an exception. Note that this error checking occurs on both the client side (when sensor data is being prepared for transmission to the server) and on the server side (when data is received, and when data is retrieved from disk).

15.6.5. Converter methods

Each entryattribute in an SDT can optionally specify a "converter" method. If a converter method is specified, then the entryattribute must also supply a "type" class. These two attributes together enable the SDT implementation mechanism to automatically convert the String representation of the entryattribute into a Java class. The converter method must be a public static method that accepts a single String argument and returns an instance of the Java class specified by the "type" attribute.

In the case of the SimpleSdt SDT, the elapsedTime entryattribute specified the type "java.lang.Integer" and a converter method called "org.hackystat.doc.simplesdt.SimpleSdt.getInteger". This method is illustrated below:

  public static Integer getInteger(String intString) {
    return new Integer(intString);
  }

As you can see, it accepts a String and returns an Integer. (It will throw a runtime exception if the passed "intString" is not a String that can be converted to an Integer. In most situations, this kind of exception indicates a bug in the Sensor that is generating the String, and if the Sensor is correctly written will result in some kind of notification of the problem on the client side. Of course, one could also decide to catch the Exception and return a default integer if that would be appropriate.)

15.6.6. Defaulter methods

Each entryattribute in an SDT can optionally specify a "defaulter" method. The defaulter method provides a default value to be used for that entryattribute in the case that the sensor (or the already stored sensor data) does not contain a value for that entryattribute. The defaulter method must be a public abstract method of no arguments that returns a String.

The SimpleSdt SDT contains a defaulter method for the elapsedTime attribute, as shown below:

  public static String getDefaultElapsedTime() {
    return "100";
  }

The value returned by the defaulter method must be a String. If a converter method is also specified, then this value is used as its argument to create the typed instance in the case where no value is explicitly provided by the sensor or stored data.

15.6.7. The getFilePaths() method

The "Workspace" abstraction is central to many Hackystat applications. Workspaces provide a platform and file system independent way of identifying the "location" where work is occurring. Workspaces are derived from the physical file paths present in sensor data. Because workspaces are so prevalent in Hackystat applications, the sensor data type definition machinery provides a special method called getFilePaths() to support the process of extracting physical file paths from the sensor data.

Because physical file paths could occur in arbitrary numbers of fields in sensor data, the SensorDataEntry class defines an abstract method called getFilePaths() which must be implemented in the wrapper method to return a list of all file paths contained in this instance of sensor data. If your sensor data does not contain any file paths, then you can define this method to return an empty list. (Do not return null, however!) Sensor data without file path information is quite rare, however, since without a physical file path it is difficult to associate the sensor data with a Project.

In the case of SimpleSdt, the "fileName" entryattribute should contain a physical file path and thus these file paths should be made available to the Workspace definition machinery. To make this possible, the getFilePaths method is implemented in this wrapper class as follows:

  public List getFilePaths() {
    List filePaths = new ArrayList();
    filePaths.add(getFileName());
    return filePaths;
  }

As you can see, it simply returns a list containing the fileName string.