/* * Copyright (c) 2007, 2010, James Leigh All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * - 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. * - Neither the name of the openrdf.org nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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. * */ package net.enilink.komma.em.internal.behaviours; import java.util.AbstractSequentialList; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import net.enilink.commons.iterator.IExtendedIterator; import net.enilink.commons.util.IPartialOrderProvider; import net.enilink.commons.util.LinearExtension; import net.enilink.composition.annotations.Iri; import net.enilink.composition.annotations.Precedes; import net.enilink.composition.properties.traits.Mergeable; import net.enilink.composition.properties.traits.Refreshable; import net.enilink.composition.traits.Behaviour; import net.enilink.komma.core.IEntity; import net.enilink.komma.core.IGraph; import net.enilink.komma.core.IReference; import net.enilink.komma.core.IStatement; import net.enilink.komma.core.IValue; import net.enilink.komma.core.Initializable; import net.enilink.komma.core.KommaException; import net.enilink.komma.core.Statement; import net.enilink.komma.core.URI; import net.enilink.komma.em.concepts.ResourceSupport; import net.enilink.komma.em.util.ISparqlConstants; import net.enilink.vocab.rdf.RDF; /** * Java instance for rdf:List as a familiar interface to manipulate this List. * This implementation can only be modified when in autoCommit (autoFlush), or * when read uncommitted is supported. */ @Iri("http://www.w3.org/1999/02/22-rdf-syntax-ns#List") @Precedes(ResourceSupport.class) public abstract class RDFList extends AbstractSequentialList<Object> implements java.util.List<Object>, Refreshable, Mergeable, Initializable, IEntity, Behaviour<IEntity> { // use system property "net.enilink.usePropertyPaths=false" to disable private static final boolean INIT_CACHE_WITH_PROPERTY_PATH = !"false" .equalsIgnoreCase(System .getProperty("net.enilink.usePropertyPaths")); private static final Item NIL_ITEM = new Item(RDF.NIL, null, null); private static class Item { final IReference self; final IValue first; final IReference rest; Item(IReference self, IValue first, IReference rest) { this.self = self; this.first = first; this.rest = rest; } } private volatile int size = -1; private volatile List<Item> cache; void addStatement(IReference subj, URI pred, Object obj) { if (obj == null) { return; } getEntityManager().add(new Statement(subj, pred, obj)); } private List<Item> getCache() { List<Item> localCache = cache; if (localCache == null && INIT_CACHE_WITH_PROPERTY_PATH) { final Map<IReference, Item> items = new LinkedHashMap<IReference, Item>(); IExtendedIterator<Object[]> results = getEntityManager() .createQuery( ISparqlConstants.PREFIX + "SELECT ?item ?first ?rest WHERE { { BIND (?self as ?item) } UNION { ?self rdf:rest+ ?item } . ?item rdf:first ?first . OPTIONAL { ?item rdf:rest ?rest } }") .restrictResultType("first", IValue.class) .restrictResultType("item", IReference.class) .restrictResultType("rest", IReference.class) .setParameter("self", getBehaviourDelegate()) .evaluate(Object[].class); for (Object[] result : results) { IReference self = (IReference) result[0]; items.put(self, new Item(self, (IValue) result[1], (IReference) result[2])); } if (!items.containsKey(RDF.NIL)) { items.put(RDF.NIL, NIL_ITEM); } if (items.size() <= 2) { localCache = new ArrayList<Item>(items.values()); } else { localCache = new LinearExtension<Item>( new IPartialOrderProvider<Item>() { @Override public Collection<Item> getElements() { return items.values(); } @Override public Collection<Item> getSuccessors(Item element) { if (element.self.equals(RDF.NIL)) { return Collections.emptyList(); } if (element.rest != null) { return Collections.singleton(items .get(element.rest)); } return Collections.singleton(NIL_ITEM); } }).createLinearExtension(); } } return cache = localCache; } private Item getItem(IReference self) { if (self == null || RDF.NIL.equals(self)) { return NIL_ITEM; } IExtendedIterator<Object[]> results = getEntityManager() .createQuery( ISparqlConstants.PREFIX + "SELECT ?first ?rest WHERE { { ?item rdf:first ?first } OPTIONAL { ?item rdf:rest ?rest } }") .restrictResultType("first", IValue.class) .restrictResultType("rest", IReference.class) .setParameter("item", self).evaluate(Object[].class); try { if (results.hasNext()) { Object[] item = results.next(); return new Item(self, (IValue) item[0], (IReference) item[1]); } return new Item(self, null, null); } finally { results.close(); } } /** * Initialize this list with data contained in <code>graph</code>. */ @Override public void init(IGraph graph) { if (graph != null && cache == null) { IReference list = getReference(); List<Item> items = new ArrayList<Item>(); Set<IReference> seen = new HashSet<IReference>(); while (list != null && seen.add(list) && !RDF.NIL.equals(list)) { IReference rest = null; IValue first = null; for (IStatement stmt : graph.filter(list, null, null)) { if (RDF.PROPERTY_FIRST.equals(stmt.getPredicate())) { first = (IValue) stmt.getObject(); } else if (RDF.PROPERTY_REST.equals(stmt.getPredicate())) { if (stmt.getObject() instanceof IReference) { rest = (IReference) stmt.getObject(); } else { // invalid list data break; } } } if (first != null) { // eagerly initialize the item // getEntityManager().toInstance(first, null, graph); items.add(new Item(list, first, rest)); list = rest; } } items.add(NIL_ITEM); // only initialize cache if list has been fully traversed if (list == null || RDF.NIL.equals(list)) { cache = items; } } } @Override public ListIterator<Object> listIterator(final int index) { final List<Item> cacheLocal = getCache(); // for thread-safety final boolean cached = cacheLocal != null; return new ListIterator<Object>() { Item next; Item item; private int nextIndex = cached ? index : 0; private ArrayList<Item> items = cached ? new ArrayList<Item>( cacheLocal) : new ArrayList<Item>(); { if (cached) { if (index - 1 >= 0) { item = items.get(index - 1); } } else { // retrieve next element step by step until index is // achieved for (int i = 0; i < index; i++) { next(); } } } public void add(Object o) { boolean active = getEntityManager().getTransaction().isActive(); try { if (!active) { getEntityManager().getTransaction().begin(); } if (getReference().equals(RDF.NIL)) { // size == 0 throw new KommaException( "cannot add a value to the nil list"); /* * list = _id = getValueFactory().createBNode(); * addStatement(list, RDF.FIRST, * SesameProperty.createValue(List.this, o)); * addStatement(list, RDF.REST, RDF.NIL); */ } if (item == null) { Item thisItem = getItem(getBehaviourDelegate()); if (thisItem.first == null) { // size == 0 IValue oValue = getEntityManager().toValue(o); item = new Item(getBehaviourDelegate(), oValue, RDF.NIL); addStatement(item.self, RDF.PROPERTY_FIRST, oValue); addStatement(item.self, RDF.PROPERTY_REST, RDF.NIL); } else { // index = 0 IReference newList = getEntityManager().create(); addStatement(newList, RDF.PROPERTY_FIRST, thisItem.first); addStatement(newList, RDF.PROPERTY_REST, thisItem.rest); removeStatements(getBehaviourDelegate(), RDF.PROPERTY_FIRST, thisItem.first); removeStatements(getBehaviourDelegate(), RDF.PROPERTY_REST, thisItem.rest); addStatement(getBehaviourDelegate(), RDF.PROPERTY_FIRST, o); addStatement(getBehaviourDelegate(), RDF.PROPERTY_REST, newList); } } else if (!item.self.equals(RDF.NIL)) { IReference newList = getEntityManager().create(); removeStatements(item.self, RDF.PROPERTY_REST, item.rest); addStatement(item.self, RDF.PROPERTY_REST, newList); addStatement(newList, RDF.PROPERTY_FIRST, o); addStatement(newList, RDF.PROPERTY_REST, item.rest); item = new Item(item.self, getEntityManager() .toValue(o), newList); } else { // index == size throw new NoSuchElementException(); } if (!active) { getEntityManager().getTransaction().commit(); } refresh(); } catch (KommaException e) { if (!active) { getEntityManager().getTransaction().rollback(); } throw e; } } public boolean hasNext() { if (next != null) { // next != RDF.NIL return next.first != null; } else if (nextIndex < items.size()) { next = items.get(nextIndex); } else if (!cached) { if (item == null) { next = getItem(getBehaviourDelegate()); } else { next = getItem(item.rest); } items.add(next); if (next.first == null) { cache = new ArrayList<Item>(items); } } return next != null && next.first != null; } public boolean hasPrevious() { return nextIndex > 0; } public Object next() { if (hasNext()) { nextIndex++; item = next; next = null; return getEntityManager().toInstance(item.first); } throw new NoSuchElementException(); } public int nextIndex() { return nextIndex; } public Object previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } nextIndex--; item = items.get(nextIndex - 1); IValue first = item.first; if (first == null) throw new NoSuchElementException(); return getEntityManager().toInstance(first); } public int previousIndex() { return nextIndex - 2; } public void remove() { if (item == null) { throw new IllegalStateException( "next() has not yet been called"); } boolean active = getEntityManager().getTransaction().isActive(); try { if (!active) { getEntityManager().getTransaction().begin(); } if (nextIndex == 1) { // remove index == 0 removeStatements(item.self, RDF.PROPERTY_FIRST, item.first); Item next = getItem(item.rest); if (next != null) { removeStatements(item.self, RDF.PROPERTY_REST, next.self); if (next.first != null) { removeStatements(next.self, RDF.PROPERTY_FIRST, next.first); addStatement(item.self, RDF.PROPERTY_FIRST, next.first); } if (next.rest != null) { removeStatements(next.self, RDF.PROPERTY_REST, next.rest); addStatement(item.self, RDF.PROPERTY_REST, next.rest); } } item = new Item(item.self, next != null ? next.first : null, next != null ? next.rest : null); } else { // remove index > 0 Item removedList = item; // replace previous item in list Item prev = items.get(nextIndex - 2); items.set(nextIndex - 2, new Item(prev.self, prev.first, prev.rest)); // remove current item from list items.remove(nextIndex - 1); removeStatements(removedList.self, RDF.PROPERTY_FIRST, removedList.first); removeStatements(removedList.self, RDF.PROPERTY_REST, removedList.rest); removeStatements(prev.self, RDF.PROPERTY_REST, removedList.self); addStatement(prev.self, RDF.PROPERTY_REST, removedList.rest); item = prev; } next = null; hasNext(); // invalidate iterator until call to next() item = null; if (!active) { getEntityManager().getTransaction().commit(); } refresh(); } catch (KommaException e) { if (!active) { getEntityManager().getTransaction().rollback(); } throw e; } } public void set(Object o) { boolean active = getEntityManager().getTransaction().isActive(); try { if (!active) { getEntityManager().getTransaction().begin(); } if (getBehaviourDelegate().equals(RDF.NIL)) { // size == 0 throw new NoSuchElementException(); } else if (item.self.equals(RDF.NIL)) { // index = size throw new NoSuchElementException(); } else { removeStatements(item.self, RDF.PROPERTY_FIRST, item.first); if (o != null) { addStatement(item.self, RDF.PROPERTY_FIRST, o); } } if (!active) { getEntityManager().getTransaction().commit(); } refresh(); } catch (KommaException e) { if (!active) { getEntityManager().getTransaction().rollback(); } throw e; } } }; } public void merge(Object source) { if (source instanceof java.util.List<?>) { clear(); // works also for the read uncommitted isolation level Iterator<?> it = ((java.util.List<?>) source).iterator(); if (it.hasNext()) { IReference current = getBehaviourDelegate(); addStatement(current, RDF.PROPERTY_FIRST, it.next()); while (it.hasNext()) { IReference last = current; current = getEntityManager().create(); addStatement(last, RDF.PROPERTY_REST, current); addStatement(current, RDF.PROPERTY_FIRST, it.next()); } addStatement(current, RDF.PROPERTY_REST, RDF.NIL); } } } @Override public void refresh() { size = -1; cache = null; } void removeStatements(IReference subj, URI pred, Object obj) { getEntityManager().remove(new Statement(subj, pred, obj)); } @Override public int size() { if (this.size < 0) { synchronized (this) { if (this.size < 0) { List<Item> items = getCache(); if (items != null) { this.size = Math.max(0, items.size() - 1); } else { items = new ArrayList<Item>(); Item item = getItem(getBehaviourDelegate()); items.add(item); int size; for (size = 0; item != null && item.first != null && !item.self.equals(RDF.NIL); size++) { item = getItem(item.rest); items.add(item); } this.cache = items; this.size = size; } } } } return size; } @Override public String toString() { return super.toString(); } }