package eu.fbk.knowledgestore.internal.rdf; import java.io.Serializable; import java.util.AbstractList; import java.util.AbstractSet; import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import javax.annotation.Nullable; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.UnmodifiableIterator; import org.openrdf.model.Value; import org.openrdf.query.Binding; import org.openrdf.query.BindingSet; import org.openrdf.query.impl.BindingImpl; public abstract class CompactBindingSet implements BindingSet, Serializable { private static final long serialVersionUID = 1L; private final VariableList variables; @Nullable private Set<String> names; private int hash; CompactBindingSet(final VariableList variableNames) { this.variables = variableNames; this.names = null; this.hash = 0; } abstract Value get(int index); @Override public final int size() { return getBindingNames().size(); } @Override public final Iterator<Binding> iterator() { return new BindingIterator(this); } @Override public final Set<String> getBindingNames() { if (this.names == null) { int count = 0; final int size = this.variables.size(); for (int i = 0; i < size; ++i) { if (get(i) != null) { ++count; } } final String[] array = new String[count]; int index = 0; for (int i = 0; i < size; ++i) { if (get(i) != null) { array[index++] = this.variables.get(i); } } this.names = new VariableSet(array); } return this.names; } @Override public final boolean hasBinding(final String name) { return get(this.variables.indexOf(name)) != null; } @Override @Nullable public final Binding getBinding(final String name) { final Value value = get(this.variables.indexOf(name)); return value == null ? null : new BindingImpl(name, value); } @Override @Nullable public final Value getValue(final String name) { return get(this.variables.indexOf(name)); } @Override public final boolean equals(final Object object) { if (object == this) { return true; } if (!(object instanceof BindingSet)) { return false; } final BindingSet other = (BindingSet) object; final int thisSize = this.variables.size(); if (other instanceof CompactBindingSet && ((CompactBindingSet) other).variables == this.variables) { final CompactBindingSet bs = (CompactBindingSet) other; for (int i = 0; i < thisSize; ++i) { if (!Objects.equal(get(i), bs.get(i))) { return false; } } } else { final Set<String> thisNames = getBindingNames(); final Set<String> otherNames = other.getBindingNames(); if (thisNames.size() != otherNames.size()) { return false; } final int size = this.variables.size(); for (int i = 0; i < size; ++i) { if (!Objects.equal(get(i), other.getValue(this.variables.get(i)))) { return false; } } } return true; } /** * {@inheritDoc} */ @Override public final int hashCode() { if (this.hash == 0) { int hash = 0; final int size = this.variables.size(); for (int i = 0; i < size; ++i) { final Value value = get(i); if (value != null) { hash ^= this.variables.get(i).hashCode() ^ value.hashCode(); } } this.hash = hash; } return this.hash; } /** * {@inheritDoc} The returned string follows the format {@code [name=value;name=value...]} (as * generally done in implementations of {@code BindingSet}). */ @Override public final String toString() { final StringBuilder builder = new StringBuilder(32 * size()); builder.append('['); String separator = ""; final int size = this.variables.size(); for (int i = 0; i < size; ++i) { final Value value = get(i); if (value != null) { final String variable = this.variables.get(i); builder.append(separator); builder.append(variable); builder.append("="); builder.append(value); separator = ";"; } } builder.append(']'); return builder.toString(); } public static Builder builder(final Iterable<? extends String> variables) { return new Builder(variables instanceof VariableList ? (VariableList) variables : new VariableList(variables)); } public static final class Builder { private final VariableList variables; private Value[] values; Builder(final VariableList variables) { this.variables = variables; this.values = new Value[variables.size()]; } public Builder set(final int index, @Nullable final Value value) throws IndexOutOfBoundsException { checkIndex(this.variables, index); this.values[index] = CompactValueFactory.getInstance().normalize(value); return this; } public Builder set(final String variable, @Nullable final Value value) throws NoSuchElementException { final int index = indexOfVariable(this.variables, variable); this.values[index] = CompactValueFactory.getInstance().normalize(value); return this; } public Builder setAll(final Value... values) throws IllegalArgumentException { final int size = values.length; checkSize(this.variables, size); for (int i = 0; i < values.length; ++i) { this.values[i] = CompactValueFactory.getInstance().normalize(values[i]); } return this; } public Builder setAll(final Iterable<? extends Value> values) throws IllegalArgumentException { final int size = Iterables.size(values); checkSize(this.variables, size); int index = 0; for (final Value value : values) { this.values[index++] = CompactValueFactory.getInstance().normalize(value); } return this; } public Builder setAll(final Map<? extends Object, ? extends Value> values) throws NoSuchElementException, IndexOutOfBoundsException { Arrays.fill(this.values, null); for (final Map.Entry<? extends Object, ? extends Value> entry : values.entrySet()) { final Object key = entry.getKey(); final Value value = entry.getValue(); if (key instanceof Number) { set(((Number) key).intValue(), value); } else { set(key.toString(), value); } } return this; } public Builder setAll(final BindingSet bindings) { Arrays.fill(this.values, null); for (final String name : bindings.getBindingNames()) { set(name, bindings.getValue(name)); } return this; } public CompactBindingSet build() { final int size = this.values.length; int singletonIndex = -1; Value singletonValue = null; for (int i = 0; i < size; ++i) { final Value value = this.values[i]; if (value != null) { if (singletonIndex == -1) { singletonIndex = i; singletonValue = value; } else { final CompactBindingSet solution = new ArrayCompactBindingSet( this.variables, this.values); this.values = new Value[size]; return solution; } } } if (singletonIndex == -1) { return new EmptyCompactBindingSet(this.variables); } this.values[singletonIndex] = null; return new SingletonCompactBindingSet(this.variables, singletonIndex, singletonValue); } private static void checkIndex(final List<String> variables, final int index) { if (index < 0 || index >= variables.size()) { throw new IndexOutOfBoundsException("Invalid variable index " + index + " (variables are: " + Joiner.on(", ").join(variables) + ")"); } } private static void checkSize(final List<String> variables, final int size) { if (size != variables.size()) { throw new IllegalArgumentException("Expected " + variables.size() + " values, got " + size + " (variables are: " + Joiner.on(", ").join(variables) + ")"); } } private static int indexOfVariable(final List<String> variables, final String variable) { final int index = variables.indexOf(variable); if (index < 0) { throw new NoSuchElementException("Unknown variable '" + variable + "' (variables are: " + Joiner.on(", ").join(variables) + ")"); } return index; } } private static final class BindingIterator extends UnmodifiableIterator<Binding> { private final CompactBindingSet bindings; private Binding next; private int index; BindingIterator(final CompactBindingSet bindings) { this.bindings = bindings; this.next = null; this.index = 0; advance(); } private void advance() { final int size = this.bindings.variables.size(); while (this.index < size) { final int index = this.index++; final Value value = this.bindings.get(index); if (value != null) { final String variable = this.bindings.variables.get(index); this.next = new BindingImpl(variable, value); break; } } } @Override public boolean hasNext() { return this.next != null; } @Override public Binding next() { final Binding result = this.next; if (result == null) { throw new NoSuchElementException(); } this.next = null; advance(); return result; } } private static class VariableSet extends AbstractSet<String> { private final String[] variables; VariableSet(final String... variables) { this.variables = variables; } @Override public Iterator<String> iterator() { return Iterators.forArray(this.variables); } @Override public int size() { return this.variables.length; } @Override public boolean contains(final Object object) { if (object instanceof String) { for (int i = 0; i < this.variables.length; ++i) { if (this.variables[i].equals(object)) { return true; } } } return false; } } private static final class VariableList extends AbstractList<String> { private final String[] variables; private final String[] variableTable; private final int[] indexTable; public VariableList(final Iterable<? extends String> variables) { final int size = Iterables.size(variables); final int tableSize = size * 4 - 1; this.variables = new String[size]; this.variableTable = new String[tableSize]; this.indexTable = new int[tableSize]; int index = 0; for (final String variable : variables) { int tableIndex = (variable.hashCode() & 0x7FFFFFFF) % tableSize; while (this.variableTable[tableIndex] != null) { tableIndex = (tableIndex + 1) % tableSize; } this.variables[index] = variable; this.variableTable[tableIndex] = variable; this.indexTable[tableIndex] = index; ++index; } } @Override public int size() { return this.variables.length; } @Override public String get(final int index) { return this.variables[index]; } @Override public boolean contains(final Object object) { return indexOf(object) != -1; } @Override public int indexOf(final Object object) { final int tableSize = this.variableTable.length; int tableIndex = (object.hashCode() & 0x7FFFFFFF) % tableSize; for (int i = 0; i < tableSize; ++i) { final String candidate = this.variableTable[tableIndex]; if (candidate != null && candidate.equals(object)) { return this.indexTable[tableIndex]; } tableIndex = (tableIndex + 1) % tableSize; } return -1; } @Override public int lastIndexOf(final Object object) { return indexOf(object); } } private static final class EmptyCompactBindingSet extends CompactBindingSet { private static final long serialVersionUID = 1L; EmptyCompactBindingSet(final VariableList variables) { super(variables); } @Override @Nullable public Value get(final int index) { return null; } } private static final class SingletonCompactBindingSet extends CompactBindingSet { private static final long serialVersionUID = 1L; private final int index; private final Value value; SingletonCompactBindingSet(final VariableList variables, final int index, // final Value value) { super(variables); this.index = index; this.value = value; } @Override @Nullable public Value get(final int index) { return index == this.index ? this.value : null; } } private static final class ArrayCompactBindingSet extends CompactBindingSet { private static final long serialVersionUID = 1L; private final Value[] values; ArrayCompactBindingSet(final VariableList variables, final Value[] values) { super(variables); this.values = values; } @Override @Nullable public Value get(final int index) { return index >= 0 && index < this.values.length ? this.values[index] : null; } } }