package com.bigdata.bop.bindingSet; import com.bigdata.bop.Constant; import java.io.Serializable; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IConstant; import com.bigdata.bop.IVariable; import com.bigdata.bop.Var; import cutthecrap.utils.striterators.Resolver; import cutthecrap.utils.striterators.Striterator; /** * <p>An {@link IBindingSet} based on a {@link LinkedList}. Since {@link Var}s may * be compared using <code>==</code> this should be faster than a hash map for * most operations unless the binding set has a large number of entries. * </p><p> * Note: {@link #push()} and {@link #pop(boolean)} are implemented by making a * copy of the current symbol table with distinct {@link Map.Entry} objects. If * the symbol table is saved when it is {@link #pop(boolean) popped), then it * simply replaces the pre-existing symbol table which was uncovered when it * was popped off of the stack. This design has several advantages, including: * <ul> * <li>Methods such as {@link #get(IVariable)}, {@link #set(IVariable, IConstant)}, * and {@link #size()} can be written solely in terms of the current symbol table.</li> * <li>{@link #clear(IVariable)} removes the {@link Map.Entry} from the * current symbol table rather than introducing <code>null</code> values or * delete markers.</li> * </ul> * </p> * The only down side to this approach is that the serialized representation of * the {@link IBindingSet} is more complex. However, java default serialization * will do a good job by providing back references for the object graph. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id: HashBindingSet.java 5123 2011-09-03 10:48:24Z thompsonbry $ */ public class ListBindingSet implements IBindingSet { private static final long serialVersionUID = 1L; /** * A (var,val) entry. */ private static class E implements Map.Entry<IVariable<?>, IConstant<?>>, Serializable { /** * */ private static final long serialVersionUID = 1L; private final IVariable<?> var; private IConstant<?> val; E(final IVariable<?> var, final IConstant<?> val) { this.var = var; this.val = val; } public IVariable<?> getKey() { return var; } public IConstant<?> getValue() { return val; } public IConstant<?> setValue(final IConstant<?> value) { if (value == null) { // Null bindings are not permitted. throw new IllegalArgumentException(); } final IConstant<?> tmp = this.val; this.val = value; return tmp; } public String toString() { return var + "=" + val; } }; // /** // * The stack of symbol tables. Each symbol table is a mapping from an // * {@link IVariable} onto its non-<code>null</code> bound {@link IConstant}. // * The stack is initialized with an empty symbol table. Symbol tables may be // * pushed onto the stack or popped off of the stack, but the stack MAY NOT // * become empty. // */ // private final LightStack<List<E>> stack; private final List<E> current; /** * Return the symbol table on the top of the stack. */ final private List<E> current() { return current; // return stack.peek(); } // public void push(final IVariable[] vars) { // // // Create a new symbol table. // final List<E> tmp = new LinkedList<E>(); // // /* // * For each variable which is to be projected into the new symbol table, // * look up its binding. If it is bound, then copy that binding into the // * new symbol table. // */ // for (IVariable<?> var : vars) { // // final IConstant<?> val = get(var); // // if (val != null) // tmp.add(new E(var, val)); // // } // // // Push the new symbol table onto the stack. // stack.push(tmp); // // hash = 0; // // } // // public void pop(final IVariable[] vars) { // // if (stack.size() < 2) { // /* // * The stack may never become empty. Therefore there must be at // * least two symbol tables on the stack for a pop() request. // */ // throw new IllegalStateException(); // } // // // Pop the symbol table off of the top of the stack. // final List<E> old = stack.pop(); // // /* // * Copy the current binding for any variable that was projected by the // * subquery. // * // * Note: This does not enforce consistency between the existing binding // * (if any) for a variable on the nested symbol table and the binding // * from the symbol table which is being popped off of the stack. It is // * the responsibility of the JOINs, BIND, etc. to verify that bindings // * are consistent when they are made. If the operators are written // * correctly then a variable which was bound on entry to a subquery will // * have the same binding on exit and there will be no need to verify // * that the value, when bound, is consistent. // */ // for (E e : old) { // for each binding from the top of the stack. // // for (IVariable<?> v : vars) { // for each projected variable. // // if (e.var == v) { // if the binding was projected // // set(e.var, e.val); // then copy to revealed symbol table. // // break; // // } // // } // // } // // hash = 0; // // } /** * Create an empty binding set. */ // * <p> // * We generally want the binding set stack to be pretty shallow. We only // * push/pop around a subquery. Simple queries have a depth of 1. A stack // * depth of 3 is typical of a complex query with nested subqueries. Deeper // * nesting is uncommon. Since we create a LOT of {@link IBindingSet}s, the // * initial capacity of the associated stack is important. public ListBindingSet() { // /* // * Start with a capacity of ONE (1) and expand only as required. // */ // stack = new LightStack<List<E>>(1/*initialCapacity*/); // // stack.push(new LinkedList<E>()); current = new LinkedList<E>(); } /** * Alternative constructor. * * @param vars * A copy is made of the data. * @param vals * A copy is made of the data. */ @SuppressWarnings("rawtypes") public ListBindingSet(final IVariable[] vars, final IConstant[] vals) { this(); if (vars == null) throw new IllegalArgumentException(); if (vals == null) throw new IllegalArgumentException(); if (vars.length != vals.length) throw new IllegalArgumentException(); for (int i = 0; i < vars.length; i++) { set(vars[i], vals[i]); } } /** * Copy constructor (used by clone, copy). * * @param src * The source to be copied. * @param variablesToKeep * The variables to be retained for the symbol table on the top * of the stack (optional). */ @SuppressWarnings("rawtypes") protected ListBindingSet(final ListBindingSet src, final IVariable[] variablesToKeep) { // final int stackSize = src.stack.size(); // // stack = new LightStack<List<E>>(stackSize); // // int depth = 1; // // for (List<E> srcLst : src.stack) { // // /* // * Copy the source bindings. // * // * Note: If a restriction exists on the variables to be copied, then // * it is applied onto the the top level of the stack. If the symbol // * table is saved when it is pop()'d, then the modified bindings // * will replace the parent symbol table on the stack. // */ // final List<E> tmp = copy(srcLst, // depth == stackSize ? variablesToKeep : null); // // // Push onto the stack. // stack.push(tmp); // // } current = copy(src.current, variablesToKeep); } private ListBindingSet(List<E> contents) { current = contents; } /** * Return a copy of the source list. The copy will use new {@link E}s to * represent the bindings so changes to the copy will not effect the source. * * @param src * The source list. * @param variablesToKeep * When non-<code>null</code>, only the bindings for the * variables listed in this array will copied. * * @return The copy. */ @SuppressWarnings("rawtypes") private List<E> copy(final List<E> src, final IVariable[] variablesToKeep) { final List<E> dst = new LinkedList<E>(); final Iterator<E> itr = src.iterator(); while (itr.hasNext()) { final E e = itr.next(); boolean keep = true; if (variablesToKeep != null) { keep = false; for (IVariable<?> x : variablesToKeep) { if (x == e.var) { keep = true; break; } } } if (keep) dst.add(new E(e.var, e.val)); } return dst; } /** * Return a copy of the source list minus entries assigning error values * (Constant.errorValue() or its copies). The copy will use new * {@link E}s to represent the bindings so changes to the copy will not * effect the source. * * @param src The source list. * @param variablesToKeep When non-<code>null</code>, only the bindings for * the variables listed in this array will copied. * * @return The copy. */ @SuppressWarnings("rawtypes") private List<E> copyMinusErrors(final List<E> src, final IVariable[] variablesToKeep) { final List<E> dst = new LinkedList<E>(); final Iterator<E> itr = src.iterator(); while (itr.hasNext()) { final E e = itr.next(); if (e.val == Constant.errorValue()) continue; boolean keep = true; if (variablesToKeep != null) { keep = false; for (IVariable<?> x : variablesToKeep) { if (x == e.var) { keep = true; break; } } } if (keep) { dst.add(new E(e.var, e.val)); } } // while (itr.hasNext()) return dst; } // copyMinusErrors(final List<E> src, final IVariable[] variablesToKeep) public ListBindingSet clone() { return new ListBindingSet(this, null /* variablesToKeep */); } @SuppressWarnings("rawtypes") public IBindingSet copy(final IVariable[] variablesToKeep) { return new ListBindingSet(this/*src*/, variablesToKeep); } @Override @SuppressWarnings("rawtypes") public final IBindingSet copyMinusErrors(final IVariable[] variablesToKeep) { return new ListBindingSet(copyMinusErrors(this.current, variablesToKeep)); } /** * @return true if this IBindingSet contains an assignment of an error value */ @Override public final boolean containsErrorValues() { for (E e : this.current) { if (e.val == Constant.errorValue()) return true; } return false; } @SuppressWarnings("rawtypes") public void clear(final IVariable var) { if (var == null) throw new IllegalArgumentException(); final List<E> cur = current(); for(E e : cur) { if(e.var == var) { cur.remove(e); // clear the hash code. hash = 0; return; } } } public void clearAll() { current().clear(); // clear the hash code. hash = 0; } @SuppressWarnings("rawtypes") public IConstant get(final IVariable var) { if (var == null) throw new IllegalArgumentException(); final List<E> cur = current(); for(E e : cur) { if(e.var == var) { return e.val; } } return null; } @SuppressWarnings("rawtypes") public boolean isBound(IVariable var) { if (var == null) throw new IllegalArgumentException(); final List<E> cur = current(); for(E e : cur) { if(e.var == var) { return true; } } return false; } @SuppressWarnings({ "unchecked", "rawtypes" }) public Iterator<Map.Entry<IVariable, IConstant>> iterator() { // return (Iterator<Map.Entry<IVariable, IConstant>>) ((List) Collections // .unmodifiableList(current())).iterator(); return (Iterator<Map.Entry<IVariable, IConstant>>) ((List) current()) .iterator(); } @SuppressWarnings("rawtypes") public void set(final IVariable var, final IConstant val) { if (var == null) throw new IllegalArgumentException(); if (val == null) throw new IllegalArgumentException(); final List<E> cur = current(); for (E e : cur) { if (e.var == var) { e.val = val; // clear the hash code. hash = 0; return; } } cur.add(new E(var, val)); // clear the hash code. hash = 0; } public boolean isEmpty() { return current().isEmpty(); } public int size() { return current().size(); } @SuppressWarnings({ "unchecked", "rawtypes" }) public Iterator<IVariable> vars() { return (Iterator<IVariable>) new Striterator(Collections .unmodifiableList(current()).iterator()) .addFilter(new Resolver() { private static final long serialVersionUID = 1L; @Override protected Object resolve(Object obj) { return ((E) obj).var; } }); } public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("{ "); int i = 0; final Iterator<E> itr = current().iterator(); while (itr.hasNext()) { if (i > 0) sb.append(", "); final E entry = itr.next(); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); i++; } sb.append(" }"); return sb.toString(); } public boolean equals(final Object t) { if (this == t) return true; if(!(t instanceof IBindingSet)) return false; final IBindingSet o = (IBindingSet) t; if (size() != o.size()) return false; final Iterator<E> itr = current().iterator(); while(itr.hasNext()) { final E entry = itr.next(); final IVariable<?> var = entry.getKey(); final IConstant<?> val = entry.getValue(); // if (!o.isBound(vars[i])) // return false; final IConstant<?> o_val = o.get ( var ) ; if (null == o_val || !val.equals(o_val)) return false; } return true; } public int hashCode() { if (hash == 0) { int result = 0; final List<E> cur = current(); for(E e : cur) { if (e.val == null) continue; result ^= e.val.hashCode(); } hash = result; } return hash; } /** * Note: The hash code MUST be reset by any mutation! */ private int hash; }