VisitorsInSwapi

Each Node is closely coupled with a dispatcher, which is used to provide Visitor Objects to make life easier when workding with the Abstract Syntax API

General Structure

The [Wiki]Visitor Pattern is used throughout the Abstract Syntax API to efficiently select different behavior based on the Node you access. Each subclass of Node has a visitor - called its Dispatcher - that's specifically tuned to that particular instance. They are called dispatchers since they are not true visitors, only applying to one particular type of node in the hierarchy. The actual visitor interface, NodeVisitor, aggregrates the interface of each Dispatcher to provide a true visitor interface.

           Node.Dispatcher <--+
                              |
       Resource.Dispatcher <--+
                              |
      BlankNode.Dispatcher <--+
                              |
         URIRef.Dispatcher <--+-- NodeVisitor <-- NodeVisitor.Default
                              |
        Literal.Dispatcher <--+
                              |
   PlainLiteral.Dispatcher <--+
                              |
   TypedLiteral.Dispatcher <--+

NodeVisitor also provides a [Wiki]Default Visitor called NodeVisitor.Default. This default implementation calls the dispatcher method for each type of Node up the hierarchy up to the default dispatcher (visiting java.lang.Object), which returns null.

Using Visitors to Work With Data

You can used visitors with SWAPI abstract syntax objects to selectively handle different data structures. For example, the dc:creator of a resource can be a literal string or a blank node. This is explained in more detail by Ian Davis in an article called [WWW] Crisis. Here's an example of two statements (in [WWW] Turtle):

# Note these prefixes for clarity:
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix sept: <http://internetalchemy.org/2005/09/> .

# Statements:
sept:crisis dc:creator "Ian Davis" .
sept:crisis dc:creator [ foaf:name "Ian Davis" ] .

If we had a graph that contained those statements, then we could query it for the creator of the article:

   1 Connection conn = connectToMyDataSource();
   2 SyntaxFactory fact = conn.getSyntaxFactory();
   3 Graph graph = conn.getGraph( "http://example.com/Graph/Describing/Ian/Davis/Articles" );
   4 URIRef crisis = fact.createURIRef( "http://internetalchemy.org/2005/09/crisis" );
   5 URIRef creator = fact.createURIRef( "http://purl.org/dc/elements/1.1/creator" );
   6 Iterator<Statement> stmts = graph.getStatements( crisis, creator, null );
   7 
   8 while( stmts.hasNext() ) {
   9   Node target = stmts.next().getTarget();
  10   // Do something with target
  11 }

Using Flexible Data Structures

That's easy enough. But how do we differentiate between the literal "Ian Davis" and the blank node []? Simple: Use a visitor!

   1 // ...
   2 URIRef name = fact.createURIRef( "http://xmlns.com/foaf/0.1/name" );
   3 //...
   4 
   5 while( stmts.hasNext() ) {
   6   String crisisCreatorName = stmts.next() .getTarget() .visitWith(
   7     new NodeVisitor.Default<String>() {
   8       String visitLiteral(Literal node) {
   9         // Ideally you want to do validation here, but this is just an example.
  10         return node.toString();
  11       }
  12       String visitResource(Resource node) {
  13         // Ideally you want to do validation here, but this is just an example.
  14         return graph.getStatements( node, name, null ) .next() .getTarget() .toString();
  15       }
  16     }
  17   );
  18   // Do something with crisisCreatorName
  19 }

Note that in the above version you can have a named URIRef instead of a regular Literal. These style of programming allows you to work with data in more flexible ways.

Validating Input

In addition to selective behavior, you can validate data. This is using the visitor for the opposite reason: instead of adapting to new structures, you want to strictly adhear to a specific structure. There are lots of cases where one or the other approach work, so neither is inheritly better than the other one.

Back to validation. Say we MUST only allow plain literals as the target of dc:creator in our example. That means that there should not blank nodes or URI References at the end of the arcs. Here's how you throw an exception for those exceptional cases:

   1 while( stmts.hasNext() ) {
   2   Node target = stmts.next().getTarget();
   3   try {
   4     String crisisCreatorName = target.visitWith(
   5       new NodeVisitor.Default<String>() {
   6         String visitPlainLiteral(PlainLiteral node) {
   7           // Ideally you want to do validation here, but this is just an example.
   8           return node.toString();
   9         }
  10         String visitNode(Object node) {
  11           throw new MyException("my message");
  12         }
  13       }
  14     );
  15    // Do something with crisisCreatorName
  16   } catch( MyException ee ) {
  17     // Handle my exception
  18   }
  19 }

Future Directions

You can also use visitors write to data streams and convert RDF data into your applications internal data structure. See the source code to the implementations of the Jena and OpenRDF drivers for examples of the latter.

In the future (before 1.0 though), SWAPI will probably have some convience methods for helping with the above too. Like a Graph.getStatement method for retrieving just the first match of a selection. Another addition may be visitors for the nodes in a statement. Things are still being standarized, so make your voice heard for your feature requests!

last edited 2007-08-18 21:03:11 by AntonyBibian