/* * $Header$ * $Revision: 624 $ * $Date: 2006-06-24 21:02:12 +1000 (Sat, 24 Jun 2006) $ * * ==================================================================== * * The Apache Software License, Version 1.1 * * Copyright (c) 2003 The JRDF Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, if * any, must include the following acknowlegement: * "This product includes software developed by the * the JRDF Project (http://jrdf.sf.net/)." * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * * 4. The names "The JRDF Project" and "JRDF" must not be used to endorse * or promote products derived from this software without prior written * permission. For written permission, please contact * newmana@users.sourceforge.net. * * 5. Products derived from this software may not be called "JRDF" * nor may "JRDF" appear in their names without prior written * permission of the JRDF Project. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the JRDF Project. For more * information on JRDF, please see <http://jrdf.sourceforge.net/>. */ package org.jrdf.graph.mem; import org.jrdf.graph.*; import org.jrdf.util.ClosableIterator; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.*; /** * A memory based RDF Graph. * * @author <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a> * @author Andrew Newman * * @version $Revision: 624 $ */ public class GraphImpl implements Graph, Serializable { /** * Allow newer compiled version of the stub to operate when changes * have not occurred with the class. * NOTE : update this serialVersionUID when a method or a public member is * deleted. */ private static final long serialVersionUID = -3066836734480153804L; // indexes are mapped as: // s -> {p -> {set of o}} // This is defined in the private add() method /** * First index. */ private Map<Long,Map<Long,Set<Long>>> index012; /** * Second index. */ private transient Map<Long,Map<Long,Set<Long>>> index120; /** * Third index. */ private transient Map<Long,Map<Long,Set<Long>>> index201; /** * Graph Element Factory. This caches the node factory. */ private transient GraphElementFactoryImpl elementFactory; /** * Triple Element Factory. This caches the element factory. */ private transient TripleFactoryImpl tripleFactory; /** * Default constructor. * * @throws GraphException There was an error creating the factory. */ public GraphImpl() throws GraphException { init(); } /** * Initialization method used by the constructor and the deserializer. * * @throws GraphException There was an error creating the factory. */ private void init() throws GraphException { // protect each field allocation with a test for null if (null == index012) { index012 = new HashMap<Long, Map<Long, Set<Long>>>(); } if (null == index120) { index120 = new HashMap<Long, Map<Long, Set<Long>>>(); } if (null == index201) { index201 = new HashMap<Long, Map<Long, Set<Long>>>(); } if (null == elementFactory) { try { elementFactory = new GraphElementFactoryImpl(this); } catch (TripleFactoryException e) { throw new GraphException(e); } } if (null == tripleFactory) { tripleFactory = new TripleFactoryImpl(this, elementFactory); } } /** * Test the graph for the occurrence of a statement. A null value for any * of the parts of a triple are treated as unconstrained, any values will be * returned. * @param subject The subject to find or null to indicate any subject. * @param predicate The predicate to find or null to indicate any predicate. * @param object The object to find or null to indicate any object. * @return True if the statement is found in the model, otherwise false. * @throws GraphException If there was an error accessing the graph. */ public boolean contains(SubjectNode subject, PredicateNode predicate, ObjectNode object) throws GraphException { // Get local node values Long[] values; try { values = localize(subject, predicate, object); } catch (GraphException ge) { // Graph exception on localize implies that the subject, predicate or // object did not exist in the graph. return false; } // Return true if all are null and size is greater than zero. if (null == subject && null == predicate && null == object) { return 0 < index012.size(); } // Subject null. if (null == subject) { // Predicate null - was null, null obj. if (null == predicate) { Map<Long,Set<Long>> objIndex = index201.get(values[2]); return null != objIndex; } else { // Predicate is not null. Could be null, pred, null or null, pred, obj. Map<Long,Set<Long>> predIndex = index120.get(values[1]); // If predicate not found return false. if (null == predIndex) return false; // If the object is null and we found the predicate return true. if (null == object) { return true; } else { // Was null, pred, obj Set<Long> group = predIndex.get(values[2]); return null != group; } } } else { // Subject is not null. Map<Long,Set<Long>> subIndex = index012.get(values[0]); // If subject not found return false. if (null == subIndex) return false; // Predicate null. Could be subj, null, null or subj, null, obj. if (null == predicate) { // If object null then we've found all we need to find. if (null == object) { return true; } else { // If the object is not null we need to find subj, null, obj Map<Long,Set<Long>> objIndex = index201.get(values[2]); if (null == objIndex) return false; Set<Long> group = objIndex.get(values[0]); return null != group; } } else { // Predicate not null. Could be subj, pred, obj or subj, pred, null. // look up the predicate Set<Long> group = subIndex.get(values[1]); if (null == group) return false; // Object not null. Must be subj, pred, obj. if (null != object) { return group.contains(values[2]); } else { // Was subj, pred, null - must be true if we get this far. return true; } } } } /** * Test the graph for the occurrence of the triple. A null value for any * of the parts of a triple are treated as unconstrained, any values will be * returned. * * @param triple The triple to find. * @return True if the triple is found in the graph, otherwise false. * @throws GraphException If there was an error accessing the graph. */ public boolean contains(Triple triple) throws GraphException { return contains(triple.getSubject(), triple.getPredicate(), triple.getObject()); } /** * Returns an iterator to a set of statements that match a given subject, * predicate and object. A null value for any of the parts of a triple are * treated as unconstrained, any values will be returned. * @param subject The subject to find or null to indicate any subject. * @param predicate The predicate to find or null to indicate any predicate. * @param object ObjectNode The object to find or null to indicate any object. * @throws GraphException If there was an error accessing the graph. */ public ClosableIterator<Triple> find(SubjectNode subject, PredicateNode predicate, ObjectNode object) throws GraphException { // Get local node values Long[] values; try { values = localize(subject, predicate, object); } catch (GraphException ge) { // A graph exception implies that the subject, predicate or object does // not exist in the graph. return new EmptyClosableIterator<Triple>(); } // test which index to use if (null != subject) { // test for {sp*} if (null != predicate) { // test for {spo} if (null != object) { // got {spo} return new ThreeFixedIterator(this, subject, predicate, object); } else { // got {sp*} return new TwoFixedIterator(index012, 0, values[0], values[1], elementFactory, new GraphHandler012(this), index012.get(values[0])); } } else { // test for {s**} if (null == object) { return new OneFixedIterator(index012, 0, values[0], elementFactory, new GraphHandler012(this), index012.get(values[0])); } // {s*o} so fall through } } if (null != predicate) { // test for {*po} if (null != object) { return new TwoFixedIterator(index120, 2, values[1], values[2], elementFactory, new GraphHandler120(this), index120.get(values[1])); } else { // test for {*p*}. {sp*} should have been picked up above assert null == subject; return new OneFixedIterator(index120, 2, values[1], elementFactory, new GraphHandler120(this), index120.get(values[1])); } } if (null != object) { // test for {s*o} if (null != subject) { return new TwoFixedIterator(index201, 1, values[2], values[0], elementFactory, new GraphHandler201(this), index201.get(values[2])); } else { // test for {**o}. {*po} should have been picked up above assert null == predicate; return new OneFixedIterator(index201, 1, values[2], elementFactory, new GraphHandler201(this), index201.get(values[2])); } } // {***} so return entire graph return new GraphIterator(index012.entrySet().iterator(), elementFactory, new GraphHandler012(this)); } /** * Returns an iterator to a set of statements that match a given subject, * predicate and object. A null value for any of the parts of a triple are * treated as unconstrained, any values will be returned. * @param triple The triple to find. * @throws GraphException If there was an error accessing the graph. */ public ClosableIterator<Triple> find(Triple triple) throws GraphException { return find(triple.getSubject(), triple.getPredicate(), triple.getObject()); } /** * Adds a triple to the graph. * * @param subject The subject. * @param predicate The predicate. * @param object The object. * @throws GraphException If the statement can't be made. */ public void add(SubjectNode subject, PredicateNode predicate, ObjectNode object) throws GraphException { // Get local node values also tests that it's a valid subject, predicate and object. Long[] values = localize(subject, predicate, object); // add to the first index add(index012, values[0], values[1], values[2]); // try and back out changes if an insertion fails try { // add to the second index add(index120, values[1], values[2], values[0]); try { // add to the third index add(index201, values[2], values[0], values[1]); } catch (GraphException e) { removeFrom120(values[1], values[2], values[0]); throw e; } } catch (GraphException e) { removeFrom012(values[0], values[1], values[2]); throw e; } } /** * Adds a triple to the graph. * @param triple The triple. * @throws GraphException If the statement can't be made. */ public void add(Triple triple) throws GraphException { add(triple.getSubject(), triple.getPredicate(), triple.getObject()); } /** * Adds an iterator containing triples into the graph. * @param triples The triple iterator. * @throws GraphException If the statements can't be made. */ public void add(Iterator<Triple> triples) throws GraphException { while (triples.hasNext()) add(triples.next()); } /** * Removes a triple from the graph. * @param subject The subject. * @param predicate The predicate. * @param object The object. * @throws GraphException If there was an error revoking the statement, for * example if it didn't exist. */ public void remove(SubjectNode subject, PredicateNode predicate, ObjectNode object) throws GraphException { // Get local node values also tests that it's a valid subject, predicate // and object. Long[] values = localize(subject, predicate, object); removeFrom012(values[0], values[1], values[2]); // if the first one succeeded then try and attempt removal on both of the others boolean success = false; try { removeFrom120(values[1], values[2], values[0]); success = true; } finally { try { removeFrom201(values[2], values[0], values[1]); } catch (GraphException e) { if (success) throw e; // Only re-throw if no other exception happened first. No logger, so ignore otherwise. } } } /** * Removes a triple from the graph. * @param triple The triple. * @throws GraphException If there was an error revoking the statement, for * example if it didn't exist. */ public void remove(Triple triple) throws GraphException { remove(triple.getSubject(), triple.getPredicate(), triple.getObject()); } /** * Removes an iterator containing triples from the graph. * @param triples The triple iterator. * @throws GraphException If the statements can't be revoked. */ public void remove(Iterator<Triple> triples) throws GraphException { while (triples.hasNext()) remove(triples.next()); } /** * Returns the node factory for the graph, or creates one. * @return the node factory for the graph, or creates one. */ public GraphElementFactory getElementFactory() { return elementFactory; } /** * Returns the triple factory for the graph, or creates one. * @return the triple factory for the graph, or creates one. */ public TripleFactory getTripleFactory() { return tripleFactory; } /** * Returns the number of triples in the graph. * @return the number of triples in the graph. */ public long getNumberOfTriples() throws GraphException { long size = 0; // go over the index map Iterator<Map<Long,Set<Long>>> first = index012.values().iterator(); while (first.hasNext()) { // go over the sub indexes Iterator<Set<Long>> second = first.next().values().iterator(); while (second.hasNext()) { // accumulate the sizes of the groups size += second.next().size(); } } return size; } /** * Returns true if the graph is empty i.e. the number of triples is 0. * @return true if the graph is empty i.e. the number of triples is 0. */ public boolean isEmpty() throws GraphException { return index012.isEmpty(); } /** * Closes any underlying resources used by this graph. */ public void close() { // no op } /** * Adds a triple to a single index. * @param first The first node. * @param second The second node. * @param third The last node. * @throws GraphException If there was an error adding the statement. */ private Long[] localize(Node first, Node second, Node third) throws GraphException { Long[] localValues = new Long[3]; // convert the nodes to local memory nodes for convenience if (null != first) { if (first instanceof BlankNodeImpl) { localValues[0] = ((BlankNodeImpl) first).getId(); } else { localValues[0] = elementFactory.getNodeIdByString(String.valueOf(first)); } if (null == localValues[0]) { throw new GraphException("Subject does not exist in graph"); } } if (null != second) { localValues[1] = elementFactory.getNodeIdByString(String.valueOf(second)); if (null == localValues[1]) { throw new GraphException("Predicate does not exist in graph"); } } if (null != third) { if (third instanceof BlankNodeImpl) { localValues[2] = ((BlankNodeImpl)third).getId(); } else if (third instanceof LiteralImpl) { localValues[2] = elementFactory.getNodeIdByString(((LiteralImpl)third).getEscapedForm()); } else { localValues[2] = elementFactory.getNodeIdByString(String.valueOf(third)); } if (null == localValues[2]) { throw new GraphException("Object does not exist in graph"); } } return localValues; } /** * Adds a triple to a single index. This method defines the internal structure. * @param index The index to add the statement to. * @param first The first node id. * @param second The second node id. * @param third The last node id. * @throws GraphException If there was an error adding the statement. */ private void add(Map<Long,Map<Long,Set<Long>>> index, Long first, Long second, Long third) throws GraphException { // find the sub index Map<Long,Set<Long>> subIndex = index.get(first); // check that the subindex exists if (null == subIndex) { // no, so create it and add it to the index subIndex = new HashMap<Long,Set<Long>>(); index.put(first, subIndex); } // find the final group Set<Long> group = subIndex.get(second); // check that the group exists if (null == group) { // no, so create it and add it to the subindex group = new HashSet<Long>(); subIndex.put(second, group); } // Add the final node to the group group.add(third); } void removeFrom012(Long first, Long second, Long third) throws GraphException { remove(index012, first, second, third); } void removeFrom120(Long first, Long second, Long third) throws GraphException { remove(index120, first, second, third); } void removeFrom201(Long first, Long second, Long third) throws GraphException { remove(index201, first, second, third); } /** * Removes a triple from a single index. * @param index The index to remove the statement from. * @param first The first node. * @param second The second node. * @param third The last node. * @throws GraphException If there was an error revoking the statement, for * example if it didn't exist. */ private void remove(Map<Long,Map<Long,Set<Long>>> index, Long first, Long second, Long third) throws GraphException { // find the sub index Map<Long,Set<Long>> subIndex = index.get(first); // check that the subindex exists if (null == subIndex) { throw new GraphException("Unable to remove nonexistent statement"); } // find the final group Set<Long> group = subIndex.get(second); // check that the group exists if (null == group) { throw new GraphException("Unable to remove nonexistent statement"); } // remove from the group, report error if it didn't exist if (!group.remove(third)) { throw new GraphException("Unable to remove nonexistent statement"); } // clean up the graph if (group.isEmpty()) { subIndex.remove(second); if (subIndex.isEmpty()) index.remove(first); } } /** * Serializes the current object to a stream. * @param out The stream to write to. * @throws IOException If an I/O error occurs while writing. */ private void writeObject(ObjectOutputStream out) throws IOException { // write out the first index with the default writer out.defaultWriteObject(); // write all the nodes as well out.writeObject(elementFactory.getNodePool().toArray()); // TODO: Consider writing these nodes individually. Converting to an array // may take up unnecessary memory } /** * Deserializes an object from a stream. * @param in The stream to read from. * @throws IOException If an I/O error occurs while reading. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { // read in the first index with the default reader in.defaultReadObject(); // initialize the fields not yet done by the constructor try { init(); } catch (GraphException e) { throw new ClassNotFoundException("Unable to initialize a new graph", e); } // read all the nodes as well Object[] nodes = (Object[])in.readObject(); try { // test node factory creation in case the constructor did it if (null == elementFactory) elementFactory = new GraphElementFactoryImpl(this); } catch (TripleFactoryException e) { throw new ClassNotFoundException("Unable to build NodeFactory", e); } // populate the node factory with these nodes for (int n = 0; n < nodes.length; n++) elementFactory.registerNode((MemNode)nodes[n]); // fill in the other indexes try { // iterate over the first column Iterator<Map.Entry<Long,Map<Long,Set<Long>>>> firstEntries = index012.entrySet().iterator(); while (firstEntries.hasNext()) { Map.Entry<Long,Map<Long,Set<Long>>> firstEntry = firstEntries.next(); Long first = firstEntry.getKey(); // now iterate over the second column Iterator<Map.Entry<Long,Set<Long>>> secondEntries = firstEntry.getValue().entrySet().iterator(); while (secondEntries.hasNext()) { Map.Entry<Long,Set<Long>> secondEntry = secondEntries.next(); Long second = secondEntry.getKey(); // now iterate over the third column Iterator<Long> thirdValues = secondEntry.getValue().iterator(); while (thirdValues.hasNext()) { Long third = thirdValues.next(); // now add the row to the other two indexes add(index120, second, third, first); add(index201, third, first, second); } } } } catch (GraphException e) { throw new ClassNotFoundException("Unable to add to a graph index", e); } } /** * Debug method to see the current state of the first index. * @param index The index to display */ static void dumpIndex(Map<Long,Map<Long,Set<Long>>> index) { Iterator<Map.Entry<Long,Map<Long,Set<Long>>>> iterator = index.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Long,Map<Long,Set<Long>>> subjectEntry = iterator.next(); Long subject = (Long) subjectEntry.getKey(); int sWidth = subject.toString().length() + 5; System.out.print(subject.toString() + " --> "); Map<Long,Set<Long>> secondIndex = subjectEntry.getValue(); if (secondIndex.isEmpty()) { System.out.println("X"); continue; } boolean firstPredicate = true; Iterator<Map.Entry<Long,Set<Long>>> predIterator = secondIndex.entrySet().iterator(); while (predIterator.hasNext()) { Map.Entry<Long,Set<Long>> predicateEntry = predIterator.next(); Long predicate = predicateEntry.getKey(); int pWidth = predicate.toString().length() + 5; if (!firstPredicate) { StringBuilder space = new StringBuilder(sWidth); space.setLength(sWidth); for (int c = 0; c < sWidth; c++) space.setCharAt(c, ' '); System.out.print(space.toString()); } else { firstPredicate = false; } System.out.print(predicate.toString() + " --> "); Set<Long> thirdIndex = predicateEntry.getValue(); if (thirdIndex.isEmpty()) { System.out.println("X"); continue; } boolean firstObject = true; Iterator<Long> objIterator = thirdIndex.iterator(); while (objIterator.hasNext()) { Long object = objIterator.next(); if (!firstObject) { StringBuilder sp2 = new StringBuilder(sWidth + pWidth); sp2.setLength(sWidth + pWidth); for (int d = 0; d < sWidth + pWidth; d++) sp2.setCharAt(d, ' '); System.out.print(sp2.toString()); } else { firstObject = false; } System.out.println(object); } } } } }