Introduction
The OWL Class Loader (OCL) is a project that was started a few years back. Originally it was used for setting up scripts for dynamically configuring plugins for the RDF Instance Creator (RIC). When development stopped on RIC, the OCL also lost steam. Recent work with PhotoStuff has provided enough momentum to bring the project back. All the code has been re-written from the original version. The new code is based on Jena instead of an old, custom RDF parser. The API is built as an extension to the Jena framework like the Multimedia API. It is my hope that building it on top of Jena will make it easier for others to use in their own projects and it will make the code easier for others to read and extend.Walkthrough
The OWL Class Loader reads a small RDF snippet which tells the loader how to create instances of specific java classes. The RDF gives the details about which class to use, which parameters to pass into the constructor, and methods to execute on the resulting instance. The loader leverages the java.reflect package to do the on-the-fly instantiation of the java objects. It can read the class files from the local classpath, or build its own class path on the fly from remote jar files. Let's walk through some examples.In java, to create a Vector, I'd need some code like:
Vector aVector = new Vector();
This line of java code creates a new instance of the class java.util.Vector and stores it in the variable "aVector". We can achieve the same basic results using the OWL Class Loader. Instead of using the Java language directly, we're using the RDF to tell us how to create a Vector. The OCL knows how to create the equivalent java class based on the information contained in the RDF. Let's take a look at how we reproduce the above line of code:
<cl:Object rdf:ID="aVector">
|
This snippet of RDF will do the same thing the previous line of Java code did. It will create an object of type java.util.Vector, as specified by the hasClass field, and will use the default constructor as shown by the hasConstructor field. This RDF essentially translates into the Java code shown earlier. It's a little more verbose than the single line of java code, but we end up with the same results.
Now lets take a more complicated example. What if I want to pass arguments to my constructor? For example, what if I wanted to create a Hashtable with a certain load factor and initial capacity. The java code would be something like this:
// variables initialized...
int capacity = 20;
float loadFactor = 1.2;
// some code...
Hashtable myHash = new Hashtable(capacity,loadFactor);
So this is a little more complex. I need to specify arguments sent to the constructor, and each argument requires a certain type and value in addition to being in the correct order. Instead of specifying the DefaultConstructor as the constructor like we did in the previous example, we're going to define a new constructor, which takes two parameters which corresponds to one of the constructors for the java.util.Hashtable class:
<cl:Object rdf:ID="myHash">
|
First thing you notice is that this is a more complex piece of OWL. We still specify the type of this object using the hasClass term, but the way we describe the constructor has changed. As I previously mentioned, we are not using the default constructor for this object, so we need to specify the exact parameters (type and value) the constructor takes. So in the Java example, the constructor took two parameters, one int for the initial capacity, and one float for the load factor. If you look carefully at the constructor specification, you'll see that two parameters are outlined, one of type int, and one of type float. And each parameter has the same value as in the Java example. The Parameter tags are very straightforward. Again you'll see the hasClass tag used to indicate the type of the parameter just as it specifies the type of the object being created. And the withValue tag which specifies the value for that parameter. This snippet will yield a Hashtable with initial capacity of 20, and a load factor of 1.2 just like the Java code did. It's as easy at that! It is important to remember when writing the description for a constructor, you get the types and the order of the parameters exactly right. The java reflect package requires the types and order to match exactly in order for it to determine which method you are referring to.
Well, now you might be thinking, "That's a pretty cool system. But I need more fine grain control over my objects. I would like to invoke methods on the objects I create. Right now you've shown that you can only create an object." No problem! You can invoke methods on your objects just like you would in Java. For example, perhaps I'm doing some parsing, and I need to process a list of tokenized objects, the Class Loader can create your list of StringTokenizers for you so you can get down to processing! Let's see how we'd do it in Java first:
// this is our vector for holding the tokenizers
Vector myVector = new Vector();
// lets create a sample tokenizer for our list
// in reality there'd be more than just one...
String toParse = "hello how are you today?";
String delims = "\n ,.:?";
boolean returnDelims = false;
StringTokenizer st = new StringTokenizer(toParse,delims,returnDelims);
myVector.addElement(st);
So three things happened in that code. One, we created a Vector. Two, we created a StringTokenizer. Three, we invoked a method on the Vector passing the StringTokenizer as an argument. So the first two items should not come as any surprise to you at this point, we've already seen how we can create objects, but we can also execute methods on our newly created objects. So let's just briefly look at some RDF which describes just that:
<cl:Object rdf:ID="st">
|
Examples
- Creating an OCL Model
- Instantiating OCL Objects
- Invoking Methods
- Using Method Chains
- Invoking Static Methods
- Invoking Methods on Create
- Creating Objects from Remote Jars
- Using the Registry
Creating an OCL Model
Creating an OCLModel is very simple. OCLFactory provides a method
createModel()
which will return a new instance of OCLModel.
private static OCLModel readModel(String thePath)
|
Instantiating OCL Objects
You can create OCLObjects via two simple methods. The first is via the OCLFactory class. The OCLFactory class provides a set of static methods for creating each of the different types of OCL classes. These methods are also mirrored on OCLModel. You can also create an OCLModel and read in an RDF file which contains some OCL resources. The model will recognize and provide access to these structures when the RDF is read in.
Invoking Methods
There are two ways of executing methods on an object. You can use the methodsToExecute property, which will be discussed later, and the specified methods will be executed on the object when it is created. Alternatively, you can define a Method object and set its onObject property. Then you can get a handle to the OCLMethod and
invoke it.
The RDF below is equivalent to creating a StringTokenizer and invoking the nextToken() method.
<cl:Method rdf:ID="foo">
|
Using Method Chains
Occasionally there will be a need to execute a sequence of methods in order to obtain an end value for passing to a constructor or method as a parameter value. MethodChain provides this functionality. It will use the property methodsToExecute in order to specify the list of methods to invoke in order to obtain the end result. The corresponding class in the API is OCLMethodChain. OCLMethodChain provides a method,
invoke, which will invoke the particular sequence of methods
and return the resulting value. The below example is equivalent to PSKowariServer.getInstance().isRunning().
<cl:MethodChain rdf:ID="aMethodChain">
|
Invoking Static Methods
Some methods on a class have a static scope and can be called without creating an instance of the class. For these methods there is the StaticMethod class which is an rdfs:subClassOf Method. It is for creating and invoking methods which are statically scoped. The corresponding API class is OCLStaticMethod. The following RDF is equivalent to
PSKowariServer.getInstance().
<cl:StaticMethod rdf:ID="foo-static">
|
Invoking Methods on Create
[ for now, see walkthrough ]
Creating Objects from Remote Jars
Until now we've only seen examples where we're creating objects that live in our local classpath. Sometimes the implementation of the class you would like to instantiate will not live on your machine, but will be hosted elsewhere on the Net. For these cases, you will want to specify the location of the remote path (it MUST be a jar file) and the loader will add that to the path and use the contents of that jar file when its creating the objects. The jar tag will tell the loader where to find the implementation for an object. Multiple jars can be specified using multiple jar tags. This is a simple example of how you would specify it in the RDF. There are a set of functions in the API for programatically handling the jar property, see
addJar(...), getJar(), listJars() which
are all methods of the OCLObject class.
<cl:Object rdf:ID="Test">
|
Using the Registry
Each OCLModel has an OCLRegistry associated with it. The purpose of the OCLRegistry is to allow users to pass in values from the program using the OCL. Normally, when you're creating the Java class represented by an OCLObject, all the values for its constructor and other methods used to instantiate it must live in the RDF. The values need to be static, like "10", or must be another OCLObject in the model. However, there are times where you will want to pass in a value from the context of the main program so it can be used by the OCL when creating an object. OCLModel provides a method
register(String, Object) which allows you to register a new object. The first parameter is the unique identifier for that object. This is what you used to refer to the object in the RDF. The second parameter is the value you want to register. OCLModel also provides some methods for seeing if a value is already registered, retrieving a registered value, as well as allowing you to set a new value for the registry.
How it works
The OCLClassLoader works by leveraging the java.reflect package. The java.reflect package provides the functionality for dynamically creating classes at runtime as well as providing ways to invoke methods. However the OCL has its own custom ClassLoader which handles loading resources from remote jar files. It is by combining these two abilities together that we gain the functionality provided by the OCL.Links and Resources
- Class Loader Ontology
- PhotoStuff
- Jena
- Mindswap OWL Class Loader API Javadocs
- Download the source.
- Download the distribution.
- The source is also available from SVN here.
- This project relies on the Mindswap Utilities Library
- Old Class Loader page
