org.apache.accumulo.examples.wikisearch.iterator.BooleanLogicIterator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.accumulo.examples.wikisearch.iterator.BooleanLogicIterator.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.accumulo.examples.wikisearch.iterator;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Set;

import org.apache.accumulo.core.data.ByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Range;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.iterators.IteratorEnvironment;
import org.apache.accumulo.core.iterators.OptionDescriber;
import org.apache.accumulo.core.iterators.SortedKeyValueIterator;
import org.apache.accumulo.examples.wikisearch.parser.JexlOperatorConstants;
import org.apache.accumulo.examples.wikisearch.parser.QueryParser;
import org.apache.accumulo.examples.wikisearch.parser.QueryParser.QueryTerm;
import org.apache.accumulo.examples.wikisearch.parser.RangeCalculator.RangeBounds;
import org.apache.accumulo.examples.wikisearch.parser.TreeNode;
import org.apache.accumulo.examples.wikisearch.util.FieldIndexKeyParser;
import org.apache.commons.jexl2.parser.ASTAndNode;
import org.apache.commons.jexl2.parser.ASTEQNode;
import org.apache.commons.jexl2.parser.ASTERNode;
import org.apache.commons.jexl2.parser.ASTGENode;
import org.apache.commons.jexl2.parser.ASTGTNode;
import org.apache.commons.jexl2.parser.ASTJexlScript;
import org.apache.commons.jexl2.parser.ASTLENode;
import org.apache.commons.jexl2.parser.ASTLTNode;
import org.apache.commons.jexl2.parser.ASTNENode;
import org.apache.commons.jexl2.parser.ASTNRNode;
import org.apache.commons.jexl2.parser.ASTNotNode;
import org.apache.commons.jexl2.parser.ASTOrNode;
import org.apache.commons.jexl2.parser.ParseException;
import org.apache.commons.jexl2.parser.ParserTreeConstants;
import org.apache.hadoop.io.Text;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.google.common.collect.Multimap;

public class BooleanLogicIterator implements SortedKeyValueIterator<Key, Value>, OptionDescriber {

    private static final Collection<ByteSequence> EMPTY_COL_FAMS = new ArrayList<ByteSequence>();
    protected static final Logger log = Logger.getLogger(BooleanLogicIterator.class);
    public static final String QUERY_OPTION = "expr";
    public static final String TERM_CARDINALITIES = "TERM_CARDINALITIES"; // comma separated list of term : count
    public static final String FIELD_INDEX_QUERY = "FIELD_INDEX_QUERY";
    public static final String FIELD_NAME_PREFIX = "fi\0";
    // --------------------------------------------------------------------------
    private static IteratorEnvironment env = new DefaultIteratorEnvironment();
    protected Text nullText = new Text();
    private Key topKey = null;
    private Value topValue = null;
    private SortedKeyValueIterator<Key, Value> sourceIterator;
    private BooleanLogicTreeNode root;
    private PriorityQueue<BooleanLogicTreeNode> positives;
    private ArrayList<BooleanLogicTreeNode> negatives = new ArrayList<BooleanLogicTreeNode>();
    private ArrayList<BooleanLogicTreeNode> rangerators;
    private String updatedQuery;
    private Map<String, Long> termCardinalities = new HashMap<String, Long>();
    private Range overallRange = null;
    private FieldIndexKeyParser keyParser;

    public BooleanLogicIterator() {
        keyParser = new FieldIndexKeyParser();
        rangerators = new ArrayList<BooleanLogicTreeNode>();
    }

    public BooleanLogicIterator(BooleanLogicIterator other, IteratorEnvironment env) {
        if (other.sourceIterator != null) {
            this.sourceIterator = other.sourceIterator.deepCopy(env);
        }
        keyParser = new FieldIndexKeyParser();
        rangerators = new ArrayList<BooleanLogicTreeNode>();
        log.debug("Congratulations, you've reached the BooleanLogicIterator");
    }

    public static void setLogLevel(Level lev) {
        log.setLevel(lev);
    }

    public void setDebug(Level lev) {
        log.setLevel(lev);
    }

    public SortedKeyValueIterator<Key, Value> deepCopy(IteratorEnvironment env) {
        return new BooleanLogicIterator(this, env);
    }

    /**
     * <b>init</b> is responsible for setting up the iterator. It will pull the serialized boolean parse tree from the options mapping and construct the
     * appropriate sub-iterators
     * 
     * Once initialized, this iterator will automatically seek to the first matching instance. If no top key exists, that means an event matching the boolean
     * logic did not exist in the partition. Subsequent calls to next will move the iterator and all sub-iterators to the next match.
     * 
     * @param source
     *          The underlying SortedkeyValueIterator.
     * @param options
     *          A Map<String, String> of options.
     * @param env
     *          The iterator environment
     * @throws IOException
     */
    public void init(SortedKeyValueIterator<Key, Value> source, Map<String, String> options,
            IteratorEnvironment env) throws IOException {
        validateOptions(options);
        try {
            if (log.isDebugEnabled()) {
                log.debug("Congratulations, you've reached the BooleanLogicIterator.init method");
            }
            // Copy the source iterator
            sourceIterator = source.deepCopy(env);

            // Potentially take advantage of term cardinalities
            String[] terms = null;
            if (null != options.get(TERM_CARDINALITIES)) {
                terms = options.get(TERM_CARDINALITIES).split(",");
                for (String term : terms) {
                    int idx = term.indexOf(":");
                    if (-1 != idx) {
                        termCardinalities.put(term.substring(0, idx), Long.parseLong(term.substring(idx + 1)));
                    }
                }
            }

            // Step 1: Parse the query
            if (log.isDebugEnabled()) {
                log.debug("QueryParser");
            }
            QueryParser qp = new QueryParser();
            qp.execute(this.updatedQuery); // validateOptions updates the updatedQuery

            // need to build the query tree based on jexl parsing.
            // Step 2: refactor QueryTree - inplace modification
            if (log.isDebugEnabled()) {
                log.debug("transformTreeNode");
            }
            TreeNode tree = qp.getIteratorTree();
            this.root = transformTreeNode(tree);

            if (log.isDebugEnabled()) {
                log.debug("refactorTree");
            }
            this.root = refactorTree(this.root);

            if (log.isDebugEnabled()) {
                log.debug("collapseBranches");
            }
            collapseBranches(root);

            // Step 3: create iterators where we need them.
            createIteratorTree(this.root);
            if (log.isDebugEnabled()) {
                log.debug("Query tree after iterator creation:\n\t" + this.root.getContents());
            }
            // Step 4: split the positive and negative leaves
            splitLeaves(this.root);

        } catch (ParseException ex) {
            log.error("ParseException in init: " + ex);
            throw new IllegalArgumentException("Failed to parse query", ex);
        } catch (Exception ex) {
            throw new IllegalArgumentException("probably had no indexed terms", ex);
        }

    }

    /* *************************************************************************
     * Methods for sub iterator creation.
     */
    private void createIteratorTree(BooleanLogicTreeNode root) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("BoolLogic createIteratorTree()");
        }
        // Walk the tree, if all of your children are leaves, roll you into the
        // appropriate iterator.
        Enumeration<?> dfe = root.depthFirstEnumeration();

        while (dfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) dfe.nextElement();
            if (!node.isLeaf() && node.getType() != ParserTreeConstants.JJTJEXLSCRIPT) {
                // try to roll up.
                if (canRollUp(node)) {
                    node.setRollUp(true);
                    if (node.getType() == ParserTreeConstants.JJTANDNODE) {
                        if (log.isDebugEnabled()) {
                            log.debug("creating IntersectingIterator");
                        }
                        node.setUserObject(createIntersectingIterator(node));
                    } else if (node.getType() == ParserTreeConstants.JJTORNODE) {
                        node.setUserObject(createOrIterator(node));
                    } else {
                        // throw an error.
                        log.debug("createIteratorTree, encounterd a node type I do not know about: "
                                + node.getType());
                        log.debug("createIteratorTree, node contents:  " + node.getContents());
                    }
                    node.removeAllChildren();
                }
            }
        }

        // now for remaining leaves, create basic iterators.
        // you can add in specialized iterator mappings here if necessary.
        dfe = root.depthFirstEnumeration();
        while (dfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) dfe.nextElement();
            if (node.isLeaf() && node.getType() != ParserTreeConstants.JJTANDNODE
                    && node.getType() != ParserTreeConstants.JJTORNODE) {
                node.setUserObject(createFieldIndexIterator(node));
            }
        }
    }

    private AndIterator createIntersectingIterator(BooleanLogicTreeNode node) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("createIntersectingIterator(node)");
            log.debug("fName: " + node.getFieldName() + " , fValue: " + node.getFieldValue() + " , operator: "
                    + node.getFieldOperator());
        }
        Text[] columnFamilies = new Text[node.getChildCount()];
        Text[] termValues = new Text[node.getChildCount()];
        boolean[] negationMask = new boolean[node.getChildCount()];
        Enumeration<?> children = node.children();
        int i = 0;
        while (children.hasMoreElements()) {
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();
            columnFamilies[i] = child.getFieldName();
            termValues[i] = child.getFieldValue();
            negationMask[i] = child.isNegated();
            i++;
        }

        AndIterator ii = new AndIterator();
        Map<String, String> options = new HashMap<String, String>();
        options.put(AndIterator.columnFamiliesOptionName, AndIterator.encodeColumns(columnFamilies));
        options.put(AndIterator.termValuesOptionName, AndIterator.encodeTermValues(termValues));
        options.put(AndIterator.notFlagsOptionName, AndIterator.encodeBooleans(negationMask));

        ii.init(sourceIterator.deepCopy(env), options, env);
        return ii;
    }

    private OrIterator createOrIterator(BooleanLogicTreeNode node) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("createOrIterator(node)");
            log.debug("fName: " + node.getFieldName() + " , fValue: " + node.getFieldValue() + " , operator: "
                    + node.getFieldOperator());
        }

        Enumeration<?> children = node.children();
        ArrayList<Text> fams = new ArrayList<Text>();
        ArrayList<Text> quals = new ArrayList<Text>();
        while (children.hasMoreElements()) {
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();
            fams.add(child.getFieldName());
            quals.add(child.getFieldValue());
        }

        OrIterator iter = new OrIterator();
        SortedKeyValueIterator<Key, Value> source = sourceIterator.deepCopy(env);
        for (int i = 0; i < fams.size(); i++) {
            iter.addTerm(source, fams.get(i), quals.get(i), env);
        }

        return iter;
    }

    /*
     * This takes the place of the SortedKeyIterator used previously. This iterator is bound to the partitioned table structure. When next is called it will jump
     * rows as necessary internally versus needing to do it externally as was the case with the SortedKeyIterator.
     */
    private FieldIndexIterator createFieldIndexIterator(BooleanLogicTreeNode node) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("BoolLogic.createFieldIndexIterator()");
            log.debug("fName: " + node.getFieldName() + " , fValue: " + node.getFieldValue() + " , operator: "
                    + node.getFieldOperator());
        }
        Text rowId = null;
        sourceIterator.seek(new Range(), EMPTY_COL_FAMS, false);
        if (sourceIterator.hasTop()) {
            rowId = sourceIterator.getTopKey().getRow();
        }

        FieldIndexIterator iter = new FieldIndexIterator(node.getType(), rowId, node.getFieldName(),
                node.getFieldValue(), node.isNegated(), node.getFieldOperator());

        Map<String, String> options = new HashMap<String, String>();
        iter.init(sourceIterator.deepCopy(env), options, env);
        if (log.isDebugEnabled()) {
            FieldIndexIterator.setLogLevel(Level.DEBUG);
        } else {
            FieldIndexIterator.setLogLevel(Level.OFF);
        }

        return iter;
    }

    /* *************************************************************************
     * Methods for testing the tree WRT boolean logic.
     */
    // After all iterator pointers have been advanced, test if the current
    // record passes the boolean logic.
    private boolean testTreeState() {
        if (log.isDebugEnabled()) {
            log.debug("BoolLogic testTreeState() begin");
        }
        Enumeration<?> dfe = this.root.depthFirstEnumeration();
        while (dfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) dfe.nextElement();
            if (!node.isLeaf()) {

                int type = node.getType();
                if (type == ParserTreeConstants.JJTANDNODE) { // BooleanLogicTreeNode.NodeType.AND) {
                    handleAND(node);
                } else if (type == ParserTreeConstants.JJTORNODE) {// BooleanLogicTreeNode.NodeType.OR) {
                    handleOR(node);
                } else if (type == ParserTreeConstants.JJTJEXLSCRIPT) {// BooleanLogicTreeNode.NodeType.HEAD) {
                    handleHEAD(node);
                } else if (type == ParserTreeConstants.JJTNOTNODE) { // BooleanLogicTreeNode.NodeType.NOT) {
                    // there should not be any "NOT"s.
                    // throw new Exception();
                }
            } else {
                // it is a leaf, if it is an AND or OR do something
                if (node.getType() == ParserTreeConstants.JJTORNODE) {// BooleanLogicTreeNode.NodeType.OR) { //OrIterator
                    node.setValid(node.hasTop());
                    node.reSet();
                    node.addToSet(node.getTopKey());

                } else if (node.getType() == ParserTreeConstants.JJTANDNODE
                        || node.getType() == ParserTreeConstants.JJTEQNODE
                        || node.getType() == ParserTreeConstants.JJTERNODE
                        || node.getType() == ParserTreeConstants.JJTLENODE
                        || node.getType() == ParserTreeConstants.JJTLTNODE
                        || node.getType() == ParserTreeConstants.JJTGENODE
                        || node.getType() == ParserTreeConstants.JJTGTNODE) {
                    // sub iterator guarantees it is in its internal range,
                    // otherwise, no top.
                    node.setValid(node.hasTop());
                }
            }
        }

        if (log.isDebugEnabled()) {
            log.debug("BoolLogic.testTreeState end, treeState:: " + this.root.getContents() + "  , valid: "
                    + root.isValid());
        }
        return this.root.isValid();
    }

    private void handleHEAD(BooleanLogicTreeNode node) {
        Enumeration<?> children = node.children();
        while (children.hasMoreElements()) {
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();

            if (child.getType() == ParserTreeConstants.JJTANDNODE) {// BooleanLogicTreeNode.NodeType.AND) {
                node.setValid(child.isValid());
                node.setTopKey(child.getTopKey());
            } else if (child.getType() == ParserTreeConstants.JJTORNODE) {// BooleanLogicTreeNode.NodeType.OR) {
                node.setValid(child.isValid());
                node.setTopKey(child.getTopKey());
            } else if (child.getType() == ParserTreeConstants.JJTEQNODE
                    || child.getType() == ParserTreeConstants.JJTERNODE
                    || child.getType() == ParserTreeConstants.JJTGTNODE
                    || child.getType() == ParserTreeConstants.JJTGENODE
                    || child.getType() == ParserTreeConstants.JJTLTNODE
                    || child.getType() == ParserTreeConstants.JJTLENODE) {// BooleanLogicTreeNode.NodeType.SEL) {
                node.setValid(true);
                node.setTopKey(child.getTopKey());
                if (child.getTopKey() == null) {
                    node.setValid(false);
                }
            }
        } // end while

        // I have to be valid AND have a top key
        if (node.isValid() && !node.hasTop()) {
            node.setValid(false);
        }
    }

    private void handleAND(BooleanLogicTreeNode me) {
        if (log.isDebugEnabled()) {
            log.debug("handleAND::" + me.getContents());
        }
        Enumeration<?> children = me.children();
        me.setValid(true); // it's easier to prove false than true

        HashSet<Key> goodSet = new HashSet<Key>();
        HashSet<Key> badSet = new HashSet<Key>();
        while (children.hasMoreElements()) {
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();

            if (child.getType() == ParserTreeConstants.JJTEQNODE
                    || child.getType() == ParserTreeConstants.JJTANDNODE
                    || child.getType() == ParserTreeConstants.JJTERNODE
                    || child.getType() == ParserTreeConstants.JJTNENODE
                    || child.getType() == ParserTreeConstants.JJTGENODE
                    || child.getType() == ParserTreeConstants.JJTLENODE
                    || child.getType() == ParserTreeConstants.JJTGTNODE
                    || child.getType() == ParserTreeConstants.JJTLTNODE) {

                if (child.isNegated()) {
                    if (child.hasTop()) {
                        badSet.add(child.getTopKey());
                        if (goodSet.contains(child.getTopKey())) {
                            me.setValid(false);
                            return;
                        }
                        if (child.isValid()) {
                            me.setValid(false);
                            return;
                        }
                    }
                } else {
                    if (child.hasTop()) {
                        if (log.isDebugEnabled()) {
                            log.debug("handleAND, child node: " + child.getContents());
                        }
                        // if you're in the bad set, you're done.
                        if (badSet.contains(child.getTopKey())) {
                            if (log.isDebugEnabled()) {
                                log.debug("handleAND, child is in bad set, setting parent false");
                            }
                            me.setValid(false);
                            return;
                        }

                        // if good set is empty, add it.
                        if (goodSet.isEmpty()) {
                            if (log.isDebugEnabled()) {
                                log.debug("handleAND, goodSet is empty, adding child: " + child.getContents());
                            }
                            goodSet.add(child.getTopKey());
                        } else {
                            // must be in the good set & not in the bad set
                            // if either fails, I'm false.
                            if (!goodSet.contains(child.getTopKey())) {
                                if (log.isDebugEnabled()) {
                                    log.debug(
                                            "handleAND, goodSet is not empty, and does NOT contain child, setting false.  child: "
                                                    + child.getContents());
                                }
                                me.setValid(false);
                                return;
                            } else {
                                // trim the good set to this one value
                                // (handles the case were the initial encounters were ORs)
                                goodSet = new HashSet<Key>();
                                goodSet.add(child.getTopKey());
                                if (log.isDebugEnabled()) {
                                    log.debug("handleAND, child in goodset, trim to this value: "
                                            + child.getContents());
                                }
                            }
                        }
                    } else {
                        // test if its children are all false
                        if (child.getChildCount() > 0) {
                            Enumeration<?> subchildren = child.children();
                            boolean allFalse = true;
                            while (subchildren.hasMoreElements()) {
                                BooleanLogicTreeNode subchild = (BooleanLogicTreeNode) subchildren.nextElement();
                                if (!subchild.isNegated()) {
                                    allFalse = false;
                                    break;
                                } else if (subchild.isNegated() && subchild.hasTop()) {
                                    allFalse = false;
                                    break;
                                }
                            }
                            if (!allFalse) {
                                me.setValid(false);
                                return;
                            }
                        } else {
                            // child returned a null value and is not a negation, this in turn makes me false.
                            me.setValid(false);
                            return;
                        }
                    }
                }

            } else if (child.getType() == ParserTreeConstants.JJTORNODE) {// BooleanLogicTreeNode.NodeType.OR) {

                // NOTE: The OR may be an OrIterator in which case it will only produce
                // a single unique identifier, or it may be a pure logical construct and
                // be capable of producing multiple unique identifiers.
                // This should handle all cases.
                Iterator<?> iter = child.getSetIterator();
                boolean goodSetEmpty = goodSet.isEmpty();
                boolean matchedOne = false;
                boolean pureNegations = true;
                if (!child.isValid()) {
                    if (log.isDebugEnabled()) {
                        log.debug("handleAND, child is an OR and it is not valid, setting false, ALL NEGATED?: "
                                + child.isChildrenAllNegated());
                    }
                    me.setValid(false); // I'm an AND if one of my children is false, I'm false.
                    return;
                } else if (child.isValid() && !child.hasTop()) {
                    // pure negation, do nothing
                } else if (child.isValid() && child.hasTop()) { // I need to match one
                    if (log.isDebugEnabled()) {
                        log.debug("handleAND, child OR, valid and has top, means not pureNegations");
                    }
                    pureNegations = false;
                    while (iter.hasNext()) {
                        Key i = (Key) iter.next();
                        if (child.isNegated()) {
                            badSet.add(i);
                            if (goodSet.contains(i)) {
                                if (log.isDebugEnabled()) {
                                    log.debug("handleAND, child OR, goodSet contains bad value: " + i);
                                }
                                me.setValid(false);
                                return;
                            }
                        } else {
                            // if the good set is empty, then push all of my ids.
                            if (goodSetEmpty && !badSet.contains(i)) {
                                goodSet.add(i);
                                matchedOne = true;
                            } else {
                                // I need at least one to match
                                if (goodSet.contains(i)) {
                                    matchedOne = true;
                                }
                            }
                        }
                    }
                }

                // is the goodSet still empty? that means were were only negations
                // otherwise, if it's not empty and we didn't match one, false
                if (child.isNegated()) {
                    // we're ok
                } else {
                    if (goodSet.isEmpty() && !pureNegations) {
                        if (log.isDebugEnabled()) {
                            log.debug("handleAND, child OR, empty goodset && !pureNegations, set false");
                        }
                        // that's bad, we weren't negated, should've pushed something in there.
                        me.setValid(false);
                        return;
                    } else if (!goodSet.isEmpty() && !pureNegations) { // goodSet contains values.
                        if (!matchedOne) { // but we didn't match any.
                            if (log.isDebugEnabled()) {
                                log.debug("handleAND, child OR, goodSet had values but I didn't match any, false");
                            }
                            me.setValid(false);
                            return;
                        }

                        // we matched something, trim the set.
                        // i.e. two child ORs
                        goodSet = child.getIntersection(goodSet);
                    }
                }

            }
        } // end while

        if (goodSet.isEmpty()) { // && log.isDebugEnabled()) {
            if (log.isDebugEnabled()) {
                log.debug("handleAND-> goodSet is empty, pure negations?");
            }
        } else {
            me.setTopKey(Collections.min(goodSet));
            if (log.isDebugEnabled()) {
                log.debug("End of handleAND, this node's topKey: " + me.getTopKey());
            }
        }
    }

    private void handleOR(BooleanLogicTreeNode me) {
        Enumeration<?> children = me.children();
        // I'm an OR node, need at least one positive.
        me.setValid(false);
        me.reSet();
        me.setTopKey(null);
        boolean allNegated = true;
        while (children.hasMoreElements()) {
            // 3 cases for child: SEL, AND, OR
            // and negation
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();
            if (child.getType() == ParserTreeConstants.JJTEQNODE || child.getType() == ParserTreeConstants.JJTNENODE
                    || child.getType() == ParserTreeConstants.JJTANDNODE
                    || child.getType() == ParserTreeConstants.JJTERNODE
                    || child.getType() == ParserTreeConstants.JJTNRNODE
                    || child.getType() == ParserTreeConstants.JJTLENODE
                    || child.getType() == ParserTreeConstants.JJTLTNODE
                    || child.getType() == ParserTreeConstants.JJTGENODE
                    || child.getType() == ParserTreeConstants.JJTGTNODE) {

                if (child.hasTop()) {
                    if (child.isNegated()) {
                        // do nothing.
                    } else {
                        allNegated = false;
                        // I have something add it to my set.
                        if (child.isValid()) {
                            me.addToSet(child.getTopKey());
                        }
                    }
                } else if (!child.isNegated()) { // I have a non-negated child
                    allNegated = false;
                    // that child could be pure negations in which case I'm true
                    me.setValid(child.isValid());
                }

            } else if (child.getType() == ParserTreeConstants.JJTORNODE) {// BooleanLogicTreeNode.NodeType.OR) {
                if (child.hasTop()) {
                    if (!child.isNegated()) {
                        allNegated = false;
                        // add its rowIds to my rowIds
                        Iterator<?> iter = child.getSetIterator();
                        while (iter.hasNext()) {
                            Key i = (Key) iter.next();
                            if (i != null) {
                                me.addToSet(i);
                            }
                        }
                    }
                } else {
                    // Or node that doesn't have a top, check if it's valid or not
                    // because it could be pure negations itself.
                    if (child.isValid()) {
                        me.setValid(true);
                    }
                }
            }
        } // end while

        if (allNegated) {
            // do all my children have top?
            children = me.children();
            while (children.hasMoreElements()) {
                BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();
                if (!child.hasTop()) {
                    me.setValid(true);
                    me.setTopKey(null);
                    return;
                }
            }
            me.setValid(false);

        } else {
            Key k = me.getMinUniqueID();
            if (k == null) {
                me.setValid(false);
            } else {
                me.setValid(true);
                me.setTopKey(k);
            }
        }
    }

    /* *************************************************************************
     * Utility methods.
     */
    // Transforms the TreeNode tree of query.parser into the
    // BooleanLogicTreeNodeJexl form.
    public BooleanLogicTreeNode transformTreeNode(TreeNode node) throws ParseException {
        if (node.getType().equals(ASTEQNode.class) || node.getType().equals(ASTNENode.class)) {
            if (log.isDebugEnabled()) {
                log.debug("Equals Node");
            }

            Multimap<String, QueryTerm> terms = node.getTerms();
            for (String fName : terms.keySet()) {
                Collection<QueryTerm> values = terms.get(fName);

                for (QueryTerm t : values) {
                    if (null == t || null == t.getValue()) {
                        continue;
                    }
                    String fValue = t.getValue().toString();
                    fValue = fValue.replaceAll("'", "");
                    boolean negated = t.getOperator().equals("!=");

                    if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                        fName = FIELD_NAME_PREFIX + fName;
                    }
                    BooleanLogicTreeNode child = new BooleanLogicTreeNode(ParserTreeConstants.JJTEQNODE, fName,
                            fValue, negated);
                    return child;
                }
            }
        }

        if (node.getType().equals(ASTERNode.class) || node.getType().equals(ASTNRNode.class)) {
            if (log.isDebugEnabled()) {
                log.debug("Regex Node");
            }

            Multimap<String, QueryTerm> terms = node.getTerms();
            for (String fName : terms.keySet()) {
                Collection<QueryTerm> values = terms.get(fName);
                for (QueryTerm t : values) {
                    if (null == t || null == t.getValue()) {
                        continue;
                    }
                    String fValue = t.getValue().toString();
                    fValue = fValue.replaceAll("'", "");
                    boolean negated = node.getType().equals(ASTNRNode.class);

                    if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                        fName = FIELD_NAME_PREFIX + fName;
                    }

                    BooleanLogicTreeNode child = new BooleanLogicTreeNode(ParserTreeConstants.JJTERNODE, fName,
                            fValue, negated);
                    return child;
                }
            }
        }

        if (node.getType().equals(ASTLTNode.class) || node.getType().equals(ASTLENode.class)
                || node.getType().equals(ASTGTNode.class) || node.getType().equals(ASTGENode.class)) {
            Multimap<String, QueryTerm> terms = node.getTerms();
            for (String fName : terms.keySet()) {
                Collection<QueryTerm> values = terms.get(fName);

                if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                    fName = FIELD_NAME_PREFIX + fName;
                }
                for (QueryTerm t : values) {
                    if (null == t || null == t.getValue()) {
                        continue;
                    }
                    String fValue = t.getValue().toString();
                    fValue = fValue.replaceAll("'", "").toLowerCase();
                    boolean negated = false; // to be negated, must be child of Not, which is handled elsewhere.
                    int mytype = JexlOperatorConstants.getJJTNodeType(t.getOperator());

                    BooleanLogicTreeNode child = new BooleanLogicTreeNode(mytype, fName, fValue, negated);
                    if (log.isDebugEnabled()) {
                        log.debug("adding child node: " + child.getContents());
                    }
                    return child;
                }
            }
        }

        BooleanLogicTreeNode returnNode = null;

        if (node.getType().equals(ASTAndNode.class) || node.getType().equals(ASTOrNode.class)) {
            int parentType = node.getType().equals(ASTAndNode.class) ? ParserTreeConstants.JJTANDNODE
                    : ParserTreeConstants.JJTORNODE;
            if (log.isDebugEnabled()) {
                log.debug("AND/OR node: " + parentType);
            }
            if (node.isLeaf() || !node.getTerms().isEmpty()) {
                returnNode = new BooleanLogicTreeNode(parentType);
                Multimap<String, QueryTerm> terms = node.getTerms();
                for (String fName : terms.keySet()) {
                    Collection<QueryTerm> values = terms.get(fName);
                    if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                        fName = FIELD_NAME_PREFIX + fName;
                    }
                    for (QueryTerm t : values) {
                        if (null == t || null == t.getValue()) {
                            continue;
                        }
                        String fValue = t.getValue().toString();
                        fValue = fValue.replaceAll("'", "");
                        boolean negated = t.getOperator().equals("!=");
                        int mytype = JexlOperatorConstants.getJJTNodeType(t.getOperator());
                        BooleanLogicTreeNode child = new BooleanLogicTreeNode(mytype, fName, fValue, negated);
                        if (log.isDebugEnabled()) {
                            log.debug("adding child node: " + child.getContents());
                        }
                        returnNode.add(child);
                    }
                }
            } else {
                returnNode = new BooleanLogicTreeNode(parentType);

            }
        } else if (node.getType().equals(ASTNotNode.class)) {
            if (log.isDebugEnabled()) {
                log.debug("NOT node");
            }
            if (node.isLeaf()) {
                // NOTE: this should be cleaned up a bit.
                Multimap<String, QueryTerm> terms = node.getTerms();
                for (String fName : terms.keySet()) {
                    Collection<QueryTerm> values = terms.get(fName);

                    if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                        fName = FIELD_NAME_PREFIX + fName;
                    }
                    for (QueryTerm t : values) {
                        if (null == t || null == t.getValue()) {
                            continue;
                        }
                        String fValue = t.getValue().toString();
                        fValue = fValue.replaceAll("'", "").toLowerCase();
                        boolean negated = !t.getOperator().equals("!=");
                        int mytype = JexlOperatorConstants.getJJTNodeType(t.getOperator());

                        if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                            fName = FIELD_NAME_PREFIX + fName;
                        }
                        return new BooleanLogicTreeNode(mytype, fName, fValue, negated);
                    }
                }
            } else {
                returnNode = new BooleanLogicTreeNode(ParserTreeConstants.JJTNOTNODE);
            }
        } else if (node.getType().equals(ASTJexlScript.class)
                || node.getType().getSimpleName().equals("RootNode")) {

            if (log.isDebugEnabled()) {
                log.debug("ROOT/JexlScript node");
            }
            if (node.isLeaf()) {
                returnNode = new BooleanLogicTreeNode(ParserTreeConstants.JJTJEXLSCRIPT);
                // NOTE: this should be cleaned up a bit.
                Multimap<String, QueryTerm> terms = node.getTerms();
                for (String fName : terms.keySet()) {
                    Collection<QueryTerm> values = terms.get(fName);

                    if (!fName.startsWith(FIELD_NAME_PREFIX)) {
                        fName = FIELD_NAME_PREFIX + fName;
                    }
                    for (QueryTerm t : values) {
                        if (null == t || null == t.getValue()) {
                            continue;
                        }
                        String fValue = t.getValue().toString();
                        fValue = fValue.replaceAll("'", "").toLowerCase();
                        boolean negated = t.getOperator().equals("!=");
                        int mytype = JexlOperatorConstants.getJJTNodeType(t.getOperator());

                        BooleanLogicTreeNode child = new BooleanLogicTreeNode(mytype, fName, fValue, negated);
                        returnNode.add(child);
                        return returnNode;
                    }
                }
            } else {
                returnNode = new BooleanLogicTreeNode(ParserTreeConstants.JJTJEXLSCRIPT);
            }
        } else {
            log.error("Currently Unsupported Node type: " + node.getClass().getName() + " \t" + node.getType());
        }
        for (TreeNode child : node.getChildren()) {
            returnNode.add(transformTreeNode(child));
        }

        return returnNode;
    }

    // After tree conflicts have been resolve, we can collapse branches where
    // leaves have been pruned.
    public static void collapseBranches(BooleanLogicTreeNode myroot) throws Exception {

        // NOTE: doing a depth first enumeration didn't wory when I started
        // removing nodes halfway through. The following method does work,
        // it's essentially a reverse breadth first traversal.
        List<BooleanLogicTreeNode> nodes = new ArrayList<BooleanLogicTreeNode>();
        Enumeration<?> bfe = myroot.breadthFirstEnumeration();

        while (bfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) bfe.nextElement();
            nodes.add(node);
        }

        // walk backwards
        for (int i = nodes.size() - 1; i >= 0; i--) {
            BooleanLogicTreeNode node = nodes.get(i);
            if (log.isDebugEnabled()) {
                log.debug("collapseBranches, inspecting node: " + node.toString() + "  " + node.printNode());
            }

            if (node.getType() == ParserTreeConstants.JJTANDNODE
                    || node.getType() == ParserTreeConstants.JJTORNODE) {
                if (node.getChildCount() == 0 && !node.isRangeNode()) {
                    node.removeFromParent();
                } else if (node.getChildCount() == 1) {
                    BooleanLogicTreeNode p = (BooleanLogicTreeNode) node.getParent();
                    BooleanLogicTreeNode c = (BooleanLogicTreeNode) node.getFirstChild();
                    node.removeFromParent();
                    p.add(c);

                }
            } else if (node.getType() == ParserTreeConstants.JJTJEXLSCRIPT) {
                if (node.getChildCount() == 0) {
                    if (log.isDebugEnabled()) {
                        log.debug("collapseBranches, headNode has no children");
                    }
                    throw new Exception("Head node has no children.");
                }
            }
        }

    }

    public BooleanLogicTreeNode refactorTree(BooleanLogicTreeNode myroot) {
        List<BooleanLogicTreeNode> nodes = new ArrayList<BooleanLogicTreeNode>();
        Enumeration<?> bfe = myroot.breadthFirstEnumeration();

        while (bfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) bfe.nextElement();
            nodes.add(node);
        }

        // walk backwards
        for (int i = nodes.size() - 1; i >= 0; i--) {
            BooleanLogicTreeNode node = nodes.get(i);
            if (node.getType() == ParserTreeConstants.JJTANDNODE
                    || node.getType() == ParserTreeConstants.JJTORNODE) {
                // 1. check to see if all children are negated
                // 2. check to see if we have to handle ranges.

                Map<Text, RangeBounds> ranges = new HashMap<Text, RangeBounds>();
                Enumeration<?> children = node.children();
                boolean allNegated = true;
                while (children.hasMoreElements()) {
                    BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();
                    if (!child.isNegated()) {
                        allNegated = false;
                        // break;
                    }

                    // currently we are not allowing unbounded ranges, so they must sit under an AND node.
                    if (node.getType() == ParserTreeConstants.JJTANDNODE) {
                        // check for ranges
                        if (child.getType() == JexlOperatorConstants.JJTGTNODE) {
                            if (log.isDebugEnabled()) {
                                log.debug("refactor: GT " + child.getContents());
                            }
                            if (ranges.containsKey(child.getFieldName())) {
                                RangeBounds rb = ranges.get(child.getFieldName());
                                rb.setLower(child.getFieldValue());
                            } else {
                                RangeBounds rb = new RangeBounds();
                                rb.setLower(child.getFieldValue());
                                ranges.put(child.getFieldName(), rb);
                            }
                        } else if (child.getType() == JexlOperatorConstants.JJTGENODE) {
                            if (log.isDebugEnabled()) {
                                log.debug("refactor: GE " + child.getContents());
                            }
                            if (ranges.containsKey(child.getFieldName())) {
                                RangeBounds rb = ranges.get(child.getFieldName());
                                rb.setLower(child.getFieldValue());
                            } else {
                                RangeBounds rb = new RangeBounds();
                                rb.setLower(child.getFieldValue());
                                ranges.put(child.getFieldName(), rb);
                            }
                        } else if (child.getType() == JexlOperatorConstants.JJTLTNODE) {
                            if (log.isDebugEnabled()) {
                                log.debug("refactor: LT " + child.getContents());
                            }
                            if (ranges.containsKey(child.getFieldName())) {
                                RangeBounds rb = ranges.get(child.getFieldName());
                                rb.setUpper(child.getFieldValue());
                            } else {
                                RangeBounds rb = new RangeBounds();
                                rb.setUpper(child.getFieldValue());
                                ranges.put(child.getFieldName(), rb);
                            }
                        } else if (child.getType() == JexlOperatorConstants.JJTLENODE) {
                            if (log.isDebugEnabled()) {
                                log.debug("refactor: LE " + child.getContents());
                            }
                            if (ranges.containsKey(child.getFieldName())) {
                                RangeBounds rb = ranges.get(child.getFieldName());
                                rb.setUpper(child.getFieldValue());
                            } else {
                                RangeBounds rb = new RangeBounds();
                                rb.setUpper(child.getFieldValue());
                                ranges.put(child.getFieldName(), rb);
                            }
                        }
                    }
                }
                if (allNegated) {
                    node.setChildrenAllNegated(true);
                }

                // see if the AND node had a range.
                if (node.getType() == ParserTreeConstants.JJTANDNODE) {

                    // if(ranges.containsKey(node.getFieldName())){
                    if (!ranges.isEmpty()) {
                        // we have a range, process it
                        if (node.getChildCount() <= 2 && ranges.size() == 1) {
                            if (log.isDebugEnabled()) {
                                log.debug("AND range 2 children or less");
                            }
                            // only has a range, modify the node
                            node.setType(ParserTreeConstants.JJTORNODE);
                            node.removeAllChildren();
                            // RangeBounds rb = ranges.get(node.getFieldName());

                            for (Entry<Text, RangeBounds> entry : ranges.entrySet()) {
                                Text fName = entry.getKey();
                                RangeBounds rb = entry.getValue();
                                node.setFieldName(fName);
                                node.setFieldValue(new Text(""));
                                node.setLowerBound(rb.getLower());
                                node.setUpperBound(rb.getUpper());
                                node.setRangeNode(true);
                            }

                            rangerators.add(node);

                            if (log.isDebugEnabled()) {
                                log.debug("refactor: " + node.getContents());
                                log.debug("refactor: " + node.getLowerBound() + "  " + node.getUpperBound());
                            }

                        } else {
                            if (log.isDebugEnabled()) {
                                log.debug("AND range more than 2 children");
                            }
                            // node has range plus other children, create another node from the range
                            // remove lt,le,gt,ge from parent and push in a single node

                            // removing nodes via enumeration doesn't work, push into a list
                            // and walk backwards
                            List<BooleanLogicTreeNode> temp = new ArrayList<BooleanLogicTreeNode>();
                            Enumeration<?> e = node.children();
                            while (e.hasMoreElements()) {
                                BooleanLogicTreeNode c = (BooleanLogicTreeNode) e.nextElement();
                                temp.add(c);
                            }

                            for (int j = temp.size() - 1; j >= 0; j--) {
                                BooleanLogicTreeNode c = temp.get(j);
                                if (c.getType() == JexlOperatorConstants.JJTLENODE
                                        || c.getType() == JexlOperatorConstants.JJTLTNODE
                                        || c.getType() == JexlOperatorConstants.JJTGENODE
                                        || c.getType() == JexlOperatorConstants.JJTGTNODE) {
                                    c.removeFromParent();
                                }
                            }

                            for (Entry<Text, RangeBounds> entry : ranges.entrySet()) {
                                Text fName = entry.getKey();
                                BooleanLogicTreeNode nchild = new BooleanLogicTreeNode(
                                        ParserTreeConstants.JJTORNODE, fName.toString(), "");
                                RangeBounds rb = entry.getValue();
                                nchild.setFieldValue(new Text(""));
                                nchild.setLowerBound(rb.getLower());
                                nchild.setUpperBound(rb.getUpper());
                                nchild.setRangeNode(true);
                                node.add(nchild);
                                rangerators.add(nchild);
                            }

                            if (log.isDebugEnabled()) {
                                log.debug("refactor: " + node.getContents());
                            }
                        }
                    }
                }

            }
        }

        return myroot;

    }

    // If all children are of type SEL, roll this up into an AND or OR node.
    private static boolean canRollUp(BooleanLogicTreeNode parent) {
        if (log.isDebugEnabled()) {
            log.debug("canRollUp: testing " + parent.getContents());
        }
        if (parent.getChildCount() < 1) {
            if (log.isDebugEnabled()) {
                log.debug("canRollUp: child count < 1, return false");
            }
            return false;
        }
        Enumeration<?> e = parent.children();
        while (e.hasMoreElements()) {
            BooleanLogicTreeNode child = (BooleanLogicTreeNode) e.nextElement();

            if (child.getType() != ParserTreeConstants.JJTEQNODE) {// BooleanLogicTreeNode.NodeType.SEL) {
                if (log.isDebugEnabled()) {
                    log.debug("canRollUp: child.getType -> " + ParserTreeConstants.jjtNodeName[child.getType()]
                            + " int: " + child.getType() + "  return false");
                }
                return false;
            }

            if (child.isNegated()) {
                if (log.isDebugEnabled()) {
                    log.debug("canRollUp: child.isNegated, return false");
                }
                return false;
            }

            if (child.getFieldValue().toString().contains("*")) {
                if (log.isDebugEnabled()) {
                    log.debug("canRollUp: child has wildcard: " + child.getFieldValue());
                }
                return false;
            }
        }
        return true;
    }

    /**
     * Small utility function to print out the depth-first enumeration of the tree. Specify the root or sub root of the tree you wish to view.
     * 
     * @param root
     *          The root node of the tree or sub-tree.
     */
    public static void showDepthFirstTraversal(BooleanLogicTreeNode root) {
        System.out.println("DepthFirstTraversal");
        Enumeration<?> e = root.depthFirstEnumeration();
        int i = -1;
        while (e.hasMoreElements()) {
            i += 1;
            BooleanLogicTreeNode n = (BooleanLogicTreeNode) e.nextElement();
            System.out.println(i + " : " + n);
        }
    }

    public static void showBreadthFirstTraversal(BooleanLogicTreeNode root) {
        System.out.println("BreadthFirstTraversal");
        log.debug("BooleanLogicIterator.showBreadthFirstTraversal()");
        Enumeration<?> e = root.breadthFirstEnumeration();
        int i = -1;
        while (e.hasMoreElements()) {
            i += 1;
            BooleanLogicTreeNode n = (BooleanLogicTreeNode) e.nextElement();
            System.out.println(i + " : " + n);
            log.debug(i + " : " + n);
        }
    }

    private void splitLeaves(BooleanLogicTreeNode node) {
        if (log.isDebugEnabled()) {
            log.debug("BoolLogic: splitLeaves()");
        }
        positives = new PriorityQueue<BooleanLogicTreeNode>(10, new BooleanLogicTreeNodeComparator());
        // positives = new ArrayList<BooleanLogicTreeNodeJexl>();
        negatives.clear();

        Enumeration<?> dfe = node.depthFirstEnumeration();
        while (dfe.hasMoreElements()) {
            BooleanLogicTreeNode elem = (BooleanLogicTreeNode) dfe.nextElement();

            if (elem.isLeaf()) {
                if (elem.isNegated()) {
                    negatives.add(elem);
                } else {
                    positives.add(elem);
                }
            }
        }
    }

    private void reHeapPriorityQueue(BooleanLogicTreeNode node) {
        positives.clear();
        Enumeration<?> dfe = node.depthFirstEnumeration();
        BooleanLogicTreeNode elem;
        while (dfe.hasMoreElements()) {
            elem = (BooleanLogicTreeNode) dfe.nextElement();
            if (elem.isLeaf() && !elem.isNegated()) {
                positives.add(elem);
            }
        }
    }

    /* *************************************************************************
     * The iterator interface methods.
     */
    public boolean hasTop() {
        return (topKey != null);
    }

    public Key getTopKey() {
        if (log.isDebugEnabled()) {
            log.debug("getTopKey: " + topKey);
        }
        return topKey;
    }

    private void setTopKey(Key key) {
        if (this.overallRange != null && key != null) {
            if (overallRange.getEndKey() != null) { // if null end key, that means range is to the end of the tablet.
                if (!this.overallRange.contains(key)) {
                    topKey = null;
                    return;
                }
            }
        }
        topKey = key;
    }

    public Value getTopValue() {
        if (topValue == null) {
            topValue = new Value(new byte[0]);
        }
        return topValue;
    }

    private void resetNegatives() {
        for (BooleanLogicTreeNode neg : negatives) {
            neg.setTopKey(null);
            neg.setValid(true);
        }
    }

    private String getEventKeyUid(Key k) {
        if (k == null || k.getColumnFamily() == null) {
            return null;
        } else {
            return k.getColumnFamily().toString();
        }
    }

    private String getIndexKeyUid(Key k) {
        try {
            int idx = 0;
            String sKey = k.getColumnQualifier().toString();
            idx = sKey.indexOf("\0");
            return sKey.substring(idx + 1);
        } catch (Exception e) {
            return null;
        }
    }

    /*
     * Remember, the Key in the BooleanLogicTreeNode is different structurally than the Key in its sub iterator because the key BooleanLogic needs to return is an
     * event key created from the index key (which is what the sub iterators are looking at!)
     */
    private Key getOptimizedAdvanceKey() throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("getOptimizedAdvanceKey() called");
        }
        Enumeration<?> bfe = root.breadthFirstEnumeration();
        ArrayList<BooleanLogicTreeNode> bfl = new ArrayList<BooleanLogicTreeNode>();
        while (bfe.hasMoreElements()) {
            BooleanLogicTreeNode node = (BooleanLogicTreeNode) bfe.nextElement();
            if (!node.isNegated()) {
                node.setAdvanceKey(node.getTopKey());
                node.setDone(false);
                bfl.add(node);
            }
        }

        // walk the tree backwards
        for (int i = bfl.size() - 1; i >= 0; i--) {
            if (bfl.get(i).isLeaf() || bfl.get(i).isNegated()) {
                if (log.isDebugEnabled()) {
                    log.debug("leaf, isDone?: " + bfl.get(i).isDone());
                }
                continue;
            }

            BooleanLogicTreeNode node = bfl.get(i);
            node.setDone(false);
            if (log.isDebugEnabled()) {
                log.debug("for loop, node: " + node + " isDone? " + node.isDone());
            }
            if (node.getType() == ParserTreeConstants.JJTANDNODE) {
                // get max
                BooleanLogicTreeNode max = null;
                Enumeration<?> children = node.children();
                boolean firstTime = true;
                while (children.hasMoreElements()) {
                    BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();

                    if (child.isNegated() || child.isChildrenAllNegated()) {
                        continue;
                    }

                    // all advance keys were initially set from topkey for the leaves.
                    if (child.getAdvanceKey() == null) {
                        log.debug("\tchild does not advance key: " + child.printNode());
                        // i'm an and, i have a child that's done, mark me as done.
                        node.setDone(true);
                        break;
                    } else {
                        log.debug("\tchild advanceKey: " + child.getAdvanceKey());
                    }

                    if (firstTime) {
                        firstTime = false;
                        max = child;
                        if (log.isDebugEnabled()) {
                            log.debug("\tAND block, first valid child: " + child);
                        }
                        continue;
                    }

                    log.debug("\tAND block, max: " + max);
                    log.debug("\tAND block, child: " + child);

                    // first test row
                    if (max.getAdvanceKey().getRow().compareTo(child.getAdvanceKey().getRow()) < 0) {
                        max = child;
                        if (log.isDebugEnabled()) {
                            log.debug("\tAND block, child row greater, new max.");
                        }
                        continue;
                    }

                    // if rows are equal, test uids
                    String uid_max = getEventKeyUid(max.getAdvanceKey());
                    String uid_child = getEventKeyUid(child.getAdvanceKey());
                    if (log.isDebugEnabled()) {
                        if (uid_max == null) {
                            log.debug("\tuid_max is currently null");
                        } else {
                            log.debug("\tuid_max: " + uid_max);
                        }
                        if (uid_child == null) {
                            log.debug("\tuid_child is null");
                        } else {
                            log.debug("\tuid_child: " + uid_child);
                        }
                    }

                    if (uid_max != null && uid_child != null) {
                        if (uid_max.compareTo(uid_child) < 0) {
                            max = child;
                        }
                    } else if (uid_child == null) { // one or the other is null so we want the next row
                        max = child;
                        log.debug("uid_child is null, we need to grab the next row.");
                        break;
                    } else {
                        log.debug("max is null and child is not, who should we keep? child: " + child);
                        break;
                    }
                } // end while
                if (log.isDebugEnabled()) {
                    log.debug("attemptOptimization: AND with children, max: " + max);
                }
                if (max != null) {
                    node.setAdvanceKey(max.getAdvanceKey());
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("AND block finished, max is null");
                    }
                    node.setDone(true);
                }

            } else if (node.getType() == ParserTreeConstants.JJTORNODE) {
                // get min
                BooleanLogicTreeNode min = null;
                Enumeration<?> children = node.children();
                boolean firstTime = true;
                int numChildren = node.getChildCount();
                int allChildrenDone = 0;

                while (children.hasMoreElements()) {
                    BooleanLogicTreeNode child = (BooleanLogicTreeNode) children.nextElement();

                    if (log.isDebugEnabled()) {
                        log.debug("\tOR block start, child: " + child);
                    }
                    if (child.isNegated() || child.isChildrenAllNegated()) {
                        if (log.isDebugEnabled()) {
                            log.debug("\tskip negated child: " + child);
                        }
                        numChildren -= 1;
                        continue;
                    }
                    if (child.isDone()) {
                        if (log.isDebugEnabled()) {
                            log.debug("\tchild is done: " + child);
                        }
                        allChildrenDone += 1;
                        if (numChildren == allChildrenDone) {
                            if (log.isDebugEnabled()) {
                                log.debug("\tnumChildren==allChildrenDone, setDone & break");
                            }
                            // we're done here
                            node.setDone(true);
                            break;
                        }
                    }

                    if (child.getAdvanceKey() == null) {
                        log.debug("\tOR child doesn't have top or an AdvanceKey");
                        continue;
                    }
                    if (firstTime) {
                        if (log.isDebugEnabled()) {
                            log.debug("\tOR block, first valid node, min=child: " + child + "  advanceKey: "
                                    + child.getAdvanceKey());
                        }

                        firstTime = false;
                        min = child;
                        continue;
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("\tOR block, min: " + min);
                        log.debug("\tOR block, child: " + child);
                    }
                    if (min.getAdvanceKey().getRow().toString()
                            .compareTo(child.getAdvanceKey().getRow().toString()) > 0) {
                        // child row is less than min, set min to child
                        min = child;
                        if (log.isDebugEnabled()) {
                            log.debug("\tmin row was greater than child, min=child: " + min);
                        }
                        continue;

                    } else if (min.getAdvanceKey().getRow().compareTo(child.getAdvanceKey().getRow()) < 0) {
                        // min row is less child, skip
                        if (log.isDebugEnabled()) {
                            log.debug("\tmin row less than childs, keep min: " + min);
                        }
                        continue;

                    } else { // they're equal, test uids
                        String uid_min = getEventKeyUid(min.getAdvanceKey());
                        String uid_child = getEventKeyUid(child.getAdvanceKey());
                        if (log.isDebugEnabled()) {
                            log.debug("\ttesting uids, uid_min: " + uid_min + "  uid_child: " + uid_child);
                        }
                        if (uid_min != null && uid_child != null) {
                            if (uid_min.compareTo(uid_child) > 0) {

                                min = child;
                                if (log.isDebugEnabled()) {
                                    log.debug("\tuid_min > uid_child, set min to child: " + min);
                                }
                            }
                        } else if (uid_min == null) {
                            if (log.isDebugEnabled()) {
                                log.debug("\tuid_min is null, take childs: " + uid_child);
                            }
                            min = child;
                        }
                    }
                } // end while
                if (log.isDebugEnabled()) {
                    log.debug("attemptOptimization: OR with children, min: " + min);
                }

                if (min != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("OR block, min != null, advanceKey? " + min.getAdvanceKey());
                    }
                    node.setAdvanceKey(min.getAdvanceKey());
                } else {
                    log.debug("OR block, min is null..." + min);
                    node.setAdvanceKey(null);
                    node.setDone(true);
                }

            } else if (node.getType() == ParserTreeConstants.JJTJEXLSCRIPT) { // HEAD node
                if (log.isDebugEnabled()) {
                    log.debug("getOptimizedAdvanceKey, HEAD node");
                }
                BooleanLogicTreeNode child = (BooleanLogicTreeNode) node.getFirstChild();

                if (child.isDone()) {
                    if (log.isDebugEnabled()) {
                        log.debug("Head node's child is done, need to move to the next row");
                    }
                    Key k = child.getAdvanceKey();
                    if (k == null) {
                        if (log.isDebugEnabled()) {
                            log.debug("HEAD node, advance key is null, try to grab next row from topKey");
                        }
                        if (hasTop()) {
                            k = this.getTopKey();
                            child.setAdvanceKey(new Key(new Text(k.getRow().toString() + "\1")));
                        } else {
                            return null;
                        }
                    } else {
                        Text row = new Text(k.getRow().toString() + "\1");
                        k = new Key(row);
                        child.setAdvanceKey(k);
                    }

                }
                if (log.isDebugEnabled()) {
                    log.debug("advance Key: " + child.getAdvanceKey());
                }
                Key key = new Key(child.getAdvanceKey().getRow(), child.getAdvanceKey().getColumnFamily(),
                        child.getAdvanceKey().getColumnFamily());
                return key;

            } // end else
        } // end for
        return null;
    }

    /*
     * The incoming jump key has been formatted into the structure of an index key, but the leaves are eventkeys
     */
    private boolean jump(Key jumpKey) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("JUMP!");
        }
        Enumeration<?> bfe = root.breadthFirstEnumeration();
        while (bfe.hasMoreElements()) {
            BooleanLogicTreeNode n = (BooleanLogicTreeNode) bfe.nextElement();
            n.setAdvanceKey(null);
        } // now advance all nodes to the advance key

        if (log.isDebugEnabled()) {
            log.debug("jump, All leaves need to advance to: " + jumpKey);
        }

        String advanceUid = getIndexKeyUid(jumpKey);
        if (log.isDebugEnabled()) {
            log.debug("advanceUid =>  " + advanceUid);
        }
        boolean ok = true;
        for (BooleanLogicTreeNode leaf : positives) {
            leaf.jump(jumpKey);
        }
        return ok;
    }

    @SuppressWarnings("unused")
    public void next() throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("next() method called");
        }
        boolean finished = false;
        boolean ok = true;
        if (positives.isEmpty()) {
            setTopKey(null);
            return;
        }

        Key previousJumpKey = null;
        while (!finished) {

            Key jumpKey = this.getOptimizedAdvanceKey();

            if (jumpKey == null) { // stop?
                if (log.isDebugEnabled()) {
                    log.debug("next(), jump key is null, stopping");
                }
                setTopKey(null);
                return;
            }

            if (log.isDebugEnabled()) {
                if (jumpKey != null) {
                    log.debug("next(), jumpKey: " + jumpKey);
                } else {
                    log.debug("jumpKey is null");
                }
            }

            boolean same = false;
            if (jumpKey != null && topKey != null) {
                // check that the uid's are not the same
                same = getIndexKeyUid(jumpKey).equals(getEventKeyUid(topKey));
                if (log.isDebugEnabled()) {
                    log.debug("jumpKeyUid: " + getIndexKeyUid(jumpKey) + "  topKeyUid: " + getEventKeyUid(topKey));
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("previousJumpKey: " + previousJumpKey);
                log.debug("current JumpKey: " + jumpKey);
            }

            if (jumpKey != null && !this.overallRange.contains(jumpKey)) {
                if (log.isDebugEnabled()) {
                    log.debug("jumpKey is outside of range, that means the next key is out of range, stopping");
                    log.debug("jumpKey: " + jumpKey + " overallRange.endKey: " + overallRange.getEndKey());
                }
                // stop
                setTopKey(null);
                return;
            }

            boolean previousSame = false;
            if (previousJumpKey != null && jumpKey != null) {
                previousSame = previousJumpKey.equals(jumpKey);
            }
            // -----------------------------------
            // OPTIMIZED block
            if (jumpKey != null && !same && !previousSame && ok) {
                previousJumpKey = jumpKey;
                ok = jump(jumpKey); // attempt to jump everybody forward to this row and uid.
                // tryJump = false;

                // now test the tree state.
                if (testTreeState()) {
                    Key tempKey = root.getTopKey();
                    // it is potentially valid, now we need to seek all of the negatives
                    if (!negatives.isEmpty()) {
                        advanceNegatives(this.root.getTopKey());
                        if (!testTreeState()) {
                            continue;
                        }
                    }

                    if (root.getTopKey().equals(tempKey)) {
                        // it's valid set nextKey and make sure it's not the same as topKey.
                        if (log.isDebugEnabled()) {
                            if (this.root.hasTop()) {
                                log.debug("this.root.getTopKey()->" + this.root.getTopKey());
                            } else {
                                log.debug("next, this.root.getTopKey() is null");
                            }

                            if (topKey != null) {
                                log.debug("topKey->" + topKey);

                            } else {
                                log.debug("topKey is null");
                            }
                        }
                        if (compare(topKey, this.root.getTopKey()) != 0) {
                            // topKey = this.root.getTopKey();
                            setTopKey(this.root.getTopKey());
                            return;
                        }
                    }
                }

                // --------------------------------------
                // Regular next block
            } else {

                reHeapPriorityQueue(this.root);
                BooleanLogicTreeNode node;

                while (true) {
                    node = positives.poll();
                    if (!node.isDone() && node.hasTop()) {
                        break;
                    }

                    if (positives.isEmpty()) {
                        setTopKey(null);
                        return;
                    }
                }

                if (log.isDebugEnabled()) {
                    if (jumpKey == null) {
                        log.debug("no jump, jumpKey is null");
                    } else if (topKey == null) {
                        log.debug("no jump, jumpKey: " + jumpKey + "  topKey: null");
                    } else {
                        log.debug("no jump, jumpKey: " + jumpKey + "  topKey: " + topKey);
                    }
                    log.debug("next, (no jump) min node: " + node);
                    log.debug(node);
                }
                node.next();
                resetNegatives();

                if (!node.hasTop()) {
                    // it may be part of an or, so it could be ok.
                    node.setValid(false);
                    if (testTreeState()) {
                        // it's valid set nextKey and make sure it's not the same as topKey.
                        if (!topKey.equals(this.root.getTopKey())) {
                            // topKey = this.root.getTopKey();
                            if (this.overallRange != null) {
                                if (this.overallRange.contains(root.getTopKey())) {
                                    setTopKey(this.root.getTopKey());
                                    return;
                                } else {
                                    setTopKey(null);
                                    finished = true;
                                    return;
                                }

                            } else {
                                setTopKey(this.root.getTopKey());
                                return;
                            }
                        }
                    }
                } else {

                    if (overallRange.contains(node.getTopKey())) {
                        // the node had something so push it back into priority queue
                        positives.add(node);
                    }

                    // now test the tree state.
                    if (testTreeState()) {
                        Key tempKey = root.getTopKey();
                        // it is potentially valid, now we need to seek all of the negatives
                        if (!negatives.isEmpty()) {
                            advanceNegatives(this.root.getTopKey());
                            if (!testTreeState()) {
                                continue;
                            }
                        }

                        if (root.getTopKey().equals(tempKey)) {
                            // it's valid set nextKey and make sure it's not the same as topKey.
                            if (log.isDebugEnabled()) {
                                if (this.root.hasTop()) {
                                    log.debug("this.root.getTopKey()->" + this.root.getTopKey());
                                } else {
                                    log.debug("next, this.root.getTopKey() is null");
                                }

                                if (topKey != null) {
                                    log.debug("topKey->" + topKey);

                                } else {
                                    log.debug("topKey is null");
                                }
                            }
                            if (compare(topKey, this.root.getTopKey()) != 0) {
                                // topKey = this.root.getTopKey();
                                if (this.overallRange != null) {
                                    if (overallRange.contains(this.root.getTopKey())) {
                                        setTopKey(this.root.getTopKey());
                                        return;
                                    } else {
                                        topKey = null;
                                        finished = true;
                                        return;
                                    }
                                } else {
                                    setTopKey(this.root.getTopKey());
                                    return;
                                }
                            }
                        }
                    }

                }

                // is the priority queue empty?
                if (positives.isEmpty()) {
                    finished = true;
                    topKey = null;
                }
            }
        }
    }

    /*
     * create a range for the given row of the
     */
    private void advanceNegatives(Key k) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("advancingNegatives for Key: " + k);
        }
        Text rowID = k.getRow();
        Text colFam = k.getColumnFamily();

        for (BooleanLogicTreeNode neg : negatives) {
            Key startKey = new Key(rowID, neg.getFieldName(), new Text(neg.getFieldValue() + "\0" + colFam));
            Key endKey = new Key(rowID, neg.getFieldName(), new Text(neg.getFieldValue() + "\0" + colFam + "\1"));
            Range range = new Range(startKey, true, endKey, false);

            if (log.isDebugEnabled()) {
                log.debug("range: " + range);
            }
            neg.seek(range, EMPTY_COL_FAMS, false);

            if (neg.hasTop()) {
                neg.setValid(false);
            }
            if (log.isDebugEnabled()) {
                if (neg.hasTop()) {
                    log.debug("neg top key: " + neg.getTopKey());
                } else {
                    log.debug("neg has no top");
                }
            }
        }
    }

    public void seek(Range range, Collection<ByteSequence> columnFamilies, boolean inclusive) throws IOException {
        this.overallRange = range;
        if (log.isDebugEnabled()) {
            log.debug("seek, overallRange: " + overallRange);
        }
        // Given some criteria, advance all iterators to that position.
        // NOTE: All of our iterators exist in the leaves.
        topKey = null;
        root.setTopKey(null);

        // set up the range iterators for the given seek range.
        // these should exist in the positives as OR iterators, but need special setup.
        setupRangerators(range);

        // don't take this out, if you jump rows on the tablet you could have
        // pulled nodes out of the positives priority queue. On a call to seek
        // it is usually jumping rows, so everything needs to become possibly
        // valid again.
        reHeapPriorityQueue(this.root);
        for (BooleanLogicTreeNode node : positives) {
            node.setDone(false);
            node.seek(range, columnFamilies, inclusive);
            if (log.isDebugEnabled()) {
                String tk = "empty";
                if (node.hasTop()) {
                    tk = node.getTopKey().toString();
                }
                log.debug("leaf: " + node.getContents() + " topKey: " + tk);
            }
        }

        // Now that all nodes have been seek'd recreate the priorityQueue to sort them properly.
        splitLeaves(this.root);
        resetNegatives();

        // test Tree, if it's not valid, call next
        if (testTreeState() && overallRange.contains(root.getTopKey())) {
            if (!negatives.isEmpty()) {
                // now advance negatives
                advanceNegatives(this.root.getTopKey());
                if (!testTreeState()) {
                    next();
                }
            }

            if (log.isDebugEnabled()) {
                log.debug("overallRange " + overallRange + " topKey " + this.root.getTopKey() + " contains "
                        + overallRange.contains(this.root.getTopKey()));
            }

            if (overallRange.contains(this.root.getTopKey()) && this.root.isValid()) {
                setTopKey(this.root.getTopKey());
            } else {
                setTopKey(null);
                return;
            }
        } else {
            // seek failed in the logic test, but there may be other possible
            // values which satisfy the logic tree. Make sure our iterators aren't
            // all null, and then call next.

            // if(!root.hasTop()){
            if (log.isDebugEnabled()) {
                log.debug("seek, testTreeState is false, HEAD(root) does not have top");
            }
            // check nodes in positives to see if they're all null/outside range
            // or if nothing percolated up to root yet.
            List<BooleanLogicTreeNode> removals = new ArrayList<BooleanLogicTreeNode>();
            for (BooleanLogicTreeNode node : positives) {
                if (!node.hasTop() || !overallRange.contains(node.getTopKey())) {
                    removals.add(node);
                }
            }
            for (BooleanLogicTreeNode node : removals) {
                positives.remove(node);
            }
            next();
            return;
        }
    }

    private int compare(Key k1, Key k2) {
        if (k1 != null && k2 != null) {
            return k1.compareTo(k2);
        } else if (k1 == null && k2 == null) {
            return 0;
        } else if (k1 == null) { // in this case, null is considered bigger b/c it's closer to the end of the table.
            return 1;
        } else {
            return -1;
        }
    }

    private void setupRangerators(Range range) throws IOException {
        if (rangerators == null || rangerators.isEmpty()) {
            return;
        }
        for (BooleanLogicTreeNode node : rangerators) {
            Set<String> fValues = new HashSet<String>();
            OrIterator orIter = new OrIterator();
            SortedKeyValueIterator<Key, Value> siter = sourceIterator.deepCopy(env);
            // create UniqFieldNameValueIterator to find uniq field names values
            UniqFieldNameValueIterator uniq = new UniqFieldNameValueIterator(node.getFieldName(),
                    node.getLowerBound(), node.getUpperBound());
            uniq.setSource(siter);
            uniq.seek(range, EMPTY_COL_FAMS, false);
            while (uniq.hasTop()) {
                // log.debug("uniq.top: "+uniq.getTopKey());
                Key k = uniq.getTopKey();
                keyParser.parse(k);
                String val = keyParser.getFieldValue();
                if (!fValues.contains(val)) {
                    fValues.add(val);
                    orIter.addTerm(siter, node.getFieldName(), new Text(val), env);
                    if (log.isDebugEnabled()) {
                        log.debug("setupRangerators, adding to OR:  " + node.getFieldName() + ":" + val);
                    }
                } else {
                    log.debug("already have this one: " + val);
                }
                uniq.next();
            }
            node.setUserObject(orIter);
        }

    }

    /* *************************************************************************
     * Inner classes
     */
    public class BooleanLogicTreeNodeComparator implements Comparator<Object> {

        public int compare(Object o1, Object o2) {
            BooleanLogicTreeNode n1 = (BooleanLogicTreeNode) o1;
            BooleanLogicTreeNode n2 = (BooleanLogicTreeNode) o2;

            Key k1 = n1.getTopKey();
            Key k2 = n2.getTopKey();
            if (log.isDebugEnabled()) {
                String t1 = "null";
                String t2 = "null";
                if (k1 != null) {
                    t1 = k1.getRow().toString() + "\0" + k1.getColumnFamily().toString();
                }
                if (k2 != null) {
                    t2 = k2.getRow().toString() + "\0" + k2.getColumnFamily().toString();
                }
                log.debug("BooleanLogicTreeNodeComparator   \tt1: " + t1 + "  t2: " + t2);
            }
            // return t1.compareTo(t2);

            if (k1 != null && k2 != null) {
                return k1.compareTo(k2);
            } else if (k1 == null && k2 == null) {
                return 0;
            } else if (k1 == null) {
                return 1;
            } else {
                return -1;
            }

        }
    }

    public IteratorOptions describeOptions() {
        return new IteratorOptions(getClass().getSimpleName(), "evaluates event objects against an expression",
                Collections.singletonMap(QUERY_OPTION, "query expression"), null);
    }

    public boolean validateOptions(Map<String, String> options) {
        if (!options.containsKey(QUERY_OPTION)) {
            return false;
        }
        if (!options.containsKey(FIELD_INDEX_QUERY)) {
            return false;
        }
        this.updatedQuery = options.get(FIELD_INDEX_QUERY);
        return true;
    }
}