/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Jun 19, 2008 */ package com.bigdata.bop.bindingSet; import com.bigdata.bop.Constant; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import com.bigdata.bop.IBindingSet; import com.bigdata.bop.IConstant; import com.bigdata.bop.IVariable; /** * {@link IBindingSet} backed by a {@link LinkedHashMap}. * <p> * Note: A {@link LinkedHashMap} provides a fast iterator, which we use a bunch. * However, {@link IBindingSet}s are inherently unordered collections of * bindings so the order preservation aspect of the {@link LinkedHashMap} is not * relied upon. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class HashBindingSet implements IBindingSet { private static final long serialVersionUID = -2989802566387532422L; // /** // * Note: A {@link LinkedHashMap} provides a fast iterator, which we use a // * bunch. // */ // private final LinkedHashMap<IVariable, IConstant> map; // /** // * 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<LinkedHashMap<IVariable, IConstant>> stack; final private LinkedHashMap<IVariable,IConstant> current; /** * Return the symbol table on the top of the stack. */ private LinkedHashMap<IVariable, IConstant> current() { return current; // return stack.peek(); } // public void push(final IVariable[] vars) { // // // Create a new symbol table. // final LinkedHashMap<IVariable, IConstant> tmp = new LinkedHashMap<IVariable, IConstant>( // current().size()); // // /* // * 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.put(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 LinkedHashMap<IVariable, IConstant> 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 (IVariable<?> var : vars) { // for each projected variable. // // // The binding from the old symbol table. // final IConstant val = old.get(var); // // if (val != null) { // if the binding was projected // // set(var, val); // then copy to revealed symbol table. // // } // // } // // hash = 0; // // } // public void push() { // // // The current symbol table. // final LinkedHashMap<IVariable, IConstant> cur = current(); // // // Create a new symbol table. // final LinkedHashMap<IVariable, IConstant> tmp = new LinkedHashMap<IVariable, IConstant>( // cur.size()); // // // Push the new symbol table onto the stack. // stack.push(tmp); // // /* // * Make a copy of each entry in the symbol table which was on the top of // * the stack when we entered this method, inserting the entries into the // * new symbol table as we go. This avoids side effects of mutation on // * the nested symbol tables and also ensures that we do not need to read // * through to the nested symbol tables when answering a query about the // * current symbol table. The only down side of this is that naive // * serialization is that much less compact. // */ // for (Map.Entry<IVariable, IConstant> e : cur.entrySet()) { // // tmp.put(e.getKey(), e.getValue()); // // } // // } // public void pop(final boolean save) { // // 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 IllegalArgumentException(); // } // // // Pop the symbol table off of the top of the stack. // final LinkedHashMap<IVariable,IConstant> old = stack.pop(); // // if (save) { // // // discard the current symbol table. // stack.pop(); // // // replacing it with the symbol table which we popped off the stack. // stack.push(old); // // } else { // // // clear the hash code. // hash = 0; // // } // // } /** * New empty binding set. */ public HashBindingSet() { // /* // * Start with a capacity of ONE (1) and expand only as required. // */ // stack = new LightStack<LinkedHashMap<IVariable, IConstant>>(1); // // stack.push(new LinkedHashMap<IVariable, IConstant>()); current = new LinkedHashMap<IVariable, IConstant>(); } /** * Copy constructor (used by clone, copy). * * @param src */ protected HashBindingSet(final HashBindingSet src, final IVariable[] variablesToKeep) { // final int stackSize = src.stack.size(); // // stack = new LightStack<LinkedHashMap<IVariable,IConstant>>(stackSize); // // int depth = 1; // // for (LinkedHashMap<IVariable, IConstant> 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 LinkedHashMap<IVariable,IConstant> tmp = copy(srcLst, // depth == stackSize ? variablesToKeep : null); // // // Push onto the stack. // stack.push(tmp); // // } current = copy(src.current, variablesToKeep); } private HashBindingSet(LinkedHashMap<IVariable,IConstant> contents) { current = contents; } /** * Return a copy of the source list. * * @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. */ private LinkedHashMap<IVariable, IConstant> copy( final LinkedHashMap<IVariable, IConstant> src, final IVariable[] variablesToKeep) { final LinkedHashMap<IVariable, IConstant> dst = new LinkedHashMap<IVariable, IConstant>( variablesToKeep != null ? variablesToKeep.length : src.size()); final Iterator<Map.Entry<IVariable, IConstant>> itr = src.entrySet() .iterator(); while (itr.hasNext()) { final Map.Entry<IVariable, IConstant> e = itr.next(); boolean keep = true; if (variablesToKeep != null) { keep = false; for (IVariable<?> x : variablesToKeep) { if (x == e.getKey()) { keep = true; break; } } } if (keep) dst.put(e.getKey(), e.getValue()); } return dst; } /** * Return a copy of the source list minus entries assigning error values * (Constant.errorValue() or its copies). . * * @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. */ private LinkedHashMap<IVariable, IConstant> copyMinusErrors( final LinkedHashMap<IVariable, IConstant> src, final IVariable[] variablesToKeep) { final LinkedHashMap<IVariable, IConstant> dst = new LinkedHashMap<IVariable, IConstant>( variablesToKeep != null ? variablesToKeep.length : src.size()); final Iterator<Map.Entry<IVariable, IConstant>> itr = src.entrySet() .iterator(); while (itr.hasNext()) { final Map.Entry<IVariable, IConstant> e = itr.next(); if (e.getValue() == Constant.errorValue()) { continue; } boolean keep = true; if (variablesToKeep != null) { keep = false; for (IVariable<?> x : variablesToKeep) { if (x == e.getKey()) { keep = true; break; } } } if (keep) { dst.put(e.getKey(), e.getValue()); } } return dst; } // copyMinusErrors(..) /** * Package private constructor used by the unit tests. * * @param src */ HashBindingSet(final IBindingSet src) { this(); final Iterator<Map.Entry<IVariable, IConstant>> itr = src.iterator(); while (itr.hasNext()) { final Map.Entry<IVariable, IConstant> e = itr.next(); set(e.getKey(), e.getValue()); } } /** * Package private constructor used by the unit tests. * @param vars * @param vals */ HashBindingSet(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]); } } public HashBindingSet clone() { return new HashBindingSet(this, null /* variablesToKeep */); } public HashBindingSet copy(final IVariable[] variablesToKeep) { return new HashBindingSet(this/* src */, variablesToKeep); } @Override public IBindingSet copyMinusErrors(final IVariable[] variablesToKeep) { return new HashBindingSet(copyMinusErrors(this.current, variablesToKeep)); } /** * @return true if this IBindingSet contains an assignment of an error value */ @Override public final boolean containsErrorValues() { for (IConstant val : this.current.values()) { if (val == Constant.errorValue()) return true; } return false; } public boolean isBound(final IVariable var) { if (var == null) throw new IllegalArgumentException(); return current().containsKey(var); } public IConstant get(final IVariable var) { if (var == null) throw new IllegalArgumentException(); return current().get(var); } public void set(final IVariable var, final IConstant val) { if (var == null) throw new IllegalArgumentException(); if (val == null) throw new IllegalArgumentException(); current().put(var,val); // clear the hash code. hash = 0; } public void clear(final IVariable var) { if (var == null) throw new IllegalArgumentException(); current().remove(var); // clear the hash code. hash = 0; } public void clearAll() { current().clear(); // clear the hash code. hash = 0; } public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("{ "); int i = 0; final Iterator<Map.Entry<IVariable, IConstant>> itr = current().entrySet() .iterator(); while (itr.hasNext()) { if (i > 0) sb.append(", "); final Map.Entry<IVariable, IConstant> entry = itr.next(); sb.append(entry.getKey()); sb.append("="); sb.append(entry.getValue()); i++; } sb.append(" }"); return sb.toString(); } public Iterator<Entry<IVariable, IConstant>> iterator() { // return Collections.unmodifiableMap(current()).entrySet().iterator(); return current().entrySet().iterator(); } public Iterator<IVariable> vars() { return Collections.unmodifiableSet(current().keySet()).iterator(); } public boolean isEmpty() { return current().isEmpty(); } public int size() { return current().size(); } 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<Map.Entry<IVariable,IConstant>> itr = current().entrySet().iterator(); while(itr.hasNext()) { final Map.Entry<IVariable,IConstant> 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; for(IConstant<?> c : current().values()) { if (c == null) continue; result ^= c.hashCode(); } hash = result; } return hash; } /** * Note: This hash code MUST be invalidate on mutation! */ private int hash; }