package com.bigdata.bop.ap.filter;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import com.bigdata.bop.Constant;
import com.bigdata.bop.IConstant;
import com.bigdata.bop.IElement;
import com.bigdata.bop.IPredicate;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.IVariableOrConstant;
import com.bigdata.io.LongPacker;
import com.bigdata.relation.accesspath.IAccessPath;
import cutthecrap.utils.striterators.IFilterTest;
/**
* Filter imposes the "same variable" constraint on the elements visited by an
* {@link IAccessPath}. The filter is required IFF a {@link IVariable} appears
* in more than one position for the {@link IPredicate} associated with the
* {@link IAccessPath}. For example, in <code>spo(?g, p1, o1, ?g)</code>, the
* variable <i>g</i> shows up at both index ZERO (0) and index THREE (3).
* <p>
* An instance of the filter is created by passing in an {@link IPredicate}. The
* filter creates an array of variables which appear more than once and the
* index at which those variables appear in the predicate. The filter then does
* the minimum amount of work and just compares the values found in the
* different slots in which each variable appears for only those variables which
* appear more than once in the {@link IPredicate}.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
* @param <E>
* The generic type of the elements that will be tested by the
* filter.
*/
public class SameVariableConstraint<E> implements IFilterTest, Externalizable {
/**
* The predicate template.
*/
private IPredicate<E> p;
/**
* An array containing one or more records, each of which has the form
*
* <pre>
* [n,index[0], index[1], ... index[n-1]]
* </pre>
*
* , where <i>n</i> is the number of occurrences of some variable and
* <i>index[i]</i>, for <i>i</i> in <code>[0,n-1]</code>, gives each index
* in the predicate at which that variable appears.
* <p>
* For example, given the predicate
*
* <pre>
* spo(?g,p1,o1,?g)
* </pre>
*
* the array would be coded as one record
*
* <pre>
* [2, 0, 3]
* </pre>
*
* Given the predicate
*
* <pre>
* foo(?g,?h,?h,?g,4,?h)
* </pre>
*
* the array would be coded as two records
*
* <pre>
* [[2, 0, 3], [3, 1, 2, 5]]
* </pre>
*
* where the records are indicated by the nested square brackets.
*/
protected int[] indices;
/**
* De-serialization constructor.
*/
public SameVariableConstraint() {
}
public SameVariableConstraint(final IPredicate<E> p, final int[] indices) {
if (p == null)
throw new IllegalArgumentException();
if (indices == null)
throw new IllegalArgumentException();
this.p = p;
this.indices = indices;
}
// public boolean canAccept(final Object o) {
// return true;
// }
@SuppressWarnings("unchecked")
public boolean isValid(final Object obj) {
return accept((E) obj);
}
public int[] getIndices() {
return indices;
}
private boolean accept(final E e) {
int i = 0;
while (i < indices.length) {
// the #of slots at which that variable appears.
final int nslots = indices[i];
assert nslots >= 2 : "i=" + i + ", nslots=" + nslots + ", indices="
+ Arrays.toString(indices);
assert i + nslots < indices.length;
i++;
final int firstIndex = indices[i];
@SuppressWarnings("unchecked")
final IConstant<?> c0 = new Constant(((IElement) e).get(firstIndex));
// final IConstant<?> c0 = p.get(e, firstIndex);
for (int j = 0; j < nslots; j++, i++) {
if (j == firstIndex) {
// no need to compare with self.
continue;
}
final int thisIndex = indices[i];
@SuppressWarnings("unchecked")
final IConstant<?> c1 = new Constant(((IElement) e)
.get(thisIndex));
// final IConstant<?> c1 = p.get(e, thisIndex);
// same reference (including null).
if (c0 == c1)
continue;
if (c0 != null && c1 == null) {
return false;
}
if (c1 == null && c0 != null) {
return false;
}
if (!c0.equals(c1)) {
// different constant.
return false;
}
}
}
return true;
}
public String toString() {
return super.toString() + "{p=" + p + ", indices="
+ Arrays.toString(indices) + "}";
}
/**
* The initial version.
*/
private static final transient byte VERSION0 = 0;
/**
* The current version.
*/
private static final transient byte VERSION = VERSION0;
@SuppressWarnings("unchecked")
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
final short version = in.readByte();
switch (version) {
case VERSION0:
break;
default:
throw new UnsupportedOperationException("Unknown version: "
+ version);
}
p = (IPredicate<E>) in.readObject();
final int len = (int) LongPacker.unpackLong(in);
indices = new int[len];
for (int i = 0; i < len; i++) {
indices[i] = (int) LongPacker.unpackLong(in);
}
}
public void writeExternal(ObjectOutput out) throws IOException {
out.writeByte(VERSION);
out.writeObject(p);
LongPacker.packLong(out, indices.length);
for (int i = 0; i < indices.length; i++) {
LongPacker.packLong(out, indices[i]);
}
}
/**
* The filter is only created and populated for variables which appear more
* than once in the predicate. If there are no variables which appear more
* than once, then the filter IS NOT created.
*
* @param p
* The predicate.
*
* @param <E>
* The generic type of the elements to be tested by the filter.
*
* @return The filter -or- <code>null</code> iff no variables appear more
* than once in the {@link IPredicate}.
*/
public static <E> SameVariableConstraint<E> newInstance(
final IPredicate<E> p) {
// #of slots in this predicate.
final int arity = p.arity();
// map exists iff variables are used more than once.
Map<IVariableOrConstant<?>, Integer> vars = null;
// #of occurrences across all variables which are used more than once.
int noccurs = 0;
// #of variables which are reused within the predicate.
int nreused = 0;
{
for (int i = 0; i < arity; i++) {
final IVariableOrConstant<?> t = p.get(i);
if (t != null && t.isVar()) {
// this slot is a variable.
if (vars == null) {
vars = new LinkedHashMap<IVariableOrConstant<?>, Integer>();
}
Integer cnt = vars.get(t);
if (cnt == null) {
vars.put(t, cnt = Integer.valueOf(1));
} else {
final int tmp = cnt.intValue() + 1;
vars.put(t, cnt = Integer.valueOf(tmp));
// note: add 2 for the 1st dup and 1 for each
// additional dup thereafter.
noccurs += (tmp == 2 ? 2 : 1);
if (tmp == 2) {
// this variable is reused.
nreused++;
}
}
}
}
if (nreused == 0) {
// there are no duplicate variables.
return null;
}
}
assert vars != null;
// allocate the array for the reused variable record(s).
final int[] indices = new int[nreused + noccurs];
// populate that array.
{
int i = 0;
final Iterator<Map.Entry<IVariableOrConstant<?>, Integer>> itr = vars
.entrySet().iterator();
while (itr.hasNext()) {
final Map.Entry<IVariableOrConstant<?>, Integer> e = itr.next();
final int nused = e.getValue().intValue();
if (nused < 2)
continue;
final IVariable<?> var = (IVariable<?>)e.getKey();
indices[i++] = nused;
for (int j = 0; j < arity; j++) {
final IVariableOrConstant<?> t = p.get(j);
if (t == var) {
// variable appears at this index.
indices[i++] = j;
}
}
}
assert i == indices.length;
}
return new SameVariableConstraint<E>(p, indices);
} // factory method.
}