org.eclipse.rdf4j.model.impl.TreeModel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rdf4j-model Show documentation
Show all versions of rdf4j-model Show documentation
RDF model implementations.
/*******************************************************************************
* Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Distribution License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.model.impl;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.util.LexicalValueComparator;
import org.eclipse.rdf4j.model.util.PatternIterator;
/**
* A Red-Black tree based {@link Model} implementation. The model is sorted according to the lexical ordering of terms.
*
* This implementation provides guaranteed log(n) time cost for filtered access by any number of terms. If an index is
* not yet available for a set of positions, it is created at runtime using a {@link TreeSet}.
*
* Note that this implementation is not synchronized. If multiple threads access a model concurrently, even if
* all of them are read operations, it must be synchronized externally. This is typically accomplished by synchronizing
* on some object that naturally encapsulates the model. If no such object exists, the set should be "wrapped" using the
* Models.synchronizedModel method.
*
*
* @author James Leigh
*/
public class TreeModel extends AbstractModel implements SortedSet {
private static final long serialVersionUID = 7893197431354524479L;
static final IRI BEFORE = new SimpleIRI("urn:from");
static final IRI AFTER = new SimpleIRI("urn:to");
private final LexicalValueComparator vc = new LexicalValueComparator();
final Set namespaces = new TreeSet<>();
final List trees = new ArrayList<>();
public TreeModel() {
trees.add(new StatementTree("spog".toCharArray()));
}
public TreeModel(Model model) {
this(model.getNamespaces());
addAll(model);
}
public TreeModel(Collection extends Statement> c) {
this();
addAll(c);
}
public TreeModel(Set namespaces, Collection extends Statement> c) {
this(c);
this.namespaces.addAll(namespaces);
}
public TreeModel(Set namespaces) {
this();
this.namespaces.addAll(namespaces);
}
@Override
public Optional getNamespace(String prefix) {
for (Namespace nextNamespace : namespaces) {
if (prefix.equals(nextNamespace.getPrefix())) {
return Optional.of(nextNamespace);
}
}
return Optional.empty();
}
@Override
public Set getNamespaces() {
return namespaces;
}
@Override
public Namespace setNamespace(String prefix, String name) {
removeNamespace(prefix);
Namespace result = new SimpleNamespace(prefix, name);
namespaces.add(result);
return result;
}
@Override
public void setNamespace(Namespace namespace) {
removeNamespace(namespace.getPrefix());
namespaces.add(namespace);
}
@Override
public Optional removeNamespace(String prefix) {
Optional result = getNamespace(prefix);
result.ifPresent(namespaces::remove);
return result;
}
@Override
public int size() {
return trees.get(0).size();
}
@Override
public void clear() {
for (StatementTree tree : trees) {
tree.clear();
}
}
@Override
public Comparator super Statement> comparator() {
return trees.get(0).tree.comparator();
}
@Override
public Statement first() {
return trees.get(0).tree.first();
}
@Override
public Statement last() {
return trees.get(0).tree.last();
}
public Statement lower(Statement e) {
return trees.get(0).tree.lower(e);
}
public Statement floor(Statement e) {
return trees.get(0).tree.floor(e);
}
public Statement ceiling(Statement e) {
return trees.get(0).tree.ceiling(e);
}
public Statement higher(Statement e) {
return trees.get(0).tree.higher(e);
}
public Statement pollFirst() {
try {
Statement first = trees.get(0).tree.first();
remove(first);
return first;
} catch (NoSuchElementException e) {
return null;
}
}
public Statement pollLast() {
try {
Statement last = trees.get(0).tree.last();
remove(last);
return last;
} catch (NoSuchElementException e) {
return null;
}
}
@Override
public SortedSet subSet(Statement fromElement, Statement toElement) {
return subSet(fromElement, true, toElement, false);
}
@Override
public SortedSet headSet(Statement toElement) {
return subSet(before(null, null, null, null), true, toElement, false);
}
@Override
public SortedSet tailSet(Statement fromElement) {
return subSet(fromElement, true, after(null, null, null, null), true);
}
@Override
public boolean add(Resource subj, IRI pred, Value obj, Resource... contexts) {
if (subj == null || pred == null || obj == null) {
throw new UnsupportedOperationException("Incomplete statement");
}
boolean changed = false;
for (Value ctx : notEmpty(contexts)) {
if (ctx == null || ctx instanceof Resource) {
Statement st = new TreeStatement(subj, pred, obj, (Resource) ctx);
for (StatementTree tree : trees) {
changed |= tree.add(st);
}
}
}
return changed;
}
@Override
public boolean contains(Resource subj, IRI pred, Value obj, Resource... contexts) {
if (contexts == null || contexts.length == 1 && contexts[0] == null) {
Iterator iter = matchPattern(subj, pred, obj, null);
while (iter.hasNext()) {
if (iter.next().getContext() == null) {
return true;
}
}
return false;
} else if (contexts.length == 0) {
return matchPattern(subj, pred, obj, null).hasNext();
} else {
for (Resource ctx : contexts) {
if (ctx == null) {
if (contains(subj, pred, obj, (Resource[]) null)) {
return true;
}
} else if (matchPattern(subj, pred, obj, ctx).hasNext()) {
return true;
}
}
return false;
}
}
@Override
public boolean remove(Resource subj, IRI pred, Value obj, Resource... contexts) {
if (isEmpty()) {
return false;
}
boolean changed = false;
if (contexts == null || contexts.length == 1 && contexts[0] == null) {
Iterator iter = matchPattern(subj, pred, obj, null);
while (iter.hasNext()) {
if (iter.next().getContext() == null) {
iter.remove();
changed = true;
}
}
} else if (contexts.length == 0) {
Iterator iter = matchPattern(subj, pred, obj, null);
while (iter.hasNext()) {
iter.next();
iter.remove();
changed = true;
}
} else {
for (Resource ctx : contexts) {
if (ctx == null) {
changed |= remove(subj, pred, obj, (Resource[]) null);
} else {
Iterator iter = matchPattern(subj, pred, obj, ctx);
while (iter.hasNext()) {
iter.next();
iter.remove();
changed = true;
}
}
}
}
return changed;
}
@Override
public Iterator iterator() {
return matchPattern(null, null, null, null);
}
@Override
public Model filter(final Resource subj, final IRI pred, final Value obj, final Resource... contexts) {
if (contexts != null && contexts.length == 0) {
return new FilteredModel(this, subj, pred, obj, contexts) {
private static final long serialVersionUID = 396293781006255959L;
@Override
public Iterator iterator() {
return matchPattern(subj, pred, obj, null);
}
@Override
protected void removeFilteredTermIteration(Iterator iter, Resource subj, IRI pred, Value obj,
Resource... contexts) {
TreeModel.this.removeTermIteration(iter, subj, pred, obj, contexts);
}
};
} else if (contexts != null && contexts.length == 1 && contexts[0] != null) {
return new FilteredModel(this, subj, pred, obj, contexts) {
@Override
public Iterator iterator() {
return matchPattern(subj, pred, obj, contexts[0]);
}
@Override
protected void removeFilteredTermIteration(Iterator iter, Resource subj, IRI pred, Value obj,
Resource... contexts) {
TreeModel.this.removeTermIteration(iter, subj, pred, obj, contexts);
}
};
} else {
return new FilteredModel(this, subj, pred, obj, contexts) {
private static final long serialVersionUID = 396293781006255959L;
@Override
public Iterator iterator() {
return new PatternIterator<>(matchPattern(subj, pred, obj, null), subj, pred, obj, contexts);
}
@Override
protected void removeFilteredTermIteration(Iterator iter, Resource subj, IRI pred, Value obj,
Resource... contexts) {
TreeModel.this.removeTermIteration(iter, subj, pred, obj, contexts);
}
};
}
}
@Override
public void removeTermIteration(Iterator iterator, Resource subj, IRI pred, Value obj,
Resource... contexts) {
TreeSet owner = ((ModelIterator) iterator).getOwner();
if (contexts == null || contexts.length == 1 && contexts[0] == null) {
StatementTree chosen = choose(subj, pred, obj, null);
Iterator iter = chosen.subIterator(before(subj, pred, obj, null), true,
after(subj, pred, obj, null), true);
iter = new PatternIterator<>(iter, subj, pred, obj, contexts);
removeAll(owner, chosen, iter);
} else if (contexts.length == 0) {
StatementTree chosen = choose(subj, pred, obj, null);
Iterator iter = chosen.subIterator(before(subj, pred, obj, null), true,
after(subj, pred, obj, null), true);
removeAll(owner, chosen, iter);
} else {
for (Value ctx : notEmpty(contexts)) {
if (ctx == null) {
removeTermIteration(iterator, subj, pred, obj, (Resource[]) null);
} else {
StatementTree chosen = choose(subj, pred, obj, ctx);
Iterator iter = chosen.subIterator(before(subj, pred, obj, ctx), true,
after(subj, pred, obj, ctx), true);
removeAll(owner, chosen, iter);
}
}
}
}
Iterator matchPattern(Resource subj, IRI pred, Value obj, Resource ctx) {
if (!isResourceURIResource(subj, pred, ctx)) {
Set emptySet = Collections.emptySet();
return emptySet.iterator();
}
StatementTree tree = choose(subj, pred, obj, ctx);
Iterator it = tree.subIterator(before(subj, pred, obj, ctx), true, after(subj, pred, obj, ctx),
true);
return new ModelIterator(it, tree);
}
int compareValue(Value o1, Value o2) {
if (o1 == o2) {
return 0;
}
if (o1 == BEFORE) {
return -1;
}
if (o2 == BEFORE) {
return 1;
}
if (o1 == AFTER) {
return 1;
}
if (o2 == AFTER) {
return -1;
}
return vc.compare(o1, o2);
}
SortedSet subSet(Statement lo, boolean loInclusive, Statement hi, boolean hiInclusive) {
return new SubSet(this, new TreeStatement(lo), loInclusive, new TreeStatement(hi), hiInclusive);
}
private void removeAll(TreeSet owner, StatementTree chosen, Iterator iter) {
while (iter.hasNext()) {
Statement last = iter.next();
for (StatementTree tree : trees) {
if (tree.owns(owner)) {
tree.reindex();
tree.remove(last);
} else if (tree != chosen) {
tree.remove(last);
}
}
iter.remove(); // remove from chosen
}
}
private boolean isResourceURIResource(Value subj, Value pred, Value ctx) {
return (subj == null || subj instanceof Resource) && (pred == null || pred instanceof IRI)
&& (ctx == null || ctx instanceof Resource);
}
private Value[] notEmpty(Value[] contexts) {
if (contexts == null || contexts.length == 0) {
return new Resource[] { null };
}
return contexts;
}
private Statement before(Value subj, Value pred, Value obj, Value ctx) {
Resource s = subj instanceof Resource ? (Resource) subj : BEFORE;
IRI p = pred instanceof IRI ? (IRI) pred : BEFORE;
Value o = obj instanceof Value ? obj : BEFORE;
Resource c = ctx instanceof Resource ? (Resource) ctx : BEFORE;
return new TreeStatement(s, p, o, c);
}
private Statement after(Value subj, Value pred, Value obj, Value ctx) {
Resource s = subj instanceof Resource ? (Resource) subj : AFTER;
IRI p = pred instanceof IRI ? (IRI) pred : AFTER;
Value o = obj instanceof Value ? obj : AFTER;
Resource c = ctx instanceof Resource ? (Resource) ctx : AFTER;
return new TreeStatement(s, p, o, c);
}
private StatementTree choose(Value subj, Value pred, Value obj, Value ctx) {
for (StatementTree tree : trees) {
if (tree.isIndexed(subj, pred, obj, ctx)) {
return tree;
}
}
return index(subj, pred, obj, ctx);
}
private StatementTree index(Value subj, Value pred, Value obj, Value ctx) {
int idx = 0;
char[] index = new char[4];
if (subj != null) {
index[idx++] = 's';
}
if (pred != null) {
index[idx++] = 'p';
}
if (obj != null) {
index[idx++] = 'o';
}
if (ctx != null) {
index[idx++] = 'g';
}
if (pred == null) {
index[idx++] = 'p';
}
if (obj == null) {
index[idx++] = 'o';
}
if (ctx == null) {
index[idx++] = 'g';
}
if (subj == null) {
index[idx++] = 's';
}
StatementTree tree = new StatementTree(index);
tree.addAll(trees.get(0));
trees.add(tree);
return tree;
}
@Override
public boolean isEmpty() {
if (trees.isEmpty()) {
return true;
}
return trees.get(0).isEmpty();
}
private class ModelIterator implements Iterator {
private final Iterator iter;
private final TreeSet owner;
private Statement last;
public ModelIterator(Iterator iter, StatementTree owner) {
this.iter = iter;
this.owner = owner.tree;
}
public TreeSet getOwner() {
return owner;
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public Statement next() {
return last = iter.next();
}
@Override
public void remove() {
if (last == null) {
throw new IllegalStateException();
}
for (StatementTree tree : trees) {
removeFrom(tree);
}
iter.remove(); // remove from owner
}
private void removeFrom(StatementTree subjects) {
if (!subjects.owns(owner)) {
subjects.remove(last);
}
}
}
static class TreeStatement extends ContextStatement {
private static final long serialVersionUID = -7720419322256724495L;
public TreeStatement(Statement st) {
super(st.getSubject(), st.getPredicate(), st.getObject(), st.getContext());
}
public TreeStatement(Resource subject, IRI predicate, Value object, Resource ctx) {
super(subject, predicate, object, ctx);
}
}
class StatementTree implements Serializable {
private static final long serialVersionUID = -7580746419791799953L;
private final char[] index;
TreeSet tree;
public StatementTree(char[] index) {
this.index = index;
Comparator[] comparators = new Comparator[index.length];
for (int i = 0; i < index.length; i++) {
switch (index[i]) {
case 's':
comparators[i] = new SubjectComparator();
break;
case 'p':
comparators[i] = new PredicateComparator();
break;
case 'o':
comparators[i] = new ObjectComparator();
break;
case 'g':
comparators[i] = new GraphComparator();
break;
default:
throw new AssertionError();
}
}
tree = new TreeSet<>(new StatementComparator(comparators));
}
public boolean owns(TreeSet set) {
return tree == set;
}
public boolean isIndexed(Value subj, Value pred, Value obj, Value ctx) {
boolean wild = false;
for (int i = 0; i < index.length; i++) {
switch (index[i]) {
case 's':
if (subj == null) {
wild = true;
} else if (wild) {
return false;
}
break;
case 'p':
if (pred == null) {
wild = true;
} else if (wild) {
return false;
}
break;
case 'o':
if (obj == null) {
wild = true;
} else if (wild) {
return false;
}
break;
case 'g':
if (ctx == null) {
wild = true;
} else if (wild) {
return false;
}
break;
default:
throw new AssertionError();
}
}
return true;
}
public void reindex() {
TreeSet treeSet = new TreeSet<>(tree.comparator());
treeSet.addAll(tree);
tree = treeSet;
}
public boolean add(Statement e) {
return tree.add(e);
}
public boolean addAll(StatementTree c) {
return tree.addAll(c.tree);
}
public int size() {
return tree.size();
}
public void clear() {
tree.clear();
}
public boolean remove(Object o) {
return tree.remove(o);
}
public Iterator subIterator(Statement fromElement, boolean fromInclusive, Statement toElement,
boolean toInclusive) {
return tree.subSet(fromElement, true, toElement, true).iterator();
}
public boolean isEmpty() {
return tree.isEmpty();
}
}
class SubjectComparator implements Serializable, Comparator {
private static final long serialVersionUID = 5275239384134217143L;
@Override
public int compare(Statement s1, Statement s2) {
return compareValue(s1.getSubject(), s2.getSubject());
}
}
class PredicateComparator implements Serializable, Comparator {
private static final long serialVersionUID = -883414941022127103L;
@Override
public int compare(Statement s1, Statement s2) {
return compareValue(s1.getPredicate(), s2.getPredicate());
}
}
class ObjectComparator implements Serializable, Comparator {
private static final long serialVersionUID = 1768294714884456242L;
@Override
public int compare(Statement s1, Statement s2) {
return compareValue(s1.getObject(), s2.getObject());
}
}
class GraphComparator implements Serializable, Comparator {
private static final long serialVersionUID = 7027824614533897706L;
@Override
public int compare(Statement s1, Statement s2) {
return compareValue(s1.getContext(), s2.getContext());
}
}
static class StatementComparator implements Serializable, Comparator {
private static final long serialVersionUID = -5602364720279633641L;
private final Comparator[] comparators;
public StatementComparator(Comparator... comparators) {
this.comparators = comparators;
}
@Override
public int compare(Statement s1, Statement s2) {
for (Comparator c : comparators) {
int r1 = c.compare(s1, s2);
if (r1 != 0) {
return r1;
}
}
return 0;
}
}
static class SubSet extends AbstractSet implements Serializable, SortedSet {
private static final long serialVersionUID = 6362727792092563793L;
private final TreeModel model;
private final TreeStatement lo, hi;
private final boolean loInclusive, hiInclusive;
public SubSet(TreeModel model, TreeStatement lo, boolean loInclusive, TreeStatement hi, boolean hiInclusive) {
this.model = model;
this.lo = lo;
this.loInclusive = loInclusive;
this.hi = hi;
this.hiInclusive = hiInclusive;
}
public Optional getNamespace(String prefix) {
return model.getNamespace(prefix);
}
public Set getNamespaces() {
return model.getNamespaces();
}
public Namespace setNamespace(String prefix, String name) {
return model.setNamespace(prefix, name);
}
public void setNamespace(Namespace namespace) {
model.setNamespace(namespace);
}
public Optional removeNamespace(String prefix) {
return model.removeNamespace(prefix);
}
@Override
public int size() {
return subSet().size();
}
@Override
public void clear() {
StatementTree tree = model.trees.get(0);
Iterator it = tree.subIterator(lo, loInclusive, hi, hiInclusive);
it = model.new ModelIterator(it, tree);
while (it.hasNext()) {
it.remove();
}
}
@Override
public Comparator super Statement> comparator() {
return model.comparator();
}
@Override
public Statement first() {
return subSet().first();
}
@Override
public Statement last() {
return subSet().last();
}
public Statement lower(Statement e) {
return subSet().lower(e);
}
@Override
public boolean isEmpty() {
return subSet().isEmpty();
}
public Statement floor(Statement e) {
return subSet().floor(e);
}
public Statement ceiling(Statement e) {
return subSet().ceiling(e);
}
public Statement higher(Statement e) {
return subSet().higher(e);
}
public Statement pollFirst() {
try {
Statement first = subSet().first();
model.remove(first);
return first;
} catch (NoSuchElementException e) {
return null;
}
}
public Statement pollLast() {
try {
Statement last = subSet().last();
model.remove(last);
return last;
} catch (NoSuchElementException e) {
return null;
}
}
@Override
public SortedSet subSet(Statement fromElement, Statement toElement) {
boolean fromInclusive = true;
boolean toInclusive = false;
if (comparator().compare(fromElement, lo) < 0) {
fromElement = lo;
fromInclusive = loInclusive;
}
if (comparator().compare(hi, toElement) < 0) {
toElement = hi;
toInclusive = hiInclusive;
}
return model.subSet(fromElement, fromInclusive, toElement, toInclusive);
}
@Override
public SortedSet headSet(Statement toElement) {
boolean toInclusive = false;
if (comparator().compare(hi, toElement) < 0) {
toElement = hi;
toInclusive = hiInclusive;
}
return model.subSet(lo, loInclusive, toElement, toInclusive);
}
@Override
public SortedSet tailSet(Statement fromElement) {
boolean fromInclusive = true;
if (comparator().compare(fromElement, lo) < 0) {
fromElement = lo;
fromInclusive = loInclusive;
}
return model.subSet(fromElement, fromInclusive, hi, hiInclusive);
}
@Override
public Iterator iterator() {
StatementTree tree = model.trees.get(0);
Iterator it = tree.subIterator(lo, loInclusive, hi, hiInclusive);
return model.new ModelIterator(it, tree);
}
private NavigableSet subSet() {
return model.trees.get(0).tree.subSet(lo, loInclusive, hi, hiInclusive);
}
}
}