/*
* Copyright (c) 2007-2009, 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 org.openrdf.repository.object.behaviours;
import static org.openrdf.query.QueryLanguage.SPARQL;
import info.aduna.iteration.CloseableIteration;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.openrdf.annotations.Precedes;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.query.BindingSet;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.Repository;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.RDFObject;
import org.openrdf.repository.object.exceptions.ObjectPersistException;
import org.openrdf.repository.object.exceptions.ObjectStoreException;
import org.openrdf.repository.object.traits.Mergeable;
import org.openrdf.repository.object.traits.Refreshable;
/**
* This behaviour provides a java.util.List interface for RDF containers.
*
* @author James Leigh
*/
@Precedes(RDFObjectImpl.class)
public abstract class RDFSContainer extends AbstractList<Object> implements
Refreshable, Mergeable, RDFObject {
private static final int UNKNOWN = -1;
private static final int BSIZE = 64;
private volatile int _size = UNKNOWN;
private List<Object[]> blocks = new ArrayList<Object[]>();
public void refresh() {
_size = UNKNOWN;
}
@Override
public Object get(int index) {
try {
int b = index / BSIZE;
Object[] block = getBlock(b);
if (block != null)
return block[index % BSIZE];
Object[] list = loadBlock(b);
assignBlock(b, list);
return list[index % BSIZE];
} catch (RepositoryException e) {
throw new ObjectStoreException(e);
} catch (QueryEvaluationException e) {
throw new ObjectStoreException(e);
}
}
@Override
public void add(int index, Object obj) {
ObjectConnection conn = getObjectConnection();
try {
boolean autoCommit = conn.isAutoCommit();
if (autoCommit)
conn.setAutoCommit(false);
try {
for (int i = size() - 1; i >= index; i--) {
replace(i + 1, get(i));
}
replace(index, obj);
if (_size > UNKNOWN)
_size++;
if (autoCommit)
conn.setAutoCommit(true);
} finally {
if (autoCommit && !conn.isAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
}
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
}
@Override
public Object set(int index, Object obj) {
ObjectConnection conn = getObjectConnection();
try {
boolean autoCommit = conn.isAutoCommit();
if (autoCommit)
conn.setAutoCommit(false);
try {
Object old = getAndSet(index, obj);
if (autoCommit)
conn.setAutoCommit(true);
return old;
} finally {
if (autoCommit && !conn.isAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
}
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
}
public void merge(Object source) {
if (source instanceof java.util.List) {
ObjectConnection conn = getObjectConnection();
try {
boolean autoCommit = conn.isAutoCommit();
if (autoCommit)
conn.setAutoCommit(false);
try {
java.util.List list = (java.util.List) source;
int size = list.size();
for (int i = 0, n = size; i < n; i++) {
Object value = list.get(i);
if (value != null) {
assign(i, value);
}
}
if (_size > UNKNOWN && _size < size)
_size = size;
if (autoCommit)
conn.setAutoCommit(true);
} finally {
if (autoCommit && !conn.isAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
}
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
}
}
@Override
public Object remove(int index) {
ObjectConnection conn = getObjectConnection();
try {
boolean autoCommit = conn.isAutoCommit();
if (autoCommit) {
conn.setAutoCommit(false);
}
Object obj = get(index);
int size = size();
for (int i = index; i < size - 1; i++) {
replace(i, get(i + 1));
}
URI pred = getMemberPredicate(size - 1);
conn.remove(getResource(), pred, null);
Object[] block = getBlock((size - 1) / BSIZE);
if (block != null) {
block[(size - 1) % BSIZE] = null;
}
if (_size > UNKNOWN)
_size--;
if (autoCommit) {
conn.setAutoCommit(true);
}
return obj;
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
}
@Override
public void clear() {
try {
ObjectConnection conn = getObjectConnection();
Resource resource = getResource();
int size = _size;
if (size < 0) {
size = (int) findSize();
}
for (int i = 0; i < size; i++) {
URI pred = getMemberPredicate(i);
conn.remove(resource, pred, null);
}
} catch (RepositoryException e) {
throw new ObjectPersistException(e);
}
}
@Override
public int size() {
try {
if (_size < 0) {
synchronized (this) {
if (_size < 0) {
int index = findSize();
_size = index;
}
}
}
return _size;
} catch (RepositoryException e) {
throw new ObjectStoreException(e);
}
}
@Override
public String toString() {
return super.toString();
}
private URI getMemberPredicate(int index) {
ObjectConnection conn = getObjectConnection();
Repository repository;
repository = conn.getRepository();
String uri = RDF.NAMESPACE + '_' + (index + 1);
return repository.getValueFactory().createURI(uri);
}
private int getIndex(URI pred) {
assert pred.stringValue().startsWith(RDF.NAMESPACE + '_');
return Integer.parseInt(pred.getLocalName().substring(1)) - 1;
}
private Object getAndSet(int index, Object o) throws RepositoryException {
if (o == null)
throw new NullPointerException();
URI pred = getMemberPredicate(index);
Object old = get(index);
ObjectConnection conn = getObjectConnection();
if (old != null) {
conn.remove(getResource(), pred, null);
}
conn.add(getResource(), pred, conn.addObject(o));
Object[] block = getBlock(index / BSIZE);
if (block != null) {
block[index % BSIZE] = o;
}
return old;
}
private void assign(int index, Object o) throws RepositoryException {
if (o == null)
throw new NullPointerException();
URI pred = getMemberPredicate(index);
Value newValue = getObjectConnection().addObject(o);
ObjectConnection conn = getObjectConnection();
conn.add(getResource(), pred, newValue);
clearBlock(index / BSIZE);
}
private void replace(int index, Object o) throws RepositoryException {
if (o == null)
throw new NullPointerException();
URI pred = getMemberPredicate(index);
ObjectConnection conn = getObjectConnection();
Value newValue = getObjectConnection().addObject(o);
boolean autoCommit = conn.isAutoCommit();
if (autoCommit)
conn.setAutoCommit(false);
try {
conn.remove(getResource(), pred, null);
conn.add(getResource(), pred, newValue);
if (autoCommit)
conn.setAutoCommit(true);
} finally {
if (autoCommit && !conn.isAutoCommit()) {
conn.rollback();
conn.setAutoCommit(true);
}
}
Object[] block = getBlock(index / BSIZE);
if (block != null) {
block[index % BSIZE] = o;
}
}
private int findSize() throws RepositoryException {
CloseableIteration<? extends Statement, RepositoryException> iter;
HashSet<URI> set = new HashSet<URI>();
ObjectConnection conn = getObjectConnection();
iter = conn.getStatements(getResource(), null, null);
try {
while (iter.hasNext()) {
set.add(iter.next().getPredicate());
}
} finally {
iter.close();
}
int index = 0;
while (set.contains(getMemberPredicate(index)))
index++;
return index;
}
private synchronized Object[] getBlock(int b) {
if (blocks.size() > b)
return blocks.get(b);
return null;
}
private synchronized void assignBlock(int b, Object[] list) {
while (blocks.size() <= b) {
blocks.add(null);
}
blocks.set(b, list);
}
private synchronized void clearBlock(int b) {
if (blocks.size() > b) {
blocks.set(b, null);
}
}
private Object[] loadBlock(int b) throws RepositoryException, QueryEvaluationException {
TupleQuery query = createBlockQuery(b);
TupleQueryResult result = query.evaluate();
BindingSet bindings = result.next();
ObjectConnection con = getObjectConnection();
Object[] list = new Object[BSIZE];
while (bindings != null) {
URI pred = (URI) bindings.getValue("pred");
int idx = getIndex(pred);
Value value = bindings.getValue("value");
Set<URI> types = new HashSet<URI>(4);
do {
Value c = bindings.getValue("value_class");
if (c instanceof URI) {
types.add((URI) c);
}
bindings = result.hasNext() ? result.next() : null;
} while (bindings != null && pred.equals(bindings.getValue("pred")));
int i = idx % BSIZE;
if (value instanceof Literal) {
list[i] = con.getObject((Literal) value);
} else {
list[i] = con.getObject(types, (Resource) value);
}
}
return list;
}
private TupleQuery createBlockQuery(int b) throws RepositoryException {
StringBuilder sb = new StringBuilder();
sb.append("SELECT ?pred ?value ?value_class\n");
sb.append("WHERE { $self ?pred ?value\n");
sb.append("OPTIONAL { ?value a ?value_class }\n");
sb.append("FILTER (");
for (int i = b * BSIZE, n = b * BSIZE + BSIZE; i < n; i++) {
sb.append("?pred = <");
sb.append(RDF.NAMESPACE);
sb.append("_");
sb.append((i + 1));
sb.append(">");
if (i + 1 < n) {
sb.append(" || ");
}
}
sb.append(")}\n");
ObjectConnection con = getObjectConnection();
try {
TupleQuery query = con.prepareTupleQuery(SPARQL, sb.toString());
query.setBinding("self", getResource());
return query;
} catch (MalformedQueryException e) {
throw new RepositoryException(e);
}
}
}