package edu.mit.simile.fresnel.util;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import edu.mit.simile.fresnel.FresnelUtilities;
/**
* Convenience class for rdf:List resources.
*
* @author ryanlee
*/
public class RDFList {
/**
* Data graph containing this rdf:List
*/
private Repository _source;
/**
* Resource identifying this rdf:List in the graph
*/
private Resource _origin;
/**
* Constructor based on graph and rdf:List resource. Check using isRDFList() and checkValid()
* before calling constructor.
*
* @param in Data <code>Graph</code>
* @param subject The rdf:List <code>Resource</code>
*/
public RDFList(Repository in, Resource subject) {
this._source = in;
this._origin = subject;
}
/**
* Checks if a resource is actually an rdf:List
*
* @param in Data <code>Graph</code>
* @param subject The <code>Resource</code> to check
* @return True if resource is an rdf:List, false if not
*/
public static boolean isRDFList(Repository in, Resource subject) {
return checkValid(in, subject);
}
/**
* As rdf:List is recursively defined, check at each step that the rest
* of the list at this stage is still valid.
*
* @return True if this part of the rdf:List is a valid list, false if not
*/
public boolean isValid() {
return checkValid(this._source, this._origin);
}
/**
* Iterator for moving through the list; synonymous with RDFListIterator(this).
*
* @return An <code>Iterator</code> through the rdf:List
*/
public RDFListIterator iterator() {
return new RDFListIterator(this);
}
/**
* Checks if the rdf:List is empty.
*
* @return True if empty, false otherwise
*/
public boolean isEmpty() {
return (this._origin instanceof URI && ((URI) this._origin).equals(RDF.NIL));
}
/**
* Convenience method to throw exceptions if attempting to access something in
* the face of an rdf:nil value in the list.
*
* @param condition A <code>String</code> explaining the potential problem
* @throws EmptyListException If rdf:List is currently at rdf:nil (no more list to view)
*/
public void checkNotNil(String condition) throws EmptyListException {
if (isEmpty())
throw new EmptyListException(condition);
}
/**
* Convert the rdf:List to a Java List and work with it in that form instead.
*
* @return A <code>List</code>
*/
public List<Value> asJavaList() {
List<Value> l = new ArrayList<Value>();
for (RDFListIterator i = iterator(); i.hasNext(); ) {
l.add(i.next());
}
return l;
}
/**
* Returns the head (rdf:first) of a rdf:List; car to those familiar with Lisp
*
* @return The value of rdf:first in this rdf:List
* @throws EmptyListException If the list is actually empty
*/
public Value getHead() throws EmptyListException {
//if (s_checkValid) {
checkValid(this._source, this._origin);
//}
checkNotNil("Tried to get the head of an empty list");
return FresnelUtilities.getSinglePropertyValue(this._source, this._origin, RDF.FIRST);
}
/**
* Returns the remainder (rdf:rest) of a rdf:list; cdr to those familiar with Lisp
*
* @return The value of rdf:rest in this rdf:List
* @throws EmptyListException If the list is actually empty
*/
public RDFList getTail() throws EmptyListException {
//if (s_checkValid) {
checkValid(this._source, this._origin);
//}
checkNotNil("Tried to get the tail of an empty list");
Resource tail = (Resource) FresnelUtilities.getSinglePropertyValue(this._source, this._origin, RDF.REST);
return new RDFList(this._source, tail);
}
/**
* Checks that an rdf:List is valid; call before constructing
*
* @param in Data source <code>Graph</code>
* @param subject Potential rdf:List <code>Resource</code>
* @return True if a valid rdf:List, false otherwise
*/
protected static boolean checkValid(Repository in, Resource subject) {
if (!(subject instanceof URI) || !((URI) subject).equals(RDF.NIL)) {
return(checkValidProperty(in, subject, RDF.FIRST) && checkValidProperty(in, subject, RDF.REST));
} else {
return true;
}
}
/**
* Checks that the properties making up an rdf:List point to only one value at a time
*
* @param in Data source <code>Graph</code>
* @param subject The rdf:List <code>Resource</code> to check
* @param p The property <code>URI</code> (normally rdf:first or rdf:rest)
* @return True if subject/property has only one value, false otherwise
*/
private static boolean checkValidProperty(Repository in, Resource subject, URI p) {
int count = 0;
try {
RepositoryConnection conn = in.getConnection();
RepositoryResult<Statement> j = conn.getStatements(subject, p, null, false);
try {
while ( j.hasNext() && count <= 2 ) {
count++;
j.next();
}
} finally {
j.close();
}
conn.close();
} catch (RepositoryException e) {
// TODO: how to handle exception
}
// exactly one value is expected
return (count == 1);
}
/**
* For iterating through the rdf:List construct.
*
* @author ryanlee
*/
protected class RDFListIterator implements Iterator {
/**
* Tracks the value of rdf:first during iteration
*/
private RDFList _head;
/**
* Tracks the previous value of rdf:first during iteration
*/
private RDFList _seen;
/**
* Constructor based on an rdf:List
*
* @param head The <code>RDFList</code>
*/
protected RDFListIterator(RDFList head) {
this._head = head;
}
/**
* Checks if anything remains in the rdf:List.
*
* @return True if anything other than rdf:nil is at the present marker in the rdf:List, false
* otherwise
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() {
return !this._head.isEmpty();
}
/**
* Fetches the next element in the rdf:List
*
* @return An <code>Object</code>
* @see java.util.Iterator#next()
*/
public Value next() {
try {
this._seen = this._head;
this._head = this._head.getTail();
return this._seen.getHead();
} catch (EmptyListException e) {
throw new NoSuchElementException(e.toString());
}
}
/**
* A no-op.
*
* @see java.util.Iterator#remove()
*/
public void remove() {
throw new UnsupportedOperationException("remove() not implemented in RDFListIterator");
}
}
}