/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package
///////////////
package org.apache.jena.rdf.model.impl;
// Imports
///////////////
import java.util.*;
import java.util.function.Function;
import org.apache.jena.enhanced.* ;
import org.apache.jena.graph.* ;
import org.apache.jena.ontology.* ;
import org.apache.jena.rdf.model.* ;
import org.apache.jena.shared.* ;
import org.apache.jena.util.iterator.* ;
import org.apache.jena.vocabulary.* ;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Standard implementation the list abstraction from rdf.model.
* </p>
*/
public class RDFListImpl
extends ResourceImpl
implements RDFList
{
// Constants
//////////////////////////////////
// Static variables
//////////////////////////////////
/**
* A factory for generating RDFList facets from nodes in enhanced graphs.
*/
@SuppressWarnings("hiding")
public static Implementation factory = new Implementation() {
@Override public EnhNode wrap( Node n, EnhGraph eg ) {
if (canWrap( n, eg )) {
RDFListImpl impl = new RDFListImpl( n, eg );
// pass on the vocabulary terms, if available
if (eg instanceof OntModel) {
Profile prof = ((OntModel) eg).getProfile();
impl.m_listFirst = prof.FIRST();
impl.m_listRest = prof.REST();
impl.m_listNil = prof.NIL();
impl.m_listType = prof.LIST();
}
return impl;
}
else {
throw new JenaException( "Cannot convert node " + n + " to RDFList");
}
}
@Override public boolean canWrap( Node node, EnhGraph eg ) {
Graph g = eg.asGraph();
// if we are using a language profile, get the first, rest and next resources from there
Resource first = RDF.first;
Resource rest = RDF.rest;
Resource nil = RDF.nil;
if (eg instanceof OntModel) {
Profile prof = ((OntModel) eg).getProfile();
first = prof.FIRST();
rest = prof.REST();
nil = prof.NIL();
}
// node will support being an RDFList facet if it has rdf:type rdf:List, is nil, or is in the domain of a list property
return
node.equals( nil.asNode() ) ||
g.contains( node, first.asNode(), Node.ANY ) ||
g.contains( node, rest.asNode(), Node.ANY ) ||
g.contains( node, RDF.type.asNode(), RDF.List.asNode() );
}
};
/** Flag to indicate whether we are checking for valid lists during list operations. Default false. */
protected static boolean s_checkValid = false;
private static final Logger log = LoggerFactory.getLogger( RDFListImpl.class );
// Instance variables
//////////////////////////////////
/** Error message if validity check fails */
protected String m_errorMsg = null;
/** Pointer to the node that is the tail of the list */
protected RDFList m_tail = null;
/** The URI for the 'first' property in this list */
protected Property m_listFirst = RDF.first;
/** The URI for the 'rest' property in this list */
protected Property m_listRest = RDF.rest;
/** The URI for the 'nil' Resource in this list */
protected Resource m_listNil = RDF.nil;
/** The URI for the rdf:type of this list */
protected Resource m_listType = RDF.List;
// Constructors
//////////////////////////////////
/**
* <p>
* Construct an implementation of RDFList in the given graph, where the
* given node is the head of the list.
* </p>
*
* @param n The node that is the head of the list, currently
* @param g The enh graph that contains n
*/
public RDFListImpl( Node n, EnhGraph g ) {
super( n, g );
}
// External signature methods
//////////////////////////////////
// vocabulary terms
public Resource listType() { return m_listType; }
public Resource listNil() { return m_listNil; }
public Property listFirst() { return m_listFirst; }
public Property listRest() { return m_listRest; }
public Class<? extends RDFList> listAbstractionClass() { return RDFList.class; }
/**
* <p>
* Answer the number of elements in the list.
* </p>
*
* @return The length of the list as an integer
*/
@Override
public int size() {
if (s_checkValid) {
checkValid();
}
int size = 0;
for (Iterator<RDFNode> i = iterator(); i.hasNext(); i.next()) {
size++;
}
return size;
}
/**
* <p>
* Answer the value that is at the head of the list.
* </p>
*
* @return The value that is associated with the head of the list.
* @exception EmptyListException if this list is the empty list
*/
@Override
public RDFNode getHead() {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to get the head of an empty list" );
return getRequiredProperty( listFirst() ).getObject();
}
/**
* <p>
* Update the head of the list to have the given value, and return the
* previous value.
* </p>
*
* @param value The value that will become the value of the list head
* @exception EmptyListException if this list is the empty list
*/
@Override
public RDFNode setHead( RDFNode value ) {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to set the head of an empty list" );
// first remove the existing head
Statement current = getRequiredProperty( listFirst() );
RDFNode n = current.getObject();
current.remove();
// now add the new head value to the graph
addProperty( listFirst(), value );
return n;
}
/**
* <p>
* Answer the list that is the tail of this list.
* </p>
*
* @return The tail of the list, as a list
* @exception EmptyListException if this list is the empty list
*/
@Override
public RDFList getTail() {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to get the tail of an empty list" );
Resource tail = getRequiredProperty( listRest() ).getResource();
return tail.as( listAbstractionClass() );
}
/**
* <p>
* Update the list cell at the front of the list to have the given list as
* tail. The old tail is returned, and remains in the model.
* </p>
*
* @param tail The new tail for this list.
* @return The old tail.
*/
@Override
public RDFList setTail( RDFList tail ) {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to set the tail of an empty list" );
return (setTailAux( this, tail, listRest() )).as( listAbstractionClass() );
}
/**
* Answer true if this list is the empty list.
*
* @return True if this is the empty (nil) list, otherwise false.
*/
@Override
public boolean isEmpty() {
if (s_checkValid) {
checkValid();
}
return equals( listNil() );
}
/**
* <p>
* Return a reference to a new list cell whose head is <code>value</code>
* and whose tail is this list.
* </p>
*
* @param value A new value to add to the head of the list
* @return The new list, whose head is <code>value</code>
*/
@Override
public RDFList cons( RDFNode value ) {
if (s_checkValid) {
checkValid();
}
// create a new, anonymous typed resource to be the list cell
// map to a list facet
return (newListCell( value, this )).as( listAbstractionClass() );
}
/**
* <p>
* Add the given value to the end of the list. This is a side-effecting
* operation on the underlying model that is only defined if this is not the
* empty list. If this list is the empty (nil) list, we cannot perform a
* side-effecting update without changing the URI of this node (from <code>rdf:nil</code)
* to a blank-node for the new list cell) without violating a Jena invariant.
* Therefore, this update operation will throw an exception if an attempt is
* made to add to the nil list. Safe ways to add to an empty list include
* {@link #with} and {@link #cons}.
* </p>
*
* @param value A value to add to the end of the list
* @exception EmptyListUpdateException if an attempt is made to
* <code>add</code> to the empty list.
*/
@Override
public void add( RDFNode value ) {
if (s_checkValid) {
checkValid();
}
// if this is the empty list, we have to barf
if (isEmpty()) {
throw new EmptyListUpdateException( "Attempt to add() to the empty list (rdf:nil)" );
}
// get the tail of the list (which may be cached)
RDFList tail = findElement( true, 0 );
// now do the concatenate
setTailAux( tail, newListCell( value, listNil() ), listRest() );
}
/**
* <p>
* Answer the list that is this list with the given value added to the end
* of the list. This operation differs from {@link #add} in that it will
* always work, even on an empty list, but the return value is the updated
* list. Specifically, in the case of adding a value to the empty list, the
* returned list will not be the same as this list. <strong>Client code should
* not assume that this is an in-place update, but should ensure that the resulting
* list is asserted back into the graph into the appropriate relationships.</strong>
* </p>
*
* @param value A value to add to the end of the list
* @return The list that results from adding a value to the end of this list
*/
@Override
public RDFList with( RDFNode value ) {
if (s_checkValid) {
checkValid();
}
// if this is the empty list, we create a new node containing value - i.e. cons
if (isEmpty()) {
return cons( value );
}
// get the tail of the list (which may be cached)
RDFList tail = findElement( true, 0 );
// now do the concatenate
setTailAux( tail, newListCell( value, listNil() ), listRest() );
return this;
}
/**
* <p>
* Answer the node that is the i'th element of the list, assuming that the
* head is item zero. If the list is too short to have an i'th element,
* throws a {@link ListIndexException}.
* </p>
*
* @param i The index into the list, from 0
* @return The list value at index i, or null
* @exception ListIndexException if the list has fewer than (i + 1)
* elements.
*/
@Override
public RDFNode get( int i ) {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to get an element from the empty list" );
return findElement( false, i ).getHead();
}
/**
* <p>
* Replace the value at the i'th position in the list with the given value.
* If the list is too short to have an i'th element, throws a {@link
* ListIndexException}.
* </p>
*
* @param i The index into the list, from 0
* @param value The new value to associate with the i'th list element
* @return The value that was previously at position i in the list
* @exception ListIndexException if the list has fewer than (i + 1)
* elements.
*/
@Override
public RDFNode replace( int i, RDFNode value ) {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Tried to replace a value in the empty list" );
return findElement( false, i ).setHead( value );
}
/**
* <p>
* Answer true if the given node appears as the value of a value of any
* of the cells of this list.
* </p>
*
* @param value A value to test for
* @return True if the list contains value.
*/
@Override
public boolean contains( RDFNode value ) {
return indexOf( value, 0 ) >= 0;
}
/**
* <p>
* Answer the index of the first occurrence of the given value in the list,
* or -1 if the value is not in the list.
* </p>
*
* @param value The value to search for
* @return The index of the first occurrence of value in the list, or
* <code>-1</code> if not found.
*/
@Override
public int indexOf( RDFNode value ) {
return indexOf( value, 0 );
}
/**
* <p>
* Answer the index of the first occurrence of the given value in the list
* after index <code>start</code>, or -1 if the value is not in the list
* after the given start point.
* </p>
*
* @param value The value to search for
* @param start The index into the list to start searching from
* @return The index (from zero, the front of the list) of the first
* occurrence of <code>value</code> in the list not less than
* <code>start</code>, or <code>-1</code> if not found.
* @exception ListIndexException if <code>start</code> is greater than the
* length of the list.
*/
@Override
public int indexOf( RDFNode value, int start ) {
if (s_checkValid) {
checkValid();
}
// first get to where we start
Resource l = findElement( false, start );
int index = start;
Property head = listFirst();
Property tail = listRest();
Resource nil = listNil();
boolean found = l.hasProperty( head, value );
// search for the element whose value is, er, value
while (!found && !l.equals( nil )) {
l = l.getRequiredProperty( tail ).getResource();
index++;
found = l.hasProperty( head, value );
}
return found ? index : -1;
}
/**
* <p>
* Answer a new list that is formed by adding each element of this list to
* the head of the the list formed from the
* given <code>nodes</code>. This is a non side-effecting
* operation on either this list or the given list, but generates a copy
* of this list. For a more storage efficient alternative, see {@link
* #concatenate concatenate}.
* </p>
*
* @param nodes An iterator whose range is RDFNode
* @return A new RDFList that contains all of this elements of this list,
* followed by all of the elements of the given iterator.
*/
@Override
public RDFList append( Iterator<? extends RDFNode> nodes ) {
return append( copy( nodes) );
}
/**
* <p>
* Answer a new list that is formed by adding each element of this list to
* the head of the given <code>list</code>. This is a non side-effecting
* operation on either this list or the given list, but generates a copy
* of this list. For a more storage efficient alternative, see {@link
* #concatenate concatenate}.
* </p>
*
* @param list The argument list
* @return A new RDFList that contains all of this elements of this list,
* followed by all of the elements of the given list.
*/
@Override
public RDFList append( RDFList list ) {
if (s_checkValid) {
checkValid();
}
if (isEmpty()) {
// special case
return list;
}
else {
// could do this recursively, but for long lists it's better to iterate
// do the copy, then change the last tail pointer to point to the arg
RDFList copy = copy( iterator() );
copy.concatenate( list );
return copy;
}
}
/**
* <p>
* Change the tail of this list to point to the given list, so that this
* list becomes the list of the concatenation of the elements of both lists.
* This is a side-effecting operation on this list; for a non side-effecting
* alternative, see {@link #append}. Due to the problem of maintaining
* the URI invariant on a node, this operation will throw an exception if an
* attempt is made to concatenate onto an empty list. To avoid this, test for
* an empty list: if true replace the empty list with the argument list, otherwise
* proceed with the concatenate as usual. An alternative solution is to use
* {@link #append} and replace the original list with the return value.
* </p>
*
* @param list The argument list to concatenate to this list
* @exception EmptyListUpdateException if this list is the nil list
*/
@Override
public void concatenate( RDFList list ) {
if (s_checkValid) {
checkValid();
}
if (isEmpty()) {
// concatenating list onto the empty list is an error
throw new EmptyListUpdateException( "Tried to concatenate onto the empty list" );
}
else {
// find the end of this list and link it to the argument list
findElement( true, 0 ).setTail( list );
}
}
/**
* <p>
* Add the nodes returned by the given iterator to the end of this list.
* </p>
*
* @param nodes An iterator whose range is RDFNode
* @exception EmptyListUpdateException if this list is the nil list
* @see #concatenate(RDFList) for details on avoiding the empty list update exception.
*/
@Override
public void concatenate( Iterator<? extends RDFNode> nodes ) {
// make a list of the nodes and add to the end of this
concatenate( copy( nodes ) );
}
/**
* <p>
* Answer a list that contains all of the elements of this list in the same
* order, but is a duplicate copy in the underlying model.
* </p>
*
* @return A copy of the current list
*/
@Override
public RDFList copy() {
if (s_checkValid) {
checkValid();
}
return copy( iterator() );
}
/**
* <p>
* Apply a function to each value in the list in turn.
* </p>
*
* @param fn The function to apply to each list node.
*/
@Override
public void apply( ApplyFn fn ) {
if (s_checkValid) {
checkValid();
}
for (Iterator<RDFNode> i = iterator(); i.hasNext(); ) {
fn.apply( i.next() );
}
}
/**
* <p>
* Apply a function to each value in the list in turn, accumulating the
* results in an accumulator. The final value of the accumulator is returned
* as the value of <code>reduce()</code>.
* </p>
*
* @param fn The reduction function to apply
* @param initial The initial value for the accumulator
* @return The final value of the accumulator.
*/
@Override
public Object reduce( ReduceFn fn, Object initial ) {
if (s_checkValid) {
checkValid();
}
Object acc = initial;
for (Iterator<RDFNode> i = iterator(); i.hasNext(); ) {
acc = fn.reduce( i.next(), acc );
}
return acc;
}
/**
* <p>Answer an iterator of the elements of this list, to each of which
* the given map function has been applied.</p>
* @param fn A Map function
* @return The iterator of the elements of this list mapped with the given map function.
*/
@Override
public <T> ExtendedIterator<T> mapWith( Function<RDFNode, T> fn ) {
return iterator().mapWith( fn );
}
/**
* <p>
* Remove the value from the head of the list. The tail of the list remains
* in the model. Note that no changes are made to list cells that point to
* this list cell as their tail. Immediately following a
* <code>removeHead</code> operation, such lists will be in a non-valid
* state.
* </p>
*
* @return The remainder of the list after the head is removed (i.e. the
* pre-removal list tail)
*/
@Override
public RDFList removeHead() {
if (s_checkValid) {
checkValid();
}
checkNotNil( "Attempted to delete the head of a nil list" );
RDFList tail = getTail();
removeProperties();
return tail;
}
/**
* <p>Remove the given value from this list. If <code>val</code> does not occur in
* the list, no action is taken. Since removing the head of the list will invalidate
* the list head cell, in general the list must return the list that results from this
* operation. However, in many cases the return value will be the same as the object
* that this method is invoked on</p>
*
* @param val The value to be removed from the list
* @return The resulting list, which will be the same as the current list in most
* cases, except when <code>val</code> occurs at the head of the list.
*/
@Override
public RDFList remove( RDFNode val ) {
if (s_checkValid) {
checkValid();
}
RDFList prev = null;
RDFList cell = this;
boolean searching = true;
while (searching && !cell.isEmpty()) {
if (cell.getHead().equals( val )) {
// found the value to be removed
RDFList tail = cell.getTail();
if (prev != null) {
prev.setTail( tail );
}
cell.removeProperties();
// return this unless we have removed the head element
return (prev == null) ? tail : this;
}
else {
// not found yet
prev = cell;
cell = cell.getTail();
}
}
// not found
return this;
}
/**
* <p>Deprecated. Since an <code>RDFList</code> does not behave like a Java container, it is not
* the case that the contents of the list can be removed and the container filled with values
* again. Therefore, this method name has been deprecated in favour of {@link #removeList}</p>
* @deprecated Replaced by {@link #removeList}
*/
@Override
@Deprecated
public void removeAll() {
removeList();
}
/**
* <p>Remove all of the components of this list from the model. Once this operation
* has completed, the {@link RDFList} resource on which it was called will no
* longer be a resource in the model, so further methods calls on the list object
* (for example, {@link #size} will fail. Due to restrictions on the encoding
* of lists in RDF, it is not possible to perform an operation which empties a list
* and then adds further values to that list. Client code wishing to perform
* such an operation should do so in two steps: first remove the old list, then
* create a new list with the new contents. It is important that RDF statements
* that reference the old list (in the object position) be updated to point
* to the newly created list.
* Note that this
* is operation is only removing the list cells themselves, not the resources
* referenced by the list - unless being the object of an <code>rdf:first</code>
* statement is the only mention of that resource in the model.</p>
*/
@Override
public void removeList() {
for ( Statement statement : collectStatements() )
{
statement.remove();
}
}
/**
* <p>Answer a set of all of the RDF statements whose subject is one of the cells
* of this list.</p>
* @return A list of the statements that form the encoding of this list.
*/
public Set<Statement> collectStatements() {
Set<Statement> stmts = new HashSet<>();
RDFList l = this;
do {
// collect all statements of this list cell
for (Iterator<Statement> i = l.listProperties(); i.hasNext(); ) {
stmts.add( i.next() );
}
// move on to next cell
l = l.getTail();
} while (!l.isEmpty());
return stmts;
}
/**
* <p>
* Answer an iterator over the elements of the list. Note that this iterator
* does not take a snapshot of the list, so changes to the list statements
* in the model while iterating will affect the behaviour of the iterator.
* To get an iterator that is not affected by model changes, use {@link
* #asJavaList}.
* </p>
*
* @return A closable iterator over the elements of the list.
*/
@Override
public ExtendedIterator<RDFNode> iterator() {
return new RDFListIterator( this );
}
/**
* <p>
* Answer the contents of this RDF list as a Java list of RDFNode values.
* </p>
*
* @return The contents of this list as a Java List.
*/
@Override
public List<RDFNode> asJavaList() {
List<RDFNode> l = new ArrayList<>();
for (Iterator<RDFNode> i = iterator(); i.hasNext(); ) {
l.add( i.next() );
}
return l;
}
/**
* <p>
* Answer true if this list has the same elements in the same order as the
* given list. Note that the standard <code>equals</code> test just tests
* for equality of two given list cells. While such a test is sufficient
* for many purposes, this test provides a broader equality definition, but
* is correspondingly more expensive to test.
* </p>
*
* @param list The list to test against
* @return True if the given list and this list are the same length, and
* contain equal elements in the same order.
*/
@Override
public boolean sameListAs( RDFList list ) {
if (s_checkValid) {
checkValid();
}
Resource r0 = this;
Resource r1 = list;
Property head = listFirst();
Property tail = listRest();
Resource nil = listNil();
// iterate through to the end of the list
while (!(r0.equals( nil ) || r1.equals( nil ))) {
RDFNode n0 = r0.getRequiredProperty( head ).getObject();
RDFNode n1 = r1.getRequiredProperty( head ).getObject();
if (n0 == null || !n0.equals( n1 )) {
// not equal at this position
return false;
}
else {
// advance along the lists
r0 = r0.getRequiredProperty( tail ).getResource();
r1 = r1.getRequiredProperty( tail ).getResource();
}
}
// lists are equal if they terminate together
return r0.equals( nil ) && r1.equals( nil );
}
/**
* <p>
* Answer true lists are operating in strict mode, in which the
* well- formedness of the list is checked at every operation.
* </p>
*
* @return True lists are being strictly checked.
*/
@Override
public boolean getStrict() {
return s_checkValid;
}
/**
* <p>
* Set a flag to indicate whether to strictly check the well-formedness of
* lists at each operation. Default false. Note that the flag that is
* manipulated is actually a static: it applies to all lists. However, RDFList
* is a Java interface, and Java does not permit static methods in interfaces.
* </p>
*
* @param strict The <b>static</b> flag for whether lists will be checked strictly.
*/
@Override
public void setStrict( boolean strict ) {
s_checkValid = strict;
}
/**
* <p>
* Answer true if the list is well-formed, by checking that each node is
* correctly typed, and has a head and tail pointer from the correct
* vocabulary.
* </p>
*
* @return True if the list is well-formed.
*/
@Override
public boolean isValid() {
m_errorMsg = null;
try {
checkValid();
}
catch (InvalidListException e) {
m_errorMsg = e.getMessage();
}
return (m_errorMsg == null);
}
/**
* <p>
* Answer the error message returned by the last failed validity check,
* if any.
* </p>
*
* @return The most recent error message, or null.
*/
@Override
public String getValidityErrorMessage() {
return m_errorMsg;
}
/**
* <p>
* Construct a new list cell with the given value and tail.
* </p>
*
* @param value The value at the head of the new list cell
* @param tail The tail of the list cell
* @return A new list cell as a resource
*/
public Resource newListCell( RDFNode value, Resource tail ) {
// Note: following the RDF WG decision, we no longer assert rdf:type rdf:List for list cells
Resource cell = getModel().createResource();
// set the head and tail
cell.addProperty( listFirst(), value );
cell.addProperty( listRest(), tail );
return cell;
}
// Internal implementation methods
//////////////////////////////////
/**
* <p>
* Answer true if this is a valid list cell, which means either that it
* is nil, or it has the appropriate type and a first and next relation.
* Updated 17-06-2003: RDFCore last comments process has decided that the
* rdf:type of a list is implied by the domain constraints on rdf:first
* and rdf:rest, so no longer needs to be asserted directly. The test
* for rdf:type has therefore been removed.
* </p>
*
* @returns True if this list cell passes basic validity checks
*/
protected void checkValid() {
if (!equals( listNil() )) {
// note that the rdf:type of list cells is now implied by the RDF M&S
// so we don't check explicitly
// checkValidProperty( RDF.type, listType() );
checkValidProperty( listFirst(), null );
checkValidProperty( listRest(), null );
}
}
private void checkValidProperty( Property p, RDFNode expected ) {
int count = 0;
for (StmtIterator j = getModel().listStatements( this, p, expected ); j.hasNext(); j.next()) {
count++;
}
// exactly one value is expected
if (count == 0) {
if (log.isDebugEnabled()) {
log.debug( "Failed validity check on " + toString() );
for (StmtIterator i = listProperties(); i.hasNext(); ) {
log.debug( " this => " + i.next() );
}
for (StmtIterator i = getModel().listStatements( null, null, this ); i.hasNext(); ) {
log.debug( " => this " + i.next() );
}
}
throw new InvalidListException( "List node " + toString() + " is not valid: it should have property " +
p.toString() +
(expected == null ? "" : ( " with value " + expected )) );
}
else if (count > 1) {
throw new InvalidListException( "List node " + toString() + " is not valid: it has more than one value for " +
p.toString() );
}
}
/**
* <p>
* Check that the current list cell is not the nil list, and throw an empty
* list exception if it is.
* </p>
*
* @param msg The context message for the empty list exception
* @exception EmptyListException if the list is the nil list
*/
protected void checkNotNil( String msg ) {
if (isEmpty()) {
throw new EmptyListException( msg );
}
}
/**
* <p>
* Find and return an element of this list - either the last element before
* the end of the list, or the i'th element from the front (starting from
* zero). Note that this method assumes the pre-condition that
* <code>this</code> is not the empty list.
* </p>
*
* @param last If true, find the element whose tail is nil
* @param index If <code>last</code> is false, find the index'th element
* from the head of the list
* @return The list cell
* @exception ListIndexException if try to access an element beyond the end
* of the list
* @exception InvalidListException if try to find the end of a badly formed
* list
*/
protected RDFList findElement( boolean last, int index ) {
Property tail = listRest();
Resource nil = listNil();
Resource l = this;
int i = index;
boolean found = (last && l.hasProperty( tail, nil )) || (!last && (i == 0));
// search for the element whose tail is nil, or whose index is now zero
while (!found && !l.equals( nil )) {
l = l.getRequiredProperty( tail ).getResource();
found = (last && l.hasProperty( tail, nil )) || (!last && (--i == 0));
}
if (!found) {
// premature end of list
if (!last) {
throw new ListIndexException( "Tried to access element " + index + " that is beyond the length of the list" );
}
else {
throw new InvalidListException( "Could not find last element of list (suggests list is not valid)" );
}
}
else {
return l.as( listAbstractionClass() );
}
}
/**
* <p>
* Create a copy of the list of nodes returned by an iterator.
* </p>
*
* @param i An iterator of RDFNodes
* @return A list formed from all of the nodes of i, in sequence
*/
protected RDFList copy( Iterator<? extends RDFNode> i ) {
Resource list = null;
Resource start = null;
Property head = listFirst();
Property tail = listRest();
Resource cellType = listType();
if (i.hasNext())
{
while (i.hasNext()){
// create a list cell to hold the next value from the existing list
Resource cell = getModel().createResource( cellType );
cell.addProperty( head, i.next() );
// point the previous list cell to this one
if (list != null) {
list.addProperty( tail, cell );
}
else {
// must be the first cell we're adding
start = cell;
}
list = cell;
}
// finally close the list
list.addProperty( tail, listNil() );
}
else
{
// create an empty list
start = getModel().createList();
}
return start.as( listAbstractionClass() );
}
/**
* <p>
* Helper method for setting the list tail, that assumes we have
* a resource that is a list.
* </p>
*
* @param root The resource representing the list cell we're setting the
* tail of
* @param tail The new tail for this list, as a resource.
* @return The old tail, as a resource.
*/
protected static Resource setTailAux( Resource root, Resource tail, Property pTail ) {
Statement current = root.getRequiredProperty( pTail );
Resource oldTail = current.getResource();
// out with the old, in with the new
current.remove();
root.addProperty( pTail, tail );
return oldTail;
}
//==============================================================================
// Inner class definitions
//==============================================================================
/**
* <p>
* Iterator that can step along chains of list pointers to the end of the
* list.
* </p>
*/
protected class RDFListIterator extends NiceIterator<RDFNode>
{
// Instance variables
/** The current list node */
protected RDFList m_head;
/** The most recently seen node */
protected RDFList m_seen = null;
// Constructor
//////////////
/**
* Construct an iterator for walking the list starting at head
*/
protected RDFListIterator( RDFList head ) {
m_head = head;
}
// External contract methods
////////////////////////////
/**
* @see Iterator#hasNext
*/
@Override public boolean hasNext() {
return !m_head.isEmpty();
}
/**
* @see Iterator#next
*/
@Override public RDFNode next() {
m_seen = m_head;
m_head = m_head.getTail();
return m_seen.getHead();
}
/**
* @see Iterator#remove
*/
@Override public void remove() {
if (m_seen == null) {
throw new IllegalStateException( "Illegal remove from list operator" );
}
// will remove three statements in a well-formed list
((Resource) m_seen).removeProperties();
m_seen = null;
}
}
}