0001: /*****************************************************************************
0002: * Source code information
0003: * -----------------------
0004: * Original author Ian Dickinson, HP Labs Bristol
0005: * Author email Ian.Dickinson@hp.com
0006: * Package Jena 2
0007: * Web http://sourceforge.net/projects/jena/
0008: * Created 24 Jan 2003
0009: * Filename $RCSfile: RDFListImpl.java,v $
0010: * Revision $Revision: 1.17 $
0011: * Release status $State: Exp $
0012: *
0013: * Last modified on $Date: 2008/01/02 12:05:10 $
0014: * by $Author: andy_seaborne $
0015: *
0016: * (c) Copyright 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
0017: * (see footer for full conditions)
0018: *****************************************************************************/package com.hp.hpl.jena.rdf.model.impl;
0019:
0020: // Imports
0021: ///////////////
0022: import com.hp.hpl.jena.ontology.*;
0023: import com.hp.hpl.jena.rdf.model.*;
0024: import com.hp.hpl.jena.shared.*;
0025: import com.hp.hpl.jena.util.iterator.*;
0026: import com.hp.hpl.jena.enhanced.*;
0027: import com.hp.hpl.jena.graph.*;
0028: import com.hp.hpl.jena.vocabulary.*;
0029:
0030: import java.util.*;
0031:
0032: import org.apache.commons.logging.Log;
0033: import org.apache.commons.logging.LogFactory;
0034:
0035: /**
0036: * <p>
0037: * Standard implementation the list abstraction from rdf.model.
0038: * </p>
0039: *
0040: * @author Ian Dickinson, HP Labs
0041: * (<a href="mailto:Ian.Dickinson@hp.com" >email</a>)
0042: * @version CVS $Id: RDFListImpl.java,v 1.17 2008/01/02 12:05:10 andy_seaborne Exp $
0043: */
0044: public class RDFListImpl extends ResourceImpl implements RDFList {
0045: // Constants
0046: //////////////////////////////////
0047:
0048: // Static variables
0049: //////////////////////////////////
0050:
0051: /**
0052: * A factory for generating RDFList facets from nodes in enhanced graphs.
0053: */
0054: public static Implementation factory = new Implementation() {
0055: public EnhNode wrap(Node n, EnhGraph eg) {
0056: if (canWrap(n, eg)) {
0057: RDFListImpl impl = new RDFListImpl(n, eg);
0058:
0059: // pass on the vocabulary terms, if available
0060: if (eg instanceof OntModel) {
0061: Profile prof = ((OntModel) eg).getProfile();
0062: impl.m_listFirst = prof.FIRST();
0063: impl.m_listRest = prof.REST();
0064: impl.m_listNil = prof.NIL();
0065: impl.m_listType = prof.LIST();
0066: }
0067:
0068: return impl;
0069: } else {
0070: throw new JenaException("Cannot convert node " + n
0071: + " to RDFList");
0072: }
0073: }
0074:
0075: public boolean canWrap(Node node, EnhGraph eg) {
0076: Graph g = eg.asGraph();
0077:
0078: // if we are using a language profile, get the first, rest and next resources from there
0079: Resource first = RDF.first;
0080: Resource rest = RDF.rest;
0081: Resource nil = RDF.nil;
0082:
0083: if (eg instanceof OntModel) {
0084: Profile prof = ((OntModel) eg).getProfile();
0085: first = prof.FIRST();
0086: rest = prof.REST();
0087: nil = prof.NIL();
0088: }
0089:
0090: // 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
0091: return node.equals(nil.asNode())
0092: || g.contains(node, first.asNode(), Node.ANY)
0093: || g.contains(node, rest.asNode(), Node.ANY)
0094: || g.contains(node, RDF.type.asNode(), RDF.List
0095: .asNode());
0096: }
0097: };
0098:
0099: /** Flag to indicate whether we are checking for valid lists during list operations. Default false. */
0100: protected static boolean s_checkValid = false;
0101:
0102: private static final Log log = LogFactory.getLog(RDFListImpl.class);
0103:
0104: // Instance variables
0105: //////////////////////////////////
0106:
0107: /** Error message if validity check fails */
0108: protected String m_errorMsg = null;
0109:
0110: /** Pointer to the node that is the tail of the list */
0111: protected RDFList m_tail = null;
0112:
0113: /** The URI for the 'first' property in this list */
0114: protected Property m_listFirst = RDF.first;
0115:
0116: /** The URI for the 'rest' property in this list */
0117: protected Property m_listRest = RDF.rest;
0118:
0119: /** The URI for the 'nil' Resource in this list */
0120: protected Resource m_listNil = RDF.nil;
0121:
0122: /** The URI for the rdf:type of this list */
0123: protected Resource m_listType = RDF.List;
0124:
0125: // Constructors
0126: //////////////////////////////////
0127:
0128: /**
0129: * <p>
0130: * Construct an implementation of RDFList in the given graph, where the
0131: * given node is the head of the list.
0132: * </p>
0133: *
0134: * @param n The node that is the head of the list, currently
0135: * @param g The enh graph that contains n
0136: */
0137: public RDFListImpl(Node n, EnhGraph g) {
0138: super (n, g);
0139: }
0140:
0141: // External signature methods
0142: //////////////////////////////////
0143:
0144: // vocabulary terms
0145: public Resource listType() {
0146: return m_listType;
0147: }
0148:
0149: public Resource listNil() {
0150: return m_listNil;
0151: }
0152:
0153: public Property listFirst() {
0154: return m_listFirst;
0155: }
0156:
0157: public Property listRest() {
0158: return m_listRest;
0159: }
0160:
0161: public Class listAbstractionClass() {
0162: return RDFList.class;
0163: }
0164:
0165: /**
0166: * <p>
0167: * Answer the number of elements in the list.
0168: * </p>
0169: *
0170: * @return The length of the list as an integer
0171: */
0172: public int size() {
0173: if (s_checkValid) {
0174: checkValid();
0175: }
0176:
0177: int size = 0;
0178:
0179: for (Iterator i = iterator(); i.hasNext(); i.next()) {
0180: size++;
0181: }
0182: return size;
0183: }
0184:
0185: /**
0186: * <p>
0187: * Answer the value that is at the head of the list.
0188: * </p>
0189: *
0190: * @return The value that is associated with the head of the list.
0191: * @exception EmptyListException if this list is the empty list
0192: */
0193: public RDFNode getHead() {
0194: if (s_checkValid) {
0195: checkValid();
0196: }
0197:
0198: checkNotNil("Tried to get the head of an empty list");
0199:
0200: return getRequiredProperty(listFirst()).getObject();
0201: }
0202:
0203: /**
0204: * <p>
0205: * Update the head of the list to have the given value, and return the
0206: * previous value.
0207: * </p>
0208: *
0209: * @param value The value that will become the value of the list head
0210: * @exception EmptyListException if this list is the empty list
0211: */
0212: public RDFNode setHead(RDFNode value) {
0213: if (s_checkValid) {
0214: checkValid();
0215: }
0216:
0217: checkNotNil("Tried to set the head of an empty list");
0218:
0219: // first remove the existing head
0220: Statement current = getRequiredProperty(listFirst());
0221: RDFNode n = current.getObject();
0222: current.remove();
0223:
0224: // now add the new head value to the graph
0225: addProperty(listFirst(), value);
0226:
0227: return n;
0228: }
0229:
0230: /**
0231: * <p>
0232: * Answer the list that is the tail of this list.
0233: * </p>
0234: *
0235: * @return The tail of the list, as a list
0236: * @exception EmptyListException if this list is the empty list
0237: */
0238: public RDFList getTail() {
0239: if (s_checkValid) {
0240: checkValid();
0241: }
0242:
0243: checkNotNil("Tried to get the tail of an empty list");
0244:
0245: Resource tail = getRequiredProperty(listRest()).getResource();
0246: return (RDFList) tail.as(listAbstractionClass());
0247: }
0248:
0249: /**
0250: * <p>
0251: * Update the list cell at the front of the list to have the given list as
0252: * tail. The old tail is returned, and remains in the model.
0253: * </p>
0254: *
0255: * @param tail The new tail for this list.
0256: * @return The old tail.
0257: */
0258: public RDFList setTail(RDFList tail) {
0259: if (s_checkValid) {
0260: checkValid();
0261: }
0262:
0263: checkNotNil("Tried to set the tail of an empty list");
0264:
0265: return (RDFList) (setTailAux(this , tail, listRest()))
0266: .as(listAbstractionClass());
0267: }
0268:
0269: /**
0270: * Answer true if this list is the empty list.
0271: *
0272: * @return True if this is the empty (nil) list, otherwise false.
0273: */
0274: public boolean isEmpty() {
0275: if (s_checkValid) {
0276: checkValid();
0277: }
0278:
0279: return equals(listNil());
0280: }
0281:
0282: /**
0283: * <p>
0284: * Return a reference to a new list cell whose head is <code>value</code>
0285: * and whose tail is this list.
0286: * </p>
0287: *
0288: * @param value A new value to add to the head of the list
0289: * @return The new list, whose head is <code>value</code>
0290: */
0291: public RDFList cons(RDFNode value) {
0292: if (s_checkValid) {
0293: checkValid();
0294: }
0295:
0296: // create a new, anonymous typed resource to be the list cell
0297: // map to a list facet
0298: return (RDFList) (newListCell(value, this ))
0299: .as(listAbstractionClass());
0300: }
0301:
0302: /**
0303: * <p>
0304: * Add the given value to the end of the list. This is a side-effecting
0305: * operation on the underlying model that is only defined if this is not the
0306: * empty list. If this list is the empty (nil) list, we cannot perform a
0307: * side-effecting update without changing the URI of this node (from <code>rdf:nil</code)
0308: * to a blank-node for the new list cell) without violating a Jena invariant.
0309: * Therefore, this update operation will throw an exception if an attempt is
0310: * made to add to the nil list. Safe ways to add to an empty list include
0311: * {@link #with} and {@link #cons}.
0312: * </p>
0313: *
0314: * @param value A value to add to the end of the list
0315: * @exception EmptyListUpdateException if an attempt is made to
0316: * <code>add</code> to the empty list.
0317: */
0318: public void add(RDFNode value) {
0319: if (s_checkValid) {
0320: checkValid();
0321: }
0322:
0323: // if this is the empty list, we have to barf
0324: if (isEmpty()) {
0325: throw new EmptyListUpdateException(
0326: "Attempt to add() to the empty list (rdf:nil)");
0327: }
0328:
0329: // get the tail of the list (which may be cached)
0330: RDFList tail = findElement(true, 0);
0331:
0332: // now do the concatenate
0333: setTailAux(tail, newListCell(value, listNil()), listRest());
0334: }
0335:
0336: /**
0337: * <p>
0338: * Answer the list that is this list with the given value added to the end
0339: * of the list. This operation differs from {@link #add} in that it will
0340: * always work, even on an empty list, but the return value is the updated
0341: * list. Specifically, in the case of adding a value to the empty list, the
0342: * returned list will not be the same as this list. <strong>Client code should
0343: * not assume that this is an in-place update, but should ensure that the resulting
0344: * list is asserted back into the graph into the appropriate relationships.</strong>
0345: * </p>
0346: *
0347: * @param value A value to add to the end of the list
0348: * @return The list that results from adding a value to the end of this list
0349: */
0350: public RDFList with(RDFNode value) {
0351: if (s_checkValid) {
0352: checkValid();
0353: }
0354:
0355: // if this is the empty list, we create a new node containing value - i.e. cons
0356: if (isEmpty()) {
0357: return cons(value);
0358: }
0359:
0360: // get the tail of the list (which may be cached)
0361: RDFList tail = findElement(true, 0);
0362:
0363: // now do the concatenate
0364: setTailAux(tail, newListCell(value, listNil()), listRest());
0365: return this ;
0366: }
0367:
0368: /**
0369: * <p>
0370: * Answer the node that is the i'th element of the list, assuming that the
0371: * head is item zero. If the list is too short to have an i'th element,
0372: * throws a {@link ListIndexException}.
0373: * </p>
0374: *
0375: * @param i The index into the list, from 0
0376: * @return The list value at index i, or null
0377: * @exception ListIndexException if the list has fewer than (i + 1)
0378: * elements.
0379: */
0380: public RDFNode get(int i) {
0381: if (s_checkValid) {
0382: checkValid();
0383: }
0384:
0385: checkNotNil("Tried to get an element from the empty list");
0386: return findElement(false, i).getHead();
0387: }
0388:
0389: /**
0390: * <p>
0391: * Replace the value at the i'th position in the list with the given value.
0392: * If the list is too short to have an i'th element, throws a {@link
0393: * ListIndexException}.
0394: * </p>
0395: *
0396: * @param i The index into the list, from 0
0397: * @param value The new value to associate with the i'th list element
0398: * @return The value that was previously at position i in the list
0399: * @exception ListIndexException if the list has fewer than (i + 1)
0400: * elements.
0401: */
0402: public RDFNode replace(int i, RDFNode value) {
0403: if (s_checkValid) {
0404: checkValid();
0405: }
0406:
0407: checkNotNil("Tried to replace a value in the empty list");
0408: return findElement(false, i).setHead(value);
0409: }
0410:
0411: /**
0412: * <p>
0413: * Answer true if the given node appears as the value of a value of any
0414: * of the cells of this list.
0415: * </p>
0416: *
0417: * @param value A value to test for
0418: * @return True if the list contains value.
0419: */
0420: public boolean contains(RDFNode value) {
0421: return indexOf(value, 0) >= 0;
0422: }
0423:
0424: /**
0425: * <p>
0426: * Answer the index of the first occurrence of the given value in the list,
0427: * or -1 if the value is not in the list.
0428: * </p>
0429: *
0430: * @param value The value to search for
0431: * @return The index of the first occurrence of value in the list, or
0432: * <code>-1</code> if not found.
0433: */
0434: public int indexOf(RDFNode value) {
0435: return indexOf(value, 0);
0436: }
0437:
0438: /**
0439: * <p>
0440: * Answer the index of the first occurrence of the given value in the list
0441: * after index <code>start</code>, or -1 if the value is not in the list
0442: * after the given start point.
0443: * </p>
0444: *
0445: * @param value The value to search for
0446: * @param start The index into the list to start searching from
0447: * @return The index (from zero, the front of the list) of the first
0448: * occurrence of <code>value</code> in the list not less than
0449: * <code>start</code>, or <code>-1</code> if not found.
0450: * @exception ListIndexException if <code>start</code> is greater than the
0451: * length of the list.
0452: */
0453: public int indexOf(RDFNode value, int start) {
0454: if (s_checkValid) {
0455: checkValid();
0456: }
0457:
0458: // first get to where we start
0459: Resource l = findElement(false, start);
0460: int index = start;
0461:
0462: Property head = listFirst();
0463: Property tail = listRest();
0464: Resource nil = listNil();
0465:
0466: boolean found = l.hasProperty(head, value);
0467:
0468: // search for the element whose value is, er, value
0469: while (!found && !l.equals(nil)) {
0470: l = l.getRequiredProperty(tail).getResource();
0471: index++;
0472: found = l.hasProperty(head, value);
0473: }
0474:
0475: return found ? index : -1;
0476: }
0477:
0478: /**
0479: * <p>
0480: * Answer a new list that is formed by adding each element of this list to
0481: * the head of the the list formed from the
0482: * given <code>nodes</code>. This is a non side-effecting
0483: * operation on either this list or the given list, but generates a copy
0484: * of this list. For a more storage efficient alternative, see {@link
0485: * #concatenate concatenate}.
0486: * </p>
0487: *
0488: * @param nodes An iterator whose range is RDFNode
0489: * @return A new RDFList that contains all of this elements of this list,
0490: * followed by all of the elements of the given iterator.
0491: */
0492: public RDFList append(Iterator nodes) {
0493: return append(copy(nodes));
0494: }
0495:
0496: /**
0497: * <p>
0498: * Answer a new list that is formed by adding each element of this list to
0499: * the head of the given <code>list</code>. This is a non side-effecting
0500: * operation on either this list or the given list, but generates a copy
0501: * of this list. For a more storage efficient alternative, see {@link
0502: * #concatenate concatenate}.
0503: * </p>
0504: *
0505: * @param list The argument list
0506: * @return A new RDFList that contains all of this elements of this list,
0507: * followed by all of the elements of the given list.
0508: */
0509: public RDFList append(RDFList list) {
0510: if (s_checkValid) {
0511: checkValid();
0512: }
0513:
0514: if (isEmpty()) {
0515: // special case
0516: return list;
0517: } else {
0518: // could do this recursively, but for long lists it's better to iterate
0519: // do the copy, then change the last tail pointer to point to the arg
0520: RDFList copy = copy(iterator());
0521: copy.concatenate(list);
0522: return copy;
0523: }
0524: }
0525:
0526: /**
0527: * <p>
0528: * Change the tail of this list to point to the given list, so that this
0529: * list becomes the list of the concatenation of the elements of both lists.
0530: * This is a side-effecting operation on this list; for a non side-effecting
0531: * alternative, see {@link #append}. Due to the problem of maintaining
0532: * the URI invariant on a node, this operation will throw an exception if an
0533: * attempt is made to concatenate onto an empty list. To avoid this, test for
0534: * an empty list: if true replace the empty list with the argument list, otherwise
0535: * proceed with the concatenate as usual. An alternative solution is to use
0536: * {@link #append} and replace the original list with the return value.
0537: * </p>
0538: *
0539: * @param list The argument list to concatenate to this list
0540: * @exception EmptyListUpdateException if this list is the nil list
0541: */
0542: public void concatenate(RDFList list) {
0543: if (s_checkValid) {
0544: checkValid();
0545: }
0546:
0547: if (isEmpty()) {
0548: // concatenating list onto the empty list is an error
0549: throw new EmptyListUpdateException(
0550: "Tried to concatenate onto the empty list");
0551: } else {
0552: // find the end of this list and link it to the argument list
0553: findElement(true, 0).setTail(list);
0554: }
0555: }
0556:
0557: /**
0558: * <p>
0559: * Add the nodes returned by the given iterator to the end of this list.
0560: * </p>
0561: *
0562: * @param nodes An iterator whose range is RDFNode
0563: * @exception EmptyListUpdateException if this list is the nil list
0564: * @see #concatenate(RDFList) for details on avoiding the empty list update exception.
0565: */
0566: public void concatenate(Iterator nodes) {
0567: // make a list of the nodes and add to the end of this
0568: concatenate(copy(nodes));
0569: }
0570:
0571: /**
0572: * <p>
0573: * Answer a list that contains all of the elements of this list in the same
0574: * order, but is a duplicate copy in the underlying model.
0575: * </p>
0576: *
0577: * @return A copy of the current list
0578: */
0579: public RDFList copy() {
0580: if (s_checkValid) {
0581: checkValid();
0582: }
0583:
0584: return copy(iterator());
0585: }
0586:
0587: /**
0588: * <p>
0589: * Apply a function to each value in the list in turn.
0590: * </p>
0591: *
0592: * @param fn The function to apply to each list node.
0593: */
0594: public void apply(ApplyFn fn) {
0595: if (s_checkValid) {
0596: checkValid();
0597: }
0598:
0599: for (Iterator i = iterator(); i.hasNext();) {
0600: fn.apply((RDFNode) i.next());
0601: }
0602: }
0603:
0604: /**
0605: * <p>
0606: * Apply a function to each value in the list in turn, accumulating the
0607: * results in an accumulator. The final value of the accumulator is returned
0608: * as the value of <code>reduce()</code>.
0609: * </p>
0610: *
0611: * @param fn The reduction function to apply
0612: * @param initial The initial value for the accumulator
0613: * @return The final value of the accumulator.
0614: */
0615: public Object reduce(ReduceFn fn, Object initial) {
0616: if (s_checkValid) {
0617: checkValid();
0618: }
0619:
0620: Object acc = initial;
0621:
0622: for (Iterator i = iterator(); i.hasNext();) {
0623: acc = fn.reduce((RDFNode) i.next(), acc);
0624: }
0625:
0626: return acc;
0627: }
0628:
0629: /**
0630: * <p>Answer an iterator of the elements of this list, to each of which
0631: * the given map function has been applied.</p>
0632: * @param fn A Map function
0633: * @return The iterator of the elements of this list mapped with the given map function.
0634: */
0635: public ExtendedIterator mapWith(Map1 fn) {
0636: return iterator().mapWith(fn);
0637: }
0638:
0639: /**
0640: * <p>
0641: * Remove the value from the head of the list. The tail of the list remains
0642: * in the model. Note that no changes are made to list cells that point to
0643: * this list cell as their tail. Immediately following a
0644: * <code>removeHead</code> operation, such lists will be in a non-valid
0645: * state.
0646: * </p>
0647: *
0648: * @return The remainder of the list after the head is removed (i.e. the
0649: * pre-removal list tail)
0650: */
0651: public RDFList removeHead() {
0652: if (s_checkValid) {
0653: checkValid();
0654: }
0655:
0656: checkNotNil("Attempted to delete the head of a nil list");
0657:
0658: RDFList tail = getTail();
0659: removeProperties();
0660:
0661: return tail;
0662: }
0663:
0664: /**
0665: * <p>Remove the given value from this list. If <code>val</code> does not occur in
0666: * the list, no action is taken. Since removing the head of the list will invalidate
0667: * the list head cell, in general the list must return the list that results from this
0668: * operation. However, in many cases the return value will be the same as the object
0669: * that this method is invoked on</p>
0670: *
0671: * @param val The value to be removed from the list
0672: * @return The resulting list, which will be the same as the current list in most
0673: * cases, except when <code>val</code> occurs at the head of the list.
0674: */
0675: public RDFList remove(RDFNode val) {
0676: if (s_checkValid) {
0677: checkValid();
0678: }
0679:
0680: RDFList prev = null;
0681: RDFList cell = this ;
0682: boolean searching = true;
0683:
0684: while (searching && !cell.isEmpty()) {
0685: if (cell.getHead().equals(val)) {
0686: // found the value to be removed
0687: RDFList tail = cell.getTail();
0688: if (prev != null) {
0689: prev.setTail(tail);
0690: }
0691:
0692: cell.removeProperties();
0693:
0694: // return this unless we have removed the head element
0695: return (prev == null) ? tail : this ;
0696: } else {
0697: // not found yet
0698: prev = cell;
0699: cell = cell.getTail();
0700: }
0701: }
0702:
0703: // not found
0704: return this ;
0705: }
0706:
0707: /**
0708: * <p>Deprecated. Since an <code>RDFList</code> does not behave like a Java container, it is not
0709: * the case that the contents of the list can be removed and the container filled with values
0710: * again. Therefore, this method name has been deprecated in favour of {@link #removeList}</p>
0711: * @deprecated Replaced by {@link #removeList}
0712: */
0713: public void removeAll() {
0714: removeList();
0715: }
0716:
0717: /**
0718: * <p>Remove all of the components of this list from the model. Once this operation
0719: * has completed, the {@link RDFList} resource on which it was called will no
0720: * longer be a resource in the model, so further methods calls on the list object
0721: * (for example, {@link #size} will fail. Due to restrictions on the encoding
0722: * of lists in RDF, it is not possible to perform an operation which empties a list
0723: * and then adds further values to that list. Client code wishing to perform
0724: * such an operation should do so in two steps: first remove the old list, then
0725: * create a new list with the new contents. It is important that RDF statements
0726: * that reference the old list (in the object position) be updated to point
0727: * to the newly created list.
0728: * Note that this
0729: * is operation is only removing the list cells themselves, not the resources
0730: * referenced by the list - unless being the object of an <code>rdf:first</code>
0731: * statement is the only mention of that resource in the model.</p>
0732: */
0733: public void removeList() {
0734: for (Iterator i = collectStatements().iterator(); i.hasNext();) {
0735: ((Statement) i.next()).remove();
0736: }
0737: }
0738:
0739: /**
0740: * <p>Answer a set of all of the RDF statements whose subject is one of the cells
0741: * of this list.</p>
0742: * @return A list of the statements that form the encoding of this list.
0743: */
0744: public Set collectStatements() {
0745: Set stmts = new HashSet();
0746: RDFList l = this ;
0747:
0748: do {
0749: // collect all statements of this list cell
0750: for (Iterator i = l.listProperties(); i.hasNext();) {
0751: stmts.add(i.next());
0752: }
0753:
0754: // move on to next cell
0755: l = l.getTail();
0756: } while (!l.isEmpty());
0757:
0758: return stmts;
0759: }
0760:
0761: /**
0762: * <p>
0763: * Answer an iterator over the elements of the list. Note that this iterator
0764: * does not take a snapshot of the list, so changes to the list statements
0765: * in the model while iterating will affect the behaviour of the iterator.
0766: * To get an iterator that is not affected by model changes, use {@link
0767: * #asJavaList}.
0768: * </p>
0769: *
0770: * @return A closable iterator over the elements of the list.
0771: */
0772: public ExtendedIterator iterator() {
0773: return new RDFListIterator(this );
0774: }
0775:
0776: /**
0777: * <p>
0778: * Answer the contents of this RDF list as a Java list of RDFNode values.
0779: * </p>
0780: *
0781: * @return The contents of this list as a Java List.
0782: */
0783: public List asJavaList() {
0784: List l = new ArrayList();
0785:
0786: for (Iterator i = iterator(); i.hasNext();) {
0787: l.add(i.next());
0788: }
0789:
0790: return l;
0791: }
0792:
0793: /**
0794: * <p>
0795: * Answer true if this list has the same elements in the same order as the
0796: * given list. Note that the standard <code>equals</code> test just tests
0797: * for equality of two given list cells. While such a test is sufficient
0798: * for many purposes, this test provides a broader equality definition, but
0799: * is correspondingly more expensive to test.
0800: * </p>
0801: *
0802: * @param list The list to test against
0803: * @return True if the given list and this list are the same length, and
0804: * contain equal elements in the same order.
0805: */
0806: public boolean sameListAs(RDFList list) {
0807: if (s_checkValid) {
0808: checkValid();
0809: }
0810:
0811: Resource r0 = this ;
0812: Resource r1 = list;
0813:
0814: Property head = listFirst();
0815: Property tail = listRest();
0816: Resource nil = listNil();
0817:
0818: // iterate through to the end of the list
0819: while (!(r0.equals(nil) || r1.equals(nil))) {
0820: RDFNode n0 = r0.getRequiredProperty(head).getObject();
0821: RDFNode n1 = r1.getRequiredProperty(head).getObject();
0822:
0823: if (n0 == null || !n0.equals(n1)) {
0824: // not equal at this position
0825: return false;
0826: } else {
0827: // advance along the lists
0828: r0 = r0.getRequiredProperty(tail).getResource();
0829: r1 = r1.getRequiredProperty(tail).getResource();
0830: }
0831: }
0832:
0833: // lists are equal if they terminate together
0834: return r0.equals(nil) && r1.equals(nil);
0835: }
0836:
0837: /**
0838: * <p>
0839: * Answer true lists are operating in strict mode, in which the
0840: * well- formedness of the list is checked at every operation.
0841: * </p>
0842: *
0843: * @return True lists are being strictly checked.
0844: */
0845: public boolean getStrict() {
0846: return s_checkValid;
0847: }
0848:
0849: /**
0850: * <p>
0851: * Set a flag to indicate whether to strictly check the well-formedness of
0852: * lists at each operation. Default false. Note that the flag that is
0853: * manipulated is actually a static: it applies to all lists. However, RDFList
0854: * is a Java interface, and Java does not permit static methods in interfaces.
0855: * </p>
0856: *
0857: * @param strict The <b>static</b> flag for whether lists will be checked strictly.
0858: */
0859: public void setStrict(boolean strict) {
0860: s_checkValid = strict;
0861: }
0862:
0863: /**
0864: * <p>
0865: * Answer true if the list is well-formed, by checking that each node is
0866: * correctly typed, and has a head and tail pointer from the correct
0867: * vocabulary.
0868: * </p>
0869: *
0870: * @return True if the list is well-formed.
0871: */
0872: public boolean isValid() {
0873: m_errorMsg = null;
0874:
0875: try {
0876: checkValid();
0877: } catch (InvalidListException e) {
0878: m_errorMsg = e.getMessage();
0879: }
0880:
0881: return (m_errorMsg == null);
0882: }
0883:
0884: /**
0885: * <p>
0886: * Answer the error message returned by the last failed validity check,
0887: * if any.
0888: * </p>
0889: *
0890: * @return The most recent error message, or null.
0891: */
0892: public String getValidityErrorMessage() {
0893: return m_errorMsg;
0894: }
0895:
0896: /**
0897: * <p>
0898: * Construct a new list cell with the given value and tail.
0899: * </p>
0900: *
0901: * @param value The value at the head of the new list cell
0902: * @param tail The tail of the list cell
0903: * @return A new list cell as a resource
0904: */
0905: public Resource newListCell(RDFNode value, Resource tail) {
0906: // Note: following the RDF WG decision, we no longer assert rdf:type rdf:List for list cells
0907: Resource cell = getModel().createResource();
0908:
0909: // set the head and tail
0910: cell.addProperty(listFirst(), value);
0911: cell.addProperty(listRest(), tail);
0912:
0913: return cell;
0914: }
0915:
0916: // Internal implementation methods
0917: //////////////////////////////////
0918:
0919: /**
0920: * <p>
0921: * Answer true if this is a valid list cell, which means either that it
0922: * is nil, or it has the appropriate type and a first and next relation.
0923: * Updated 17-06-2003: RDFCore last comments process has decided that the
0924: * rdf:type of a list is implied by the domain constraints on rdf:first
0925: * and rdf:rest, so no longer needs to be asserted directly. The test
0926: * for rdf:type has therefore been removed.
0927: * </p>
0928: *
0929: * @return True if this list cell passes basic validity checks
0930: */
0931: protected void checkValid() {
0932: if (!equals(listNil())) {
0933: // note that the rdf:type of list cells is now implied by the RDF M&S
0934: // so we don't check explicitly
0935: // checkValidProperty( RDF.type, listType() );
0936:
0937: checkValidProperty(listFirst(), null);
0938: checkValidProperty(listRest(), null);
0939: }
0940: }
0941:
0942: private void checkValidProperty(Property p, RDFNode expected) {
0943: int count = 0;
0944:
0945: for (StmtIterator j = getModel().listStatements(this , p,
0946: expected); j.hasNext(); j.next()) {
0947: count++;
0948: }
0949:
0950: // exactly one value is expected
0951: if (count == 0) {
0952: if (log.isDebugEnabled()) {
0953: log.debug("Failed validity check on " + toString());
0954: for (StmtIterator i = listProperties(); i.hasNext();) {
0955: log.debug(" this => " + i.next());
0956: }
0957: for (StmtIterator i = getModel().listStatements(null,
0958: null, this ); i.hasNext();) {
0959: log.debug(" => this " + i.next());
0960: }
0961: }
0962: throw new InvalidListException("List node "
0963: + toString()
0964: + " is not valid: it should have property "
0965: + p.toString()
0966: + (expected == null ? ""
0967: : (" with value " + expected)));
0968: } else if (count > 1) {
0969: throw new InvalidListException("List node " + toString()
0970: + " is not valid: it has more than one value for "
0971: + p.toString());
0972: }
0973: }
0974:
0975: /**
0976: * <p>
0977: * Check that the current list cell is not the nil list, and throw an empty
0978: * list exception if it is.
0979: * </p>
0980: *
0981: * @param msg The context message for the empty list exception
0982: * @exception EmptyListException if the list is the nil list
0983: */
0984: protected void checkNotNil(String msg) {
0985: if (isEmpty()) {
0986: throw new EmptyListException(msg);
0987: }
0988: }
0989:
0990: /**
0991: * <p>
0992: * Find and return an element of this list - either the last element before
0993: * the end of the list, or the i'th element from the front (starting from
0994: * zero). Note that this method assumes the pre-condition that
0995: * <code>this</code> is not the empty list.
0996: * </p>
0997: *
0998: * @param last If true, find the element whose tail is nil
0999: * @param index If <code>last</code> is false, find the index'th element
1000: * from the head of the list
1001: * @return The list cell
1002: * @exception ListIndexException if try to access an element beyond the end
1003: * of the list
1004: * @exception InvalidListException if try to find the end of a badly formed
1005: * list
1006: */
1007: protected RDFList findElement(boolean last, int index) {
1008: Property tail = listRest();
1009: Resource nil = listNil();
1010:
1011: Resource l = this ;
1012: int i = index;
1013: boolean found = (last && l.hasProperty(tail, nil))
1014: || (!last && (i == 0));
1015:
1016: // search for the element whose tail is nil, or whose index is now zero
1017: while (!found && !l.equals(nil)) {
1018: l = l.getRequiredProperty(tail).getResource();
1019: found = (last && l.hasProperty(tail, nil))
1020: || (!last && (--i == 0));
1021: }
1022:
1023: if (!found) {
1024: // premature end of list
1025: if (!last) {
1026: throw new ListIndexException("Tried to access element "
1027: + index
1028: + " that is beyond the length of the list");
1029: } else {
1030: throw new InvalidListException(
1031: "Could not find last element of list (suggests list is not valid)");
1032: }
1033: } else {
1034: return (RDFList) l.as(listAbstractionClass());
1035: }
1036: }
1037:
1038: /**
1039: * <p>
1040: * Create a copy of the list of nodes returned by an iterator.
1041: * </p>
1042: *
1043: * @param i An iterator of RDFNodes
1044: * @return A list formed from all of the nodes of i, in sequence
1045: */
1046: protected RDFList copy(Iterator i) {
1047: Resource list = null;
1048: Resource start = null;
1049:
1050: Property head = listFirst();
1051: Property tail = listRest();
1052: Resource cellType = listType();
1053:
1054: while (i.hasNext()) {
1055: // create a list cell to hold the next value from the existing list
1056: Resource cell = getModel().createResource(cellType);
1057: cell.addProperty(head, (RDFNode) i.next());
1058:
1059: // point the previous list cell to this one
1060: if (list != null) {
1061: list.addProperty(tail, cell);
1062: } else {
1063: // must be the first cell we're adding
1064: start = cell;
1065: }
1066:
1067: list = cell;
1068: }
1069:
1070: // finally close the list
1071: list.addProperty(tail, listNil());
1072:
1073: return (RDFList) start.as(listAbstractionClass());
1074: }
1075:
1076: /**
1077: * <p>
1078: * Helper method for setting the list tail, that assumes we have
1079: * a resource that is a list.
1080: * </p>
1081: *
1082: * @param root The resource representing the list cell we're setting the
1083: * tail of
1084: * @param tail The new tail for this list, as a resource.
1085: * @return The old tail, as a resource.
1086: */
1087: protected static Resource setTailAux(Resource root, Resource tail,
1088: Property pTail) {
1089: Statement current = root.getRequiredProperty(pTail);
1090: Resource oldTail = current.getResource();
1091:
1092: // out with the old, in with the new
1093: current.remove();
1094: root.addProperty(pTail, tail);
1095:
1096: return oldTail;
1097: }
1098:
1099: //==============================================================================
1100: // Inner class definitions
1101: //==============================================================================
1102:
1103: /**
1104: * <p>
1105: * Iterator that can step along chains of list pointers to the end of the
1106: * list.
1107: * </p>
1108: */
1109: protected class RDFListIterator extends NiceIterator {
1110: // Instance variables
1111:
1112: /** The current list node */
1113: protected RDFList m_head;
1114:
1115: /** The most recently seen node */
1116: protected RDFList m_seen = null;
1117:
1118: // Constructor
1119: //////////////
1120:
1121: /**
1122: * Construct an iterator for walking the list starting at head
1123: */
1124: protected RDFListIterator(RDFList head) {
1125: m_head = head;
1126: }
1127:
1128: // External contract methods
1129: ////////////////////////////
1130:
1131: /**
1132: * @see Iterator#hasNext
1133: */
1134: public boolean hasNext() {
1135: return !m_head.isEmpty();
1136: }
1137:
1138: /**
1139: * @see Iterator#next
1140: */
1141: public Object next() {
1142: m_seen = m_head;
1143: m_head = m_head.getTail();
1144:
1145: return m_seen.getHead();
1146: }
1147:
1148: /**
1149: * @see Iterator#remove
1150: */
1151: public void remove() {
1152: if (m_seen == null) {
1153: throw new IllegalStateException(
1154: "Illegal remove from list operator");
1155: }
1156:
1157: // will remove three statements in a well-formed list
1158: ((Resource) m_seen).removeProperties();
1159: m_seen = null;
1160: }
1161: }
1162: }
1163:
1164: /*
1165: (c) Copyright 2002, 2003, 2004, 2005, 2006, 2007, 2008 Hewlett-Packard Development Company, LP
1166: All rights reserved.
1167:
1168: Redistribution and use in source and binary forms, with or without
1169: modification, are permitted provided that the following conditions
1170: are met:
1171:
1172: 1. Redistributions of source code must retain the above copyright
1173: notice, this list of conditions and the following disclaimer.
1174:
1175: 2. Redistributions in binary form must reproduce the above copyright
1176: notice, this list of conditions and the following disclaimer in the
1177: documentation and/or other materials provided with the distribution.
1178:
1179: 3. The name of the author may not be used to endorse or promote products
1180: derived from this software without specific prior written permission.
1181:
1182: THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1183: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1184: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1185: IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
1186: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
1187: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1188: DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1189: THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1190: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
1191: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1192: */
|