java -jar lib/qcf.jar
http://www.yorkieworks.com:8180/qcf/viewerThis will display a page with the list of available processes. Click on one of the hyperlinks to execute the process.
In order for this approach to work, it is
important to separate the calculation logic from the code which
handles the gui display. In the case of the framework, the
calculation logic is stored in the qcf.modules and qcf.examples
packages; the calculation controller in the package,
edu.gatech.math.qcf. The Swing gui code is in the package,
edu.gatech.math.qcf.gui and the servlet code is in the
package, edu.gatech.math.qcf.servlet.
The calculation controller knows that all calculation modules implement
this interface; therefore, it can execute them individually and chain
them together without any knowledge of the inner workings of the
modules themselves. Further, new modules can be created and just
plugged into the framework by specifying their class name through an
an XML configuration file. The "How-To" section later in this document
shows you how to build your own module and add it to the framework.
Since XML has no pre-defined tags, you define the tags that you need
to fit the problem that you are trying to solve. A set of tags can
be constrained through your own Document Type Definition (DTD) and
then applied to a set of XML documents. XML Schema is a newer and better
technique for editing XML; however, not all tools currently support
Schema. Through XSL, you can define
style sheets to format your XML content in various ways.
There are a few rules that you must follow when building XML documents
in order to make them well-formed:
In addition to the flexibility of being able to define your own tags
and editing criteria, XML takes a tree-structured approach to the
representation of data. Thus, it fits best when the data that you
are trying to represent is also tree structured.
Lets consider the XML used to control the Financial Framework as a concrete
example. Here is a skeleton of the actual control file (viewer.xml):
Java Interface Facilitates a Plug-in Architecture
Through a Java interface, you define a set of methods and these
methods specify the standard behavior of any object which implements
the interface. For example, the Java interface Runnable defines
a single method run. Any class which implements Runnable
can be executed in a separate thread. In the case of the financial framework,
all calculation modules must implement the Module interface. This
interface also has a single method, perform. This method
has the following method signature:
DataTable [] perform(Properties props, DataTable [] data)
throws ModuleException;
This signature indicates that the method must accept a Java
properties object and also an array of DataTable objects.
Further, it must return an array of DataTable objects. If
it encounters any serious problemm, then it should throw a
ModuleException.
Java Properties for Configuration Data
Java Properties represent a "key-value" mapping which is a convenient
tool for storing configuration information and simple data. The Java
Properties class is an extension of the Java Hashtable class
with methods tuned to read files and work with Strings rather than objects.
Here is a the properties file used to control one of the modules in the
framework:
#
# Properties for FileTableReader module
#
filePrefix=/usr/local/qcf/data/
fileSuffix=.csv
The value on the left of the equals is the "key" and the value on the
right of the equals is the "value". So, in the above example,
the key "fileSuffix" has the value ".csv". You can create a Java
Properties object by loading data from a file or by storing entries
manually. See the JDK JavaDoc available from Sun for further details.
XML for Data and Process Control
XML is similar to HTML in that it is a tag language. However,
unlike HTML, XML has no predefined tags. XML is an acronym for
"Extensible Mark-up Language"; it is standard of the W3C (World-Wide Web
Consortium). XML originated from the work of Jon Bosak of Sun Microsystems
working with the W3C.
<?xml version="1.0"?>
<?xml version="1.0"?>
<!DOCTYPE Viewer SYSTEM "viewer.dtd">
<Viewer>
<!-- This element defines the menu of processes which the user can execute -->
<Menu>
<MenuItem label="Average List" description="Average numbers in a list"
process = "average1" />
<MenuItem label="Average Two" description="Averages Two Numbers"
process="average2" />
...
</Menu>
<!-- This section defines a series of display layouts for use by processes -->
<DisplayLayout id="1" columns="free">
<Input id="data1" control="JTextField" label="First: "
description="Enter the first field" type="numeric" width="10"/>
<Input id="data2" control="JTextField" label="Second: "
description="Enter the second field" type="numeric" width="10"/>
</DisplayLayout>
<DisplayLayout id="2" columns="free">
<Input id="tdata" control="JTable" rows="4">
<Column label="" editable = "false">
<Row value="Value #1" />
<Row value="Value #2" />
<Row value="Value #3" />
<Row value="Value #4" />
</Column>
<Column label="Number" type="numeric" editable="true" />
</Input>
</DisplayLayout>
...
<Process id="average1" description = "Average of A List">
<Input type="gui" layout="2" />
<Module class="qcf.examples.AverageTable" />
</Process>
<Process id="average2" description="Average Two Numbers" >
<Input type="gui" layout="1" />
<Module class="qcf.examples.AverageTwo" />
</Process>
...
</Viewer>
|
Here are some things to note about this file:
In order to read an XML file, you need an XML parser. Various XML parsers are available and most of these support the two different approaches to XML parsing:
In addition to the parser, there are various add-ons which make XML parsing more Java friendly. For JDK 1.4 and later, XML parsing will become a core part of the API. For JDK 1.3 and earlier, the following jar files provide you what you need to get started with XML:
package qcf.examples;
import edu.gatech.math.qcf.*;
import java.util.*;
/**
* This class illustrates how to write a simple calculation module
* that just computes the average of two numbers which are provided
* by a Java properties file.
*/
public class AverageTwo implements Module {
/** First number to average */
private double data1;
/** Second number to average */
private double data2;
/** Default label */
private String label = "Average";
/**
* This method performs the calculation which involves
* computing the average of the two numbers provided
* through the properties object. It returns the input data
* as the first output DataTable and the result as the
* second output DataTable.
*
* @param props Properties with data and optional header.
* @param table Data table (not used).
*
* @return Input data plus calculated result.
*/
public synchronized DataTable [] perform(Properties props,
DataTable [] table)
throws ModuleException {
// Get the data from the properties.
parseProperties(props);
// Store the input in a DataTable using this class name as the ID.
// The DataTable has two rows and one column. This is not required
// but might be useful if you want to chain modules together.
String cid = getClass().getName();
DataTable input = new DataTable(cid+":input", 2, 1);
input.setColumnName("Input", 0);
input.setValueAt(data1, 0, 0);
input.setValueAt(data2, 1, 0);
// Compute the average.
double avg = (data1+data2)/2;
// Store the result in the output table.
DataTable results = new DataTable(cid+":output", 1, 1);
results.setColumnName(label, 0);
results.setValueAt(avg, 0, 0);
// Return both the input and the output.
DataTable [] all = {input, results};
return all;
}
/**
* This method extracts the numbers to average and the
* optional output label.
*
* @param props Properties file.
*/
private void parseProperties(Properties props) throws ModuleException {
String tmp;
// Get and edit the first number.
tmp = props.getProperty("data1");
if (tmp == null)
throw new ModuleException("Missing first number.");
try {
data1 = Double.parseDouble(tmp);
}
catch(Exception e) {
throw new ModuleException("Invalid first number: "+e.getMessage());
}
// Get and edit the second number.
tmp = props.getProperty("data2");
if (tmp == null)
throw new ModuleException("Missing second number.");
try {
data2 = Double.parseDouble(tmp);
}
catch(Exception e) {
throw new ModuleException("Invalid second number: "+e.getMessage());
}
// Get the optional header.
tmp = props.getProperty("header");
if (tmp != null)
label = tmp;
}
}
|
Next the input data is packaged into the DataTable object. This part is optional; however, it may be useful to pass along the input data if modules are being chained together.
The actual work is done through this one line of code:
double avg = (data1+data2)/2;Then this result must be stored in a results DataTable object, which is what the remainder of the code does.
DataTable dataTable = new DataTable("Puff", 2, 1);
Indexing in Java is zero-based. To add an element to row 1 and column 0:
dataTable.setValueAt(myValue, 1, 0);"myValue" may be a double or long primitive or any object.
To retrieve the same element:
Object obj = dataTable.getValueAt(1, 0);If your table contains double primitives, then the result of this call will be a Double object. In this case, it would be more convenient to use the following:
double d = dataTable.getValueAsDouble(1, 0);The DataTable class is similar to the AbstractTableModel class provided by the Java Swing package. See the JavaDoc for all of the methods available for DataTable.
<MenuItem label="Average Two" description="Averages Two Numbers"
process="average2" />
<Process id="average2" description="Average Two Numbers" > <Input type="gui" layout="1" /> <Module class="qcf.examples.AverageTwo" /> </Process>
<DisplayLayout id="1" columns="free">
<Input id="data1" control="JTextField" label="First: "
description="Enter the first field" type="numeric" width="10"/>
<Input id="data2" control="JTextField" label="Second: "
description="Enter the second field" type="numeric" width="10"/>
</DisplayLayout>
In order to test your change through the servlet, you may need to recycle
the servlet container. Sample servlet screen shots are also included
in the next section.
Swing Client: Screenshot #1
Swing Client: Screenshot #2
Swing Client: Screenshot #3
Servlet: Screenshot #1
Servlet: Screenshot #2
Servlet: Screenshot #3