mulgara - semantic store

skip navigation

SHOW SITE NAV
fixed
fluid
straight

Statements

Once the content handler and parser are written, the last step is to create the Statements implementation that navigates the results of the parser's work. Statements objects hold and navigate the triples generated by the parser and are an integral part of any resolver. They are the only component of the content handling side of resolvers that are passed back to the Resolver SPI to have constraints resolved against.

With the Statements implementation you need to decide how it navigates the triples generated by the parsing classes. For the MP3 statements a JRDF Graph is available that has features to allow for the retrieval of an iterator that can be used to navigate the parsed content of the file. The MP3Statements object also handles the parsing of the file content into triples.

 
Implementing the Interface

After deciding on the best method of navigating the generated statements, it is relatively straightforward to implement the interface. The following code is the MP3 implementation using a JRDF Graph (see MP3Statements.java):

package org.mulgara.content.mp3;

// Java 2 standard packages
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.*;
import org.xml.sax.*;

// Third party packages
import org.jrdf.graph.*;
import org.jrdf.graph.mem.*;
import org.apache.log4j.Logger; // Apache Log4J
import org.jrdf.graph.*; // JRDF
import org.jrdf.util.ClosableIterator; // JRDF
import org.jrdf.graph.*; // JRDF
import org.farng.mp3.MP3File;
import org.farng.mp3.TagException;

// Locally written packages
import org.mulgara.query.Constraint;
import org.mulgara.query.QueryException;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.query.rdf.*;
import org.mulgara.resolver.spi.LocalizeException;
import org.mulgara.resolver.spi.Statements;
import org.mulgara.resolver.spi.ResolverSession;
import org.mulgara.store.StoreException;
import org.mulgara.store.tuples.AbstractTuples;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.content.Content;
import org.mulgara.content.mp3.parser.*;
import org.mulgara.content.mp3.parser.exception.*;
import org.mulgara.content.mp3.parser.api.*;

public class MP3Statements extends AbstractTuples implements Statements {

/** Logger. */
private static Logger logger =
Logger.getLogger(MP3Statements.class.getName());

/** Column index for subjects */
public static final int SUBJECT = 0;

/** Column index for predicates */
public static final int PREDICATE = 1;

/** Column index for predicates */
public static final int OBJECT = 2;

/** The session used to globalize the RDF nodes from the stream. */
private ResolverSession resolverSession;

/** The queue of triples generated by the ID3 parser. */
private ArrayList triples;

/** The number of statements in the ID3 tag. */
private long rowCount;

/** The current row. If the cursor is not on a row, this will be null */
private Triple tripleStatement;

/** An interator into triples positioned at the next triple. */
private ClosableIterator nextTriple;

/** The content representing the MP3 file */
private Content content;

/** The model which will store the content of parsed mp3 files */
private Graph model;

/**
* Map ARP anonymous node IDs to {@link BlankNode}s.
*
* This is null if no parsing is in progress.
*/
private Map blankNodeMap = null;

//
// Constructors
//

/**
* Construct an RDF/XML stream parser.
*
* @param content the content object representing our MP3 file
* @param resolverSession session against which to localize RDF nodes
* @throws IllegalArgumentException if inputStream or
* resolverSession are null
* @throws TuplesException if the inputStream can't be parsed as
* RDF/XML
*/
MP3Statements(Content content, ResolverSession resolverSession) throws
TuplesException {

// Validate "content" parameter
if (content == null) {
throw new IllegalArgumentException("Null \"content\" parameter");
}

// Validate "resolverSession" parameter
if (resolverSession == null) {
throw new IllegalArgumentException("Null \"resolverSession\" parameter");
}

// Initialize fields
this.content = content;
this.resolverSession = resolverSession;
this.triples = new ArrayList();

// Fix the magical column names for RDF statements
setVariables(new Variable[] {new Variable("subject"),
new Variable("predicate"),
new Variable("object")});

try {

// Initialise the parser factory
ParserFactory.getInstance().initialiseFactory();
} catch (FactoryException factoryException) {

throw new TuplesException("Unable to initialise factory for parsers.",
factoryException);
}

// Load in the RDF conversion of the given mp3 content
loadURL();
}

/**
* Load in the RDF conversion from the content object.
*
* @throws TuplesException
*/
private void loadURL() throws TuplesException {

// Discard any existing statements
triples.clear();

try {

// Initialise the model to be a memory based graph
model = new GraphImpl();
} catch (GraphException graphException) {

throw new TuplesException("Unable to create a new graph object.",
graphException);
}

// Create a container for our file
File contentFile = null;

if (!content.getURI().getScheme().equals("file")) {

// If we are dealing with anything other than a file then use the
// caching process

try {

// Convert the URI into a file
contentFile = getCachedFile(content.newInputStream(), content.getURI());
} catch (IOException ioException) {

throw new TuplesException(
"Unable to open a stream to the content file [" +
content.getURI().toString() + "]", ioException);
}
} else {

// Files are local and do not need caching
contentFile = new File(content.getURI());
}

// Parse the content of the file/directory to the model
parseFile(contentFile);

// Parse the stream into RDF statements
blankNodeMap = new HashMap();

try {

// Initialize the metadata now that we know the statements
rowCount = model.getNumberOfTriples();
} catch (GraphException graphException) {

throw new TuplesException(
"Unable to retrieve number of triples in graph.",
graphException);
}

if (logger.isDebugEnabled()) {

logger.debug("Parsed MP3: Found " + rowCount + " triples");
}
}

//
// Methods implementing Statements
//

/**
* Retrieves the value contained in the subject column for the current triple.
*
* @return The subject value for the current triple
*
* @throws TuplesException
*/
public long getSubject() throws TuplesException {

return getColumnValue(SUBJECT);
}

/**
* Retrieves the value contained in the predicate column for the current triple.
*
* @return The predicate value for the current triple
*
* @throws TuplesException
*/
public long getPredicate() throws TuplesException {

return getColumnValue(PREDICATE);
}

/**
* Retrieves the value contained in the object column for the current triple.
*
* @return The object value for the current triple
*
* @throws TuplesException
*/
public long getObject() throws TuplesException {

return getColumnValue(OBJECT);
}

//
// Methods implementing AbstractTuples
//

/**
* Resets the counter for triples to be the first.
*
* @param prefix The prefix to use
* @param suffixTruncation The truncation of suffixes to use
*
* @throws TuplesException
*/
public void beforeFirst(long[] prefix, int suffixTruncation) throws
TuplesException {

try {

// Get the iterator for statements in the model
nextTriple = model.find(null, null, null);
} catch (GraphException graphException) {

throw new TuplesException("Unable to retrieve triple iterator for graph.",
graphException);
}

if (logger.isDebugEnabled()) {

try {

logger.debug("-- Getting the before first value from model " + model +
" which has statements " + nextTriple.hasNext() + " from " +
model.getNumberOfTriples() + " triples");
} catch (GraphException graphException) {

// Since we are debugging, it is not important if this exception is
// ignored
}
}
}

public Object clone() {

MP3Statements cloned = (MP3Statements)super.clone();

// Copy immutable fields by reference
cloned.resolverSession = resolverSession;
cloned.rowCount = rowCount;
cloned.tripleStatement = tripleStatement;
cloned.content = content;

// Copy mutable fields by value
cloned.triples = (ArrayList) triples.clone();

return cloned;
}

/**
* Close the RDF/XML formatted input stream.
*/
public void close() throws TuplesException {

resolverSession = null;
tripleStatement = null;
triples = null;
content = null;
}

/**
* @param column 0 for the subject, 1 for the predicate, 2 for the object
*/
public long getColumnValue(int column) throws TuplesException {

// Pull the appropriate field from the current triple as a JRDF Node
Node node = null;

switch (column) {
case SUBJECT:

// Try creating the node with a URI reference
node = tripleStatement.getSubject();

break;
case PREDICATE:

// Try to create a URI reference node to represent the predicate
node = tripleStatement.getPredicate();

break;
case OBJECT:

// Create a literal node with the value for objects
node = tripleStatement.getObject();

break;
default:

throw new TuplesException("No such column " + column);
}
assert node != null;

// Localize the node
try {

return resolverSession.localize(node);
} catch (LocalizeException e) {

throw new TuplesException("Couldn't get column " + column + " value", e);
}
}

public List getOperands() {

return Collections.EMPTY_LIST;
}

public long getRowCount() throws TuplesException {

return rowCount;
}

public long getRowUpperBound() throws TuplesException {

return getRowCount();
}

public boolean hasNoDuplicates() throws TuplesException {

return false;
}

public boolean isColumnEverUnbound(int column) throws TuplesException {

switch (column) {

case 0:

case 1:

case 2:

return false;
default:

throw new TuplesException("No such column " + column);
}
}

public boolean next() throws TuplesException {

if (nextTriple.hasNext()) {

// Get the next statement in the iterator
tripleStatement = (Triple) nextTriple.next();

if (logger.isDebugEnabled()) {

logger.debug("-- Getting next statement: " + tripleStatement.toString());
}

return true;
} else {

tripleStatement = null;

return false;
}
}

/**
* Checks whether the given file is a file or directory and then acts
* accordingly. It should not be confused with the parseFile method which
* does the actual conversion from an ID3 tag to RDF. This method is
* recursive so subdirectories will be navigated.
*
* @param file The file or directory we are checking the content of
*
* @throws TuplesException
*/
private void parseFile(File file) throws TuplesException {

if (file.getName().endsWith(".mp3")) {

// If the file is a valid mp3 file then parse the content into the model

// Container for our mp3 file
MP3File mp3File = null;

try {

// Create a new MP3 file to represent our content
mp3File = new MP3File(file);
} catch (IOException ioException) {

throw new TuplesException("Unable to create mp3 file object for path: " +
file.getAbsolutePath(), ioException);
} catch (TagException tagException) {

throw new TuplesException("Unable to read ID3 tags for file: " +
file.getAbsolutePath(), tagException);
}

// Create a container for our file id
String fileId = "";

// Try to use the file uri as the id
fileId = file.toURI().toString();

// Create a new conversion object
MP3Conversion conversion = new MP3Conversion(mp3File, model, fileId);

// Container for our parser object
ID3Parser parser = null;

try {

// Get a parser instance
parser = ParserFactory.getInstance().createID3Parser();
} catch (FactoryException factoryException) {

throw new TuplesException(
"Unable to create a new ID3Parser due to a factory error.",
factoryException);
}

try {

// Parse the mp3 into the model
parser.parseTags(conversion);
} catch (ParserException parserException) {

throw new TuplesException("Unable to parse tags for file: " +
content.getURI().toString(), parserException);
}
} else {

throw new TuplesException("Content object did not contain a valid mime " +
"type for parsing.");
}
}

/**
* Creates a locally cached version of a file from an input stream. If the
* file already exists then it will not download the file but instead use the
* cached version.
*
* @param inputStream The stream of data we are caching
* @param uri The uri of the data we are caching
*
* @return The file handle to the cached file
*
* @throws TuplesException
*/
private File getCachedFile(InputStream inputStream, URI uri) throws
TuplesException {

// Retrieve the path to the file on the remote host
String remotePath = uri.getPath();

// Retrieve the actual name of the file
String fileName = remotePath.substring(remotePath.lastIndexOf("/") + 1,
remotePath.length());

if (logger.isDebugEnabled()) {

logger.debug("Transferring [" + uri + "] to cached file [" + fileName + "]");
}

// Create a temporary cache directory handle
File cache = new File(System.getProperty("java.io.tmpdir") + File.separator +
"resolvercache");

if (!cache.exists()) {

// Check that the directory exists and if not then create it
cache.mkdirs();
}

if (!cache.isDirectory()) {

// If we can't use the directory name because a file has it, then just use
// the temporary directory
cache = new File(System.getProperty("java.io.tmpdir"));
}

// Create a new file representing the cached file
File file = new File(cache, fileName);

if (!file.exists()) {

// If the file does not exists in the cache already then download the file

// Container for our OutputStream to the file
OutputStream outStream = null;

try {

// Attempt to create an output stream to the output file
outStream = new FileOutputStream(file);
} catch (FileNotFoundException fileNotFoundException) {

throw new TuplesException("Unable to locate output file for caching " +
"of local version of: " + uri.toString(),
fileNotFoundException);
}

// Create an inputStream to read from
InputStreamReader reader = new InputStreamReader(inputStream);

// Container for the bytes in our stream
int nextByte = 0;

try {

// Get the first byte of the stream
nextByte = reader.read();

while (nextByte != -1) {

// Write out the current byte
outStream.write(nextByte);

// Read the next byte of the file
nextByte = reader.read();
}
} catch (IOException ioException) {

throw new TuplesException("Failed to transfer bytes from source to " +
"cache due to an I/O error.", ioException);
} finally {

try {

// Attempt to shutdown the output stream
outStream.flush();
outStream.close();
} catch (IOException ioException) {

throw new TuplesException("Failed to close output stream to cache",
ioException);
}

try {

// Attempt to close down the input stream reader
reader.close();
} catch (IOException ioException) {

throw new TuplesException("Failed to close input stream from " +
uri.toString(), ioException);
}
}
}

return file;
}
}

An analysis of the class is as follows:

package org.mulgara.content.mp3;

// Java 2 standard packages
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.MalformedURLException;
import java.util.*;
import org.xml.sax.*;

// Third party packages
import org.jrdf.graph.*;
import org.jrdf.graph.mem.*;
import org.apache.log4j.Logger; // Apache Log4J
import org.jrdf.graph.*; // JRDF
import org.jrdf.util.ClosableIterator; // JRDF
import org.jrdf.graph.*; // JRDF
import org.farng.mp3.MP3File;
import org.farng.mp3.TagException;

// Locally written packages
import org.mulgara.query.Constraint;
import org.mulgara.query.QueryException;
import org.mulgara.query.TuplesException;
import org.mulgara.query.Variable;
import org.mulgara.query.rdf.*;
import org.mulgara.resolver.spi.LocalizeException;
import org.mulgara.resolver.spi.Statements;
import org.mulgara.resolver.spi.ResolverSession;
import org.mulgara.store.StoreException;
import org.mulgara.store.tuples.AbstractTuples;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.content.Content;
import org.mulgara.content.mp3.parser.*;
import org.mulgara.content.mp3.parser.exception.*;
import org.mulgara.content.mp3.parser.api.*;

There are no specific requirements for the packaging of the Statements implementation but it is recommended that you keep it in the same package as your ContentHandler implementation. You must import the following classes:

Any supporting classes can also be imported.

public class MP3Statements extends AbstractTuples implements Statements {

All statements classes should implement Statements and extend AbstractTuples.

/**
* Construct an RDF/XML stream parser.
*
* @param content the content object representing our MP3 file
* @param resolverSession session against which to localize RDF nodes
* @throws IllegalArgumentException if inputStream or
* resolverSession are null
* @throws TuplesException if the inputStream can't be parsed as
* RDF/XML
*/
MP3Statements(Content content, ResolverSession resolverSession) throws
TuplesException {

// Validate "content" parameter
if (content == null) {
throw new IllegalArgumentException("Null \"content\" parameter");
}

// Validate "resolverSession" parameter
if (resolverSession == null) {
throw new IllegalArgumentException("Null \"resolverSession\" parameter");
}

// Initialize fields
this.content = content;
this.resolverSession = resolverSession;
this.triples = new ArrayList();

// Fix the magical column names for RDF statements
setVariables(new Variable[] {new Variable("subject"),
new Variable("predicate"),
new Variable("object")});

try {

// Initialise the parser factory
ParserFactory.getInstance().initialiseFactory();
} catch (FactoryException factoryException) {

throw new TuplesException("Unable to initialise factory for parsers.",
factoryException);
}

// Load in the RDF conversion of the given mp3 content
loadURL();
}

Since the statements object is closely linked to the content handler, constructors should be given package scope as only the handler uses it. However, if you have packaged your Statements implementation differently to your handler then you need to make it public. Generally the only parameter necessary is the resolver's session. If you are parsing the file as part of the Statements object then it is possible to also require the Content object to allow the parser to find the resource. Any configuration required for the parser should occur here, as well as the conversion of triples to a readable format by the interface.

/**
* Load in the RDF conversion from the content object.
*
* @throws TuplesException
*/
private void loadURL() throws TuplesException {

// Discard any existing statements
triples.clear();

try {

// Initialise the model to be a memory based graph
model = new GraphImpl();
} catch (GraphException graphException) {

throw new TuplesException("Unable to create a new graph object.",
graphException);
}

// Create a container for our file
File contentFile = null;

if (!content.getURI().getScheme().equals("file")) {

// If we are dealing with anything other than a file then use the
// caching process

try {

// Convert the URI into a file
contentFile = getCachedFile(content.newInputStream(), content.getURI());
} catch (IOException ioException) {

throw new TuplesException(
"Unable to open a stream to the content file [" +
content.getURI().toString() + "]", ioException);
}
} else {

// Files are local and do not need caching
contentFile = new File(content.getURI());
}

// Parse the content of the file/directory to the model
parseFile(contentFile);

// Parse the stream into RDF statements
blankNodeMap = new HashMap();

try {

// Initialize the metadata now that we know the statements
rowCount = model.getNumberOfTriples();
} catch (GraphException graphException) {

throw new TuplesException(
"Unable to retrieve number of triples in graph.",
graphException);
}

if (logger.isDebugEnabled()) {

logger.debug("Parsed MP3: Found " + rowCount + " triples");
}
}

/**
* Checks whether the given file is a file or directory and then acts
* accordingly. It should not be confused with the parseFile method which
* does the actual conversion from an ID3 tag to RDF. This method is
* recursive so subdirectories will be navigated.
*
* @param file The file or directory we are checking the content of
*
* @throws TuplesException
*/
private void parseFile(File file) throws TuplesException {

if (file.getName().endsWith(".mp3")) {

// If the file is a valid mp3 file then parse the content into the model

// Container for our mp3 file
MP3File mp3File = null;

try {

// Create a new MP3 file to represent our content
mp3File = new MP3File(file);
} catch (IOException ioException) {

throw new TuplesException("Unable to create mp3 file object for path: " +
file.getAbsolutePath(), ioException);
} catch (TagException tagException) {

throw new TuplesException("Unable to read ID3 tags for file: " +
file.getAbsolutePath(), tagException);
}

// Create a container for our file id
String fileId = "";

// Try to use the file uri as the id
fileId = file.toURI().toString();

// Create a new conversion object
MP3Conversion conversion = new MP3Conversion(mp3File, model, fileId);

// Container for our parser object
ID3Parser parser = null;

try {

// Get a parser instance
parser = ParserFactory.getInstance().createID3Parser();
} catch (FactoryException factoryException) {

throw new TuplesException(
"Unable to create a new ID3Parser due to a factory error.",
factoryException);
}

try {

// Parse the mp3 into the model
parser.parseTags(conversion);
} catch (ParserException parserException) {

throw new TuplesException("Unable to parse tags for file: " +
content.getURI().toString(), parserException);
}
} else {

throw new TuplesException("Content object did not contain a valid mime " +
"type for parsing.");
}
}

/**
* Creates a locally cached version of a file from an input stream. If the
* file already exists then it will not download the file but instead use the
* cached version.
*
* @param inputStream The stream of data we are caching
* @param uri The uri of the data we are caching
*
* @return The file handle to the cached file
*
* @throws TuplesException
*/
private File getCachedFile(InputStream inputStream, URI uri) throws
TuplesException {

// Retrieve the path to the file on the remote host
String remotePath = uri.getPath();

// Retrieve the actual name of the file
String fileName = remotePath.substring(remotePath.lastIndexOf("/") + 1,
remotePath.length());

if (logger.isDebugEnabled()) {

logger.debug("Transferring [" + uri + "] to cached file [" + fileName + "]");
}

// Create a temporary cache directory handle
File cache = new File(System.getProperty("java.io.tmpdir") + File.separator +
"resolvercache");

if (!cache.exists()) {

// Check that the directory exists and if not then create it
cache.mkdirs();
}

if (!cache.isDirectory()) {

// If we can't use the directory name because a file has it, then just use
// the temporary directory
cache = new File(System.getProperty("java.io.tmpdir"));
}

// Create a new file representing the cached file
File file = new File(cache, fileName);

if (!file.exists()) {

// If the file does not exists in the cache already then download the file

// Container for our OutputStream to the file
OutputStream outStream = null;

try {

// Attempt to create an output stream to the output file
outStream = new FileOutputStream(file);
} catch (FileNotFoundException fileNotFoundException) {

throw new TuplesException("Unable to locate output file for caching " +
"of local version of: " + uri.toString(),
fileNotFoundException);
}

// Create an inputStream to read from
InputStreamReader reader = new InputStreamReader(inputStream);

// Container for the bytes in our stream
int nextByte = 0;

try {

// Get the first byte of the stream
nextByte = reader.read();

while (nextByte != -1) {

// Write out the current byte
outStream.write(nextByte);

// Read the next byte of the file
nextByte = reader.read();
}
} catch (IOException ioException) {

throw new TuplesException("Failed to transfer bytes from source to " +
"cache due to an I/O error.", ioException);
} finally {

try {

// Attempt to shutdown the output stream
outStream.flush();
outStream.close();
} catch (IOException ioException) {

throw new TuplesException("Failed to close output stream to cache",
ioException);
}

try {

// Attempt to close down the input stream reader
reader.close();

} catch (IOException ioException) {

throw new TuplesException("Failed to close input stream from " +
uri.toString(), ioException);
}
}
}

return file;
}

These three methods are not part of the Statements interface implementation but are responsible for loading in the triples from the input stream represented by the Content object using the parser. The loadURL() method is called from the constructor and sets up the content for parsing, however, the Content interface only allows us access by an input stream so we don't have to worry about protocol. This is a problem for the MP3 parser as it only handles local files. To overcome this, the getCachedFile(InputStream, URI) method exists to allow you to read a remote file into a local cache. Once a local file is created (if the resource wasn't already) you can then call parseFile(File) to generate the JRDF Graph object that you can navigate through to read triples from.

/**
* Retrieves the value contained in the subject column for the current triple.
*
* @return The subject value for the current triple
*
* @throws TuplesException
*/
public long getSubject() throws TuplesException {

return getColumnValue(SUBJECT);
}

/**
* Retrieves the value contained in the predicate column for the current triple.
*
* @return The predicate value for the current triple
*
* @throws TuplesException
*/
public long getPredicate() throws TuplesException {

return getColumnValue(PREDICATE);
}

/**
* Retrieves the value contained in the object column for the current triple.
*
* @return The object value for the current triple
*
* @throws TuplesException
*/
public long getObject() throws TuplesException {

return getColumnValue(OBJECT);
}

/**
* @param column 0 for the subject, 1 for the predicate, 2 for the object
*/
public long getColumnValue(int column) throws TuplesException {

// Pull the appropriate field from the current triple as a JRDF Node
Node node = null;

switch (column) {
case SUBJECT:

// Try creating the node with a URI reference
node = tripleStatement.getSubject();

break;
case PREDICATE:

// Try to create a URI reference node to represent the predicate
node = tripleStatement.getPredicate();

break;
case OBJECT:

// Create a literal node with the value for objects
node = tripleStatement.getObject();

break;
default:

throw new TuplesException("No such column " + column);
}
assert node != null;

// Localize the node
try {

return resolverSession.localize(node);
} catch (LocalizeException e) {

throw new TuplesException("Couldn't get column " + column + " value", e);
}
}

The class maintains a list of the triples represented by the model URL as well as the current triple, which can have the values of each of its columns retrieved using the above methods. Nodes retrieved using these methods must be in their localised form.

/**
* Resets the counter for triples to be the first.
*
* @param prefix The prefix to use
* @param suffixTruncation The truncation of suffixes to use
*
* @throws TuplesException
*/
public void beforeFirst (long[] prefix, int suffixTruncation) throws
TuplesException {

try {

// Get the iterator for statements in the model
nextTriple = model.find(null, null, null);
} catch (GraphException graphException) {

throw new TuplesException("Unable to retrieve triple iterator for graph.",
graphException);
}


if (logger.isDebugEnabled()) {

try {

logger.debug("-- Getting the before first value from model " + model +
" which has statements " + nextTriple.hasNext() + " from " +
model.getNumberOfTriples() + " triples");
} catch (GraphException graphException) {

// Since we are debugging, it is not important if this exception is
// ignored
}
}
}

Since statements represent a navigable list of tuples they should also have a method for resetting the pointer. This is what the beforeFirst(long[], int) method does. In the case of the MP3 statement set, the iterator is reset to begin interation over the graph's triples.

/**
* Close the RDF/XML formatted input stream.
*/
public void close () throws TuplesException {

resolverSession = null;
tripleStatement = null;
triples = null;
url = null;
}

After statements are finished with, the close() method is called to free up any resources that are associated with them. Since the MP3 parser takes care of itself, only the objects used by the statement navigation need to be freed up.

public List getOperands () {

return Collections.EMPTY_LIST;
}

public long getRowCount () throws TuplesException {

return rowCount;
}

public long getRowUpperBound () throws TuplesException {

return getRowCount();
}

public boolean hasNoDuplicates () throws TuplesException {

return false;
}

public boolean isColumnEverUnbound (int column) throws TuplesException {

switch (column) {

case 0:

case 1:

case 2:

return false;
default:

throw new TuplesException("No such column " + column);
}
}

The above methods find out information about the object, such as the number of rows, whether there are duplicates, if columns are unbound (that is, have no value) or what the operands on the tuples are.

public boolean next () throws TuplesException {

if (nextTriple.hasNext()) {

// Get the next statement in the iterator
tripleStatement = (Triple) nextTriple.next();

if (logger.isDebugEnabled()) {

logger.debug("-- Getting next statement: " + tripleStatement.toString());
}

return true;
} else {

tripleStatement = null;

return false;
}
}

Statements iterate through the triples of the data store and use the next() method to advance the pointer. The actual triple is stored internally and its values are given out through the getSubject(), getPredicate(), and getObject() methods. If there are no more triples available then the method returns false. In the case of the MP3Statements object, the next value of the iterator is retrieved.

Valid XHTML 1.0 TransitionalValid CSS 3.0!