/*
 * 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.geode.cache.query.internal;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.geode.InternalGemFireError;
import org.apache.geode.cache.EntryDestroyedException;
import org.apache.geode.cache.query.FunctionDomainException;
import org.apache.geode.cache.query.NameResolutionException;
import org.apache.geode.cache.query.QueryInvocationTargetException;
import org.apache.geode.cache.query.QueryService;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.cache.query.Struct;
import org.apache.geode.cache.query.TypeMismatchException;
import org.apache.geode.cache.query.internal.index.IndexManager;
import org.apache.geode.cache.query.internal.index.IndexProtocol;
import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
import org.apache.geode.cache.query.internal.types.StructTypeImpl;
import org.apache.geode.cache.query.types.ObjectType;
import org.apache.geode.internal.Assert;

/**
 * Conjunctions and Disjunctions (LITERAL_and LITERAL_or) As a part of feature development to ensure
 * index usage for multiple regions & index usage in equi join conditions across the regions , a
 * CompiledJunction's organized operands method creates internal structures like GroupJunction,
 * AllGroupJunction & CompositeGroupJunction. The method createJunction is where the decision of
 * creating an AllGroupJunction ( wrapping multiple GroupJunctions and CompositeGroupJunctions) or a
 * single GroupJunction or a singe CompositeGroupJunction is taken.
 *
 * CompiledJunction -----> on organization of Operands may result in any of the following cases
 *
 * 1) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
 * (Filter evaluable) (Filter evaluable)
 *
 * 2) CompiledJunction | | -------------------------------- | | CompiledJunction RangeJunction
 * (Filter evaluable same index operands. May contain iter operands belonging to the same Group)
 * (Filter evaluable)
 *
 *
 * 3) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
 * (Filter evaluable) (Filter evaluable) | | ---------------------------------------------- | |
 * CompiledComparison RangeJunction (at least one filter evaluable compiled Comparison using an
 * index shared by no other condition)
 *
 *
 * 5) CompiledJunction | | -------------------------------- | | CompiledJunction GroupJunction
 * (Filter evaluable) (Filter evaluable) | | --------------------------------------- | | |
 * RangeJunction RangeJunction CompiledComparison (zero or more Filter evaluable or iter evaluable )
 *
 * 6) CompiledJunction | ---------------------------------- | | CompiledJunction AllGroupJunction
 * (filter evaluable) | ------------------------------------------------ | | | GroupJunction
 * GroupJunction CompositeGroupJunction | --------------------------------------- | | |
 * GroupJunction equi join conditions GroupJunction
 *
 *
 *
 * @version $Revision: 1.2 $
 */
public class CompiledJunction extends AbstractCompiledValue implements Negatable {

  /** left operand */
  private final CompiledValue[] _operands;
  private int _operator = 0;
  private List unevaluatedFilterOperands = null;

  // A token to place into the samesort map. This is to let the engine know there is more than one
  // index
  // being used for this junction but allows actual operands to form range junctions if enough
  // exist.
  // The mechanism checks to see if the mapped object is an integer, if so, it increments, if it's
  // not it sets as 1
  // Because we are a string place holder, the next actual operand would just start at one. If the
  // join is added
  // after a valid operand has already set the counter to an integer, we instead just ignore and do
  // not set the place holder
  private static final String PLACEHOLDER_FOR_JOIN = "join";

  CompiledJunction(CompiledValue[] operands, int operator) {
    // invariant: operator must be LITERAL_and or LITERAL_or
    // invariant: at least two operands
    if (!((operator == LITERAL_or || operator == LITERAL_and) && operands.length >= 2)) {
      throw new InternalGemFireError(
          "operator=" + operator + "operands.length =" + operands.length);
    }
    _operator = operator;
    _operands = operands;
  }

  @Override
  public List getChildren() {
    return Arrays.asList(_operands);
  }

  @Override
  public int getType() {
    return JUNCTION;
  }

  @Override
  public Object evaluate(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    Object r = _operands[0].evaluate(context); // UNDEFINED, null, or a Boolean
    // if it's true, and op is or then return true immediately
    // if it's false and the op is and then return false immediately
    if (r instanceof Boolean) {
      if ((Boolean) r && _operator == LITERAL_or) {
        return r;
      } else if (!(Boolean) r && _operator == LITERAL_and) {
        return r;
      }
    }
    if (r == null || r == QueryService.UNDEFINED) {
      r = QueryService.UNDEFINED; // keep going to see if we hit a
    }
    // short-circuiting truth value
    else if (!(r instanceof Boolean)) {
      throw new TypeMismatchException(
          String.format(
              "LITERAL_and/LITERAL_or operands must be of type boolean, not type ' %s '",
              r.getClass().getName()));
    }
    for (int i = 1; i < _operands.length; i++) {
      Object ri = null;
      try {
        ri = _operands[i].evaluate(context); // UNDEFINED, null, or
      } catch (EntryDestroyedException ede) {
        continue;
      }
      // Boolean
      if (ri instanceof Boolean) {
        if ((Boolean) ri && _operator == LITERAL_or) {
          return ri;
        } else if (!(Boolean) ri && _operator == LITERAL_and) {
          return ri;
        }
      }
      if (ri == null || ri == QueryService.UNDEFINED || r == QueryService.UNDEFINED) {
        r = QueryService.UNDEFINED;
        continue; // keep going to see if we hit a short-circuiting truth value
      } else if (!(ri instanceof Boolean)) {
        throw new TypeMismatchException(
            String.format(
                "LITERAL_and/LITERAL_or operands must be of type boolean, not type ' %s '",
                ri.getClass().getName()));
      }
      // now do the actual and/or
      if (_operator == LITERAL_and) {
        r = (Boolean) r && (Boolean) ri;
      } else
      // LITERAL_or
      {
        r = (Boolean) r || (Boolean) ri;
      }
    }
    return r;
  }

  @Override
  public Set computeDependencies(ExecutionContext context)
      throws TypeMismatchException, NameResolutionException {
    for (final CompiledValue operand : _operands) {
      context.addDependencies(this, operand.computeDependencies(context));
    }
    return context.getDependencySet(this, true);
  }

  @Override
  public SelectResults filterEvaluate(ExecutionContext context, SelectResults intermediateResults)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    OrganizedOperands newOperands = organizeOperands(context);
    SelectResults result = intermediateResults;
    Support.Assert(newOperands.filterOperand != null);
    // evaluate directly on the operand
    result =
        newOperands.isSingleFilter ? (newOperands.filterOperand).filterEvaluate(context, result)
            : (newOperands.filterOperand).auxFilterEvaluate(context, result);
    if (!newOperands.isSingleFilter) {
      List unevaluatedOps =
          ((CompiledJunction) newOperands.filterOperand).unevaluatedFilterOperands;
      if (unevaluatedOps != null) {
        if (newOperands.iterateOperand == null) {
          if (unevaluatedOps.size() == 1) {
            newOperands.iterateOperand = (CompiledValue) unevaluatedOps.get(0);
          } else {
            int len = unevaluatedOps.size();
            CompiledValue[] iterOps = new CompiledValue[len];
            for (int i = 0; i < len; i++) {
              iterOps[i] = (CompiledValue) unevaluatedOps.get(i);
            }
            newOperands.iterateOperand = new CompiledJunction(iterOps, getOperator());
          }
        } else {
          // You cannot have two CompiledJunctions a the same level
          if (newOperands.iterateOperand instanceof CompiledJunction) {
            CompiledJunction temp = (CompiledJunction) newOperands.iterateOperand;
            List prevOps = temp.getOperands();
            unevaluatedOps.addAll(prevOps);
          } else {
            unevaluatedOps.add(newOperands.iterateOperand);
          }
          int totalLen = unevaluatedOps.size();
          CompiledValue[] combinedOps = new CompiledValue[totalLen];
          Iterator itr = unevaluatedOps.iterator();
          int j = 0;
          while (itr.hasNext()) {
            combinedOps[j++] = (CompiledValue) itr.next();
          }
          newOperands.iterateOperand = new CompiledJunction(combinedOps, getOperator());
        }
      }
    }
    if (newOperands.iterateOperand != null)
    // call private method here to evaluate
    {
      result = auxIterateEvaluate(newOperands.iterateOperand, context, result);
    }
    return result;
  }

  private List getCondtionsSortedOnIncreasingEstimatedIndexResultSize(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    // The checks invoked before this function have ensured that all the
    // operands are of type ComparisonQueryInfo and of the form 'var = constant'.
    // Also need for sorting will not arise if there are only two operands
    int len = _operands.length;
    List sortedList = new ArrayList(len);
    for (final CompiledValue operand : _operands) {
      Filter toSort = (Filter) operand;
      int indxRsltToSort = toSort.getSizeEstimate(context);
      int sortedListLen = sortedList.size();
      int j = 0;
      for (; j < sortedListLen; ++j) {
        Filter currSorted = (Filter) sortedList.get(j);
        if (currSorted.getSizeEstimate(context) > indxRsltToSort) {
          break;
        }
      }
      sortedList.add(j, toSort);
    }
    return sortedList;
  }

  /**
   * invariant: all operands are known to be evaluated as a filter no operand organization is
   * necessary
   */
  @Override
  public SelectResults auxFilterEvaluate(ExecutionContext context,
      SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    // evaluate the result set from the indexed values
    // using the intermediate results so far (passed in)
    // put results into new intermediate results
    List sortedConditionsList =
        getCondtionsSortedOnIncreasingEstimatedIndexResultSize(context);

    Boolean applyLimit = (Boolean) context.cacheGet(CompiledValue.CAN_APPLY_LIMIT_AT_INDEX);

    boolean modifiedApplyLimits = false;
    if (applyLimit != null && applyLimit && sortedConditionsList.size() > 1
        && _operator == LITERAL_and) {
      context.cachePut(CAN_APPLY_LIMIT_AT_INDEX, Boolean.FALSE);
      modifiedApplyLimits = true;
    }

    // Sort the operands in increasing order of resultset size
    Iterator sortedConditionsItr = sortedConditionsList.iterator();
    while (sortedConditionsItr.hasNext()) {
      // Asif:TODO The intermediate ResultSet should be passed as null when invoking
      // filterEvaluate. Just because filterEvaluate is being called, itself
      // guarantees that there will be at least on auxFilterEvalaute call.
      // The filterEvalaute will return after invoking auxIterEvaluate.
      // The auxIterEvaluate cannot get invoked for an OR junction.
      // The approach of calculation of resultset should be Bubble UP ( i.e
      // bottom to Top of the tree. This implies that for filterEvaluate, we
      // should not be passing IntermediateResultSet.
      // Each invocation of filterEvaluate will be complete in itself with the
      // recursion being ended by evaluating auxIterEvaluate if any. The passing
      // of IntermediateResult in filterEvalaute causes AND junction evaluation
      // to be corrupted , if the intermediateResultset contains some value.

      if (modifiedApplyLimits && sortedConditionsList.size() == 1) {
        context.cachePut(CAN_APPLY_LIMIT_AT_INDEX, Boolean.TRUE);
        modifiedApplyLimits = false;
      }

      SelectResults filterResults =
          ((Filter) sortedConditionsItr.next()).filterEvaluate(context, null);
      if (_operator == LITERAL_and) {
        if (filterResults != null && filterResults.isEmpty()) {
          return filterResults;
        } else if (filterResults != null) {
          intermediateResults = (intermediateResults == null) ? filterResults
              : QueryUtils.intersection(intermediateResults, filterResults, context);

          sortedConditionsItr.remove();

          if (intermediateResults.size() <= indexThresholdSize) {
            // Abort further intersection , the remaining filter operands will be
            // transferred for
            // iter evaluation
            break;
          }
        }
      } else {
        // Asif : In case of OR clause, the filterEvaluate cannot return a
        // null value ,as a null value implies Cartesian of Iterators
        // in current scope which can happen only if the operand of OR
        // clause evaluates to true (& which we are not allowing).
        // Though if the operand evaluates to false,it is permissible case
        // for filterEvaluation.
        Assert.assertTrue(filterResults != null);
        intermediateResults = intermediateResults == null ? filterResults
            : QueryUtils.union(intermediateResults, filterResults, context);
      }
    }
    if (_operator == LITERAL_and && !sortedConditionsList.isEmpty()) {
      unevaluatedFilterOperands = sortedConditionsList;
    }
    return intermediateResults;
  }

  /** invariant: the operand is known to be evaluated by iteration */
  SelectResults auxIterateEvaluate(CompiledValue operand, ExecutionContext context,
      SelectResults intermediateResults) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    // Asif: This can be a valid value if we have an AND condition like ID=1 AND
    // true = true. In such cases , if an index is not available on ID still we
    // have at least one expression which is independent of current scope. In
    // such cases a value of null means ignore the null ( because it means all
    // the results ) thus a the result-set of current auxIterate which will be
    // the subset of total result-set , will be the right data.

    // auxIterEvalaute can be called only from an AND junction.

    // This also implies that there will be at least one operand which will be
    // evaluated using auxFilterEvaluate.Thus the result-set given to this
    // function can be empty ( obtained using auxFilterEvaluate ,meaning no need
    // to do iterations below ) but it can never be null. As null itself
    // signifies that the junction cannot be evaluated as a filter.
    if (intermediateResults == null) {
      throw new RuntimeException(
          "intermediateResults can not be null");
    }
    if (intermediateResults.isEmpty()) // short circuit
    {
      return intermediateResults;
    }
    List currentIters = context.getCurrentIterators();
    RuntimeIterator[] rIters = new RuntimeIterator[currentIters.size()];
    currentIters.toArray(rIters);
    ObjectType elementType = intermediateResults.getCollectionType().getElementType();
    SelectResults resultSet;
    if (elementType.isStructType()) {
      resultSet = QueryUtils.createStructCollection(context, (StructTypeImpl) elementType);
    } else {
      resultSet = QueryUtils.createResultCollection(context, elementType);
    }


    QueryObserver observer = QueryObserverHolder.getInstance();
    try {
      observer.startIteration(intermediateResults, operand);
      for (final Object tuple : intermediateResults) {
        if (tuple instanceof Struct) {
          Object[] values = ((Struct) tuple).getFieldValues();
          for (int i = 0; i < values.length; i++) {
            rIters[i].setCurrent(values[i]);
          }
        } else {
          rIters[0].setCurrent(tuple);
        }
        Object result = null;
        try {
          result = operand.evaluate(context);
        } finally {
          observer.afterIterationEvaluation(result);
        }
        if (result instanceof Boolean) {
          if ((Boolean) result) {
            resultSet.add(tuple);
          }
        } else if (result != null && result != QueryService.UNDEFINED) {
          throw new TypeMismatchException(
              String.format("AND/OR operands must be of type boolean, not type ' %s '",
                  result.getClass().getName()));
        }
      }
    } finally {
      observer.endIteration(resultSet);
    }
    return resultSet;
  }

  /**
   * invariant: all operands are known to be evaluated as a filter no operand organization is
   * necessary
   */
  @Override
  public void negate() {
    _operator = inverseOperator(_operator);
    for (int i = 0; i < _operands.length; i++) {
      if (_operands[i] instanceof Negatable) {
        ((Negatable) _operands[i]).negate();
      } else {
        _operands[i] = new CompiledNegation(_operands[i]);
      }
    }
  }

  @Override
  protected PlanInfo protGetPlanInfo(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    Support.Assert(_operator == LITERAL_or || _operator == LITERAL_and);
    PlanInfo resultPlanInfo = new PlanInfo();
    // set default evalAsFilter depending on operator
    boolean isOr = (_operator == LITERAL_or);
    resultPlanInfo.evalAsFilter = isOr;
    // collect indexes
    // for LITERAL_and operator, if any say yes to filter,
    // then change default evalAsFilter from false to true
    // of LITERAL_or operator, if any say no to filter, change to false
    for (final CompiledValue operand : _operands) {
      PlanInfo opPlanInfo = operand.getPlanInfo(context);
      resultPlanInfo.indexes.addAll(opPlanInfo.indexes);
      if (!isOr && opPlanInfo.evalAsFilter) {
        resultPlanInfo.evalAsFilter = true;
      } else if (isOr && !opPlanInfo.evalAsFilter) {
        resultPlanInfo.evalAsFilter = false;
      }
    }
    return resultPlanInfo;
  }

  /* Package methods */
  @Override
  public int getOperator() {
    return _operator;
  }

  List<CompiledValue> getOperands() {
    // return unmodifiable copy
    return Collections.unmodifiableList(Arrays.asList(_operands));
  }


  /**
   * TODO: Should composite operands be part of iterator operands of CompiledJunction or should it
   * be part of AllGroupJunction Write a unit Test for this function.
   *
   * Asif: The iterators which can be considered part of GroupJunction are those which are
   * exclusively dependent only on the independent iterator of the group. Any iterator which is
   * ultimately dependent on more than one independent iterators cannot be assumed to be part of the
   * GroupJunction, even if one of independent iterator belongs to a different scope.
   *
   * @return New combination of AbstractCompiledValue(s) in form of CompiledJunction.
   */
  OrganizedOperands organizeOperands(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    // get the list of operands to evaluate, and evaluate operands that can use
    // indexes first.
    List evalOperands = new ArrayList(_operands.length);
    int indexCount = 0;
    // TODO: Check if we can defer the creation of this array list only
    // if there exists an eval operand
    List compositeIterOperands = new ArrayList(_operands.length);
    // Asif: This Map will contain as key the composite filter operand & as
    // value , the set containing independent RuntimeIterators ( which will
    // necessarily be two )
    Map compositeFilterOpsMap = new LinkedHashMap();
    Map iterToOperands = new HashMap();
    CompiledValue operand = null;
    boolean isJunctionNeeded = false;
    boolean indexExistsOnNonJoinOp = false;

    for (final CompiledValue compiledValue : _operands) {
      // Asif : If we are inside this function this itself indicates
      // that there exists at least on operand which can be evaluated
      // as an auxFilterEvaluate. If any operand even if its flag of
      // evalAsFilter happens to be false, but if it is independently
      // evaluable, we should attach it with the auxFilterevaluable
      // operands irrespective of its value ( either true or false.)
      // This is because if it is true or false, it can always be paired
      // up with other filter operands for AND junction. But for
      // OR junction it can get paired with the filterOperands only
      // if it is false. But we do not have to worry about whether
      // the junction is AND or OR because, if the independent operand's
      // value is true & was under an OR junction , it would not have
      // been filterEvaluated. Instead it would have gone for auxIterEvaluate.
      // We are here itself implies, that any independent operand can be
      // either true or false for an AND junction but always false for an
      // OR Junction.
      operand = compiledValue;
      if (!operand.isDependentOnCurrentScope(context)) {
        indexCount++;
        // Asif Ensure that independent operands are always at the start
        evalOperands.add(0, operand);
      } else if (operand instanceof CompiledJunction) {
        if (operand.getPlanInfo(context).evalAsFilter) {
          // Asif Ensure that independent operands are always at the start
          evalOperands.add(indexCount++, operand);
        } else {
          evalOperands.add(operand);
        }
      } else {
        // If the operand happens to be a Like predicate , this is the point
        // where we can expand as now the structures created are all for the current
        // execution. We expand only if the current junction is AND because if its
        // OR we cannot bring Like at level of OR , because like itself is an AND
        // Also cannot expand for NOT LIKE because CCs generated by CompiledLike with AND
        // will be converted to OR by negation
        CompiledValue[] expandedOperands = null;

        if (operand.getType() == LIKE && _operator == OQLLexerTokenTypes.LITERAL_and
            && ((CompiledLike) operand).getOperator() != OQLLexerTokenTypes.TOK_NE) {
          expandedOperands =
              ((CompiledLike) operand).getExpandedOperandsWithIndexInfoSetIfAny(context);
        } else {
          expandedOperands = new CompiledValue[] {operand};
        }

        for (CompiledValue expndOperand : expandedOperands) {
          boolean operandEvalAsFilter = expndOperand.getPlanInfo(context).evalAsFilter;
          isJunctionNeeded = isJunctionNeeded || operandEvalAsFilter;
          Set set = QueryUtils.getCurrentScopeUltimateRuntimeIteratorsIfAny(expndOperand, context);
          if (set.size() != 1) {
            // Asif: A set of size not equal to 1 ( which can mean anything more
            // than 1, will mean a composite condition. For a Composite
            // Condition which is filter evaluable that means necessarily that
            // RHS is dependent on one independent iterator & LHS on the other.
            if (operandEvalAsFilter) {
              Support.Assert(set.size() == 2,
                  " The no of independent iterators should be equal to 2");
              compositeFilterOpsMap.put(expndOperand, set);
            } else {
              compositeIterOperands.add(expndOperand);
            }
          } else {
            Support.Assert(set.size() == 1,
                "The size has to be 1 & cannot be zero as that would mean it is independent");
            RuntimeIterator rIter = (RuntimeIterator) set.iterator().next();
            List operandsList = (List) iterToOperands.get(rIter);
            if (operandsList == null) {
              operandsList = new ArrayList();
              iterToOperands.put(rIter, operandsList);
            }
            if (operandEvalAsFilter && _operator == LITERAL_and) {
              indexExistsOnNonJoinOp = true;
            }
            operandsList.add(expndOperand);
          }
        }
      }
    }
    /*
     * Asif : If there exists a SingleGroupJunction & no other Filter operand , then all remaining
     * eval operands ( even if they are CompiledJunction which are not evaluable as Filter) &
     * composite operands should become part of GroupJunction. If there exists only
     * SingleGroupJunction & at least one Filter operand, then all conditions of GroupJunction which
     * are not filter evaluable should become iter operand of CompiledJunction. If there exists an
     * AllGroupJunction & no Filter operand, then all remaining iter operands of CompiledJunction
     * along with Composite operands should be part of AllGroupJunction. If there exists at least
     * one Filter operand , all remaining operands of AllGroupJunction ( including those
     * GroupJunction not filter evaluable should become part of eval operand of CompiledJunction)
     * The index count will give the idea of number of filter operands
     */
    if (isJunctionNeeded) {
      // There exists at least one condition which must have an index available.
      Filter junction = createJunction(compositeIterOperands, compositeFilterOpsMap, iterToOperands,
          context, indexCount, evalOperands, indexExistsOnNonJoinOp);
      // Asif Ensure that independent operands are always at the start
      evalOperands.add(indexCount++, junction);
    } else {
      // if(compositeIterOperands != null) {
      if (!compositeIterOperands.isEmpty()) {
        evalOperands.addAll(compositeIterOperands);
      }
      for (final Object o : iterToOperands.values()) {
        evalOperands.addAll((List) o);
      }
    }
    OrganizedOperands result = new OrganizedOperands();
    // Group the operands into those that use indexes and those that don't,
    // so we can evaluate all the operands that don't use indexes together in
    // one iteration first get filter operands
    Filter filterOperands = null;
    // there must be at least one filter operand or else we wouldn't be here
    Support.Assert(indexCount > 0);
    if (indexCount == 1) {
      filterOperands = (Filter) evalOperands.get(0);
      result.isSingleFilter = true;
    } else {
      CompiledValue[] newOperands = new CompiledValue[indexCount];
      for (int i = 0; i < indexCount; i++) {
        newOperands[i] = (CompiledValue) evalOperands.get(i);
      }
      filterOperands = new CompiledJunction(newOperands, _operator);
    }
    // get iterating operands
    // INVARIANT: the number of iterating operands when the _operator is
    // LITERAL_or must be zero. This is an invariant on filter evaluation
    CompiledValue iterateOperands = null;
    // int numIterating = _operands.length - indexCount;
    int numIterating = evalOperands.size() - indexCount;
    Support.Assert(_operator == LITERAL_and || numIterating == 0);
    if (numIterating > 0) {
      if (numIterating == 1) {
        iterateOperands = (CompiledValue) evalOperands.get(indexCount);
      } else {
        CompiledValue[] newOperands = new CompiledValue[numIterating];
        for (int i = 0; i < numIterating; i++) {
          newOperands[i] = (CompiledValue) evalOperands.get(i + indexCount);
        }
        iterateOperands = new CompiledJunction(newOperands, _operator);
      }
    }
    result.filterOperand = filterOperands;
    result.iterateOperand = iterateOperands;
    return result;
  }

  /**
   * Creates a GroupJunction or a RangeJunction based on the operands passed. The operands are
   * either Filter Operands belonging to single independent RuntimeIterator or are iter evaluable on
   * the RuntimeIterator[] passed as parameter. If all the filter evaluable operands use a single
   * Index , than a RangeJunction is created with iter operand as part of RangeJunction. If there
   * exists operands using multiple indexes, then a GroupJunction is created. In that case , the
   * operands using the same index are grouped in a RangeJunction & that Range Junction becomes part
   * of the GroupJunction. A GroupJunction may contain one or more RangeJunction
   *
   * @param needsCompacting boolean indicating if there is a possibility of RangeJunction or not. If
   *        needsCompacting is false , then a GroupJunction will be formed, without any
   *        RangeJunction
   * @param grpIndpndntItr Array of RuntimeIterator which represents the independent iterator for
   *        the Group. for all cases , except a CompositeGroupJunction containing a single
   *        GroupJunction , will have a single RuntimeIterator in this array.
   * @param completeExpnsn boolean indicating whether the Group or Range Junction needs to be
   *        expanded to the query from clause level or the group level
   * @param cv Array of CompiledValue containing operands belonging to a group
   * @param sameIndexOperands Map object containing Index as the key & as a value an Integer object
   *        or a List. If it contains an Integer as value against Index, it implies that there is
   *        only one operand which uses that type of index & hence cannot form a RangeJunction. If
   *        it contains a List then the Lis contains the operands which use same index & thus can
   *        form a RangeJunction
   * @return Object of GroupJunction or RangeJunction as the case may be
   */
  private AbstractGroupOrRangeJunction createGroupJunctionOrRangeJunction(boolean needsCompacting,
      RuntimeIterator[] grpIndpndntItr, boolean completeExpnsn, CompiledValue[] cv,
      Map sameIndexOperands) {
    AbstractGroupOrRangeJunction junction;
    if (needsCompacting) {
      Iterator itr = sameIndexOperands.values().iterator();
      if (sameIndexOperands.size() == 1) {
        // This means that there exists only one type of Index for the whole
        // Group. Thus overall we should just create a RangeJunction. It also
        // means that the rest of the operands are the Iter Operands ( they may
        // contain those operands which are not index evaluable but evaluate to
        // true/false ( not dependent on current scope)
        List sameIndexOps = (List) itr.next();
        Iterator sameIndexOpsItr = sameIndexOps.iterator();
        for (int i = 0; i < cv.length; ++i) {
          if (cv[i] == null) {
            cv[i] = (CompiledValue) sameIndexOpsItr.next();
          }
        }
        junction = new RangeJunction(_operator, grpIndpndntItr, completeExpnsn, cv);
      } else {
        int numRangeJunctions = 0;
        CompiledValue[] rangeJunctions = new CompiledValue[sameIndexOperands.size()];
        int nullifiedFields = 0;
        while (itr.hasNext()) {
          Object listOrPosition = itr.next();
          if (listOrPosition instanceof List) {
            List ops = (List) listOrPosition;
            nullifiedFields += ops.size();
            CompiledValue[] operands = (CompiledValue[]) ops.toArray(new CompiledValue[0]);
            rangeJunctions[numRangeJunctions++] =
                new RangeJunction(_operator, grpIndpndntItr, completeExpnsn, operands);
          }
        }
        int totalOperands = cv.length - nullifiedFields + numRangeJunctions;
        CompiledValue[] allOperands = new CompiledValue[totalOperands];
        // Fill the Non RangeJunction operands first
        int k = 0;
        for (CompiledValue tempCV : cv) {
          if (tempCV != null) {
            allOperands[k++] = tempCV;
          }
        }
        for (int i = 0; i < numRangeJunctions; ++i) {
          allOperands[k++] = rangeJunctions[i];
        }
        junction = new GroupJunction(_operator, grpIndpndntItr, completeExpnsn, allOperands);
      }
    } else {
      junction = new GroupJunction(_operator, grpIndpndntItr, completeExpnsn, cv);
    }
    return junction;
  }

  private boolean sortSameIndexOperandsForGroupJunction(CompiledValue[] cv, List operandsList,
      Map sameIndexOperands, ExecutionContext context)
      throws TypeMismatchException, NameResolutionException,
      FunctionDomainException, QueryInvocationTargetException {
    int size = operandsList.size();
    CompiledValue tempOp = null;
    boolean needsCompacting = false;
    for (int i = 0; i < size; ++i) {
      tempOp = (CompiledValue) operandsList.get(i);
      IndexInfo[] indx = null;
      Object listOrPosition = null;
      boolean evalAsFilter = tempOp.getPlanInfo(context).evalAsFilter;
      // TODO:Do not club Like predicate in an existing range
      if (evalAsFilter) {
        indx = ((Indexable) tempOp).getIndexInfo(context);
        // We are now sorting these for joins, therefore we need to weed out the join indexes
        if (!IndexManager.JOIN_OPTIMIZATION || indx.length == 1) {
          Assert.assertTrue(indx.length == 1,
              "There should have been just one index for the condition");
          listOrPosition = sameIndexOperands.get(indx[0]._index);
        }
      }

      if (listOrPosition != null) {
        if (listOrPosition instanceof Integer) {
          int position = (Integer) listOrPosition;
          List operands = new ArrayList(size);
          operands.add(cv[position]);
          operands.add(tempOp);
          cv[position] = null;
          sameIndexOperands.put(indx[0]._index, operands);
          needsCompacting = true;
        } else if (listOrPosition instanceof List) {
          List operands = (List) listOrPosition;
          operands.add(tempOp);
        } else {
          // a join was present here, let's now occupy that spot and remove the placeholder
          listOrPosition = null;
        }
      }
      if (listOrPosition == null) {
        cv[i] = tempOp;
        if (indx != null && indx.length == 1) {
          // TODO: Enable only for AND junction for now
          if (evalAsFilter && _operator == OQLLexerTokenTypes.LITERAL_and) {
            sameIndexOperands.put(indx[0]._index, i);
          }
        } else if (indx != null && indx.length == 2) {
          if (evalAsFilter && _operator == OQLLexerTokenTypes.LITERAL_and) {
            if (!sameIndexOperands.containsKey(indx[0]._index)) {
              sameIndexOperands.put(indx[0]._index, PLACEHOLDER_FOR_JOIN);
            }
            if (!sameIndexOperands.containsKey(indx[1]._index)) {
              sameIndexOperands.put(indx[1]._index, PLACEHOLDER_FOR_JOIN);
            }
          }
        }
      }
    }
    return needsCompacting;
  }

  // This is called only if the CompiledJunction was either independent or filter evaluable.
  @Override
  public int getSizeEstimate(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    if (isDependentOnCurrentScope(context)) {
      return Integer.MAX_VALUE;
    } else {
      return 0;
    }
  }

  // TODO: Optmize this function further in terms of creation of Arrays &
  // Lists
  private Filter createJunction(List compositeIterOperands, Map compositeFilterOpsMap,
      Map iterToOperands, ExecutionContext context, int indexCount, List evalOperands,
      boolean indexExistsOnNonJoinOp) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    Support.Assert(!(iterToOperands.isEmpty() && compositeFilterOpsMap.isEmpty()),
        " There should not have been any need to create a Junction");
    CompiledValue junction = null;
    int size;
    /*---------- Create only a  GroupJunction */
    if (iterToOperands.size() == 1 && (compositeFilterOpsMap.isEmpty()
        || (indexExistsOnNonJoinOp && IndexManager.JOIN_OPTIMIZATION))) {
      if ((indexExistsOnNonJoinOp && IndexManager.JOIN_OPTIMIZATION)) {
        // For the optimization we will want to add the compositeFilterOpsMap 848
        // without the optimization we only fall into here if it's empty anyways, but have not
        // tested the removal of this if clause
        evalOperands.addAll(compositeFilterOpsMap.keySet());
      }
      // Asif :Create only a GroupJunction. The composite conditions can be
      // evaluated as iter operands inside GroupJunction.
      Map.Entry entry = (Map.Entry) iterToOperands.entrySet().iterator().next();
      List operandsList = (List) entry.getValue();
      // Asif: If there exists no other filter operand, transfer all the
      // remaining iter operands to the Group Junction. Else transfer all the
      // remaining operands to eval operands of CompiledJunctio. This means that
      // we are following the logic that as far as possible minimize the
      // result-set used for iter evaluation of conditions without index.
      // This way we also save on extra iterations for eval conditions if they
      // existed both in GroupJunction as well as CompiledJunction. The only
      // exception in this logic is the iter evaluable conditions belonging to a
      // GroupJunction.
      // If a condition is dependent on a Single Group & is not filter evaluable
      // we do not push it out to iter operands of AllGroupJunction or Compiled
      // Junction , but make it part of GroupJunction itself. This helps bcoz we
      // are sure that the condition will be completely evaluable by the
      // iterators of that group itself.
      if (indexCount == 0) {
        operandsList.addAll(evalOperands);
        evalOperands.clear();
        if (!compositeIterOperands.isEmpty()) {
          operandsList.addAll(compositeIterOperands);
        }
      } else {
        if (!compositeIterOperands.isEmpty()) {
          evalOperands.addAll(compositeIterOperands);
        }
        Iterator itr = operandsList.iterator();
        while (itr.hasNext()) {
          CompiledValue cv = (CompiledValue) itr.next();
          // Asif : Those conditions not filter evaluable are transferred to
          // eval operand of CompiledJunction.
          if (!cv.getPlanInfo(context).evalAsFilter) {
            itr.remove();
            evalOperands.add(cv);
          }
        }
      }
      size = operandsList.size();
      Map sameIndexOperands = new HashMap(size);
      CompiledValue[] cv = new CompiledValue[size];
      // Enable only for AND junction
      boolean needsCompacting =
          sortSameIndexOperandsForGroupJunction(cv, operandsList, sameIndexOperands, context);
      junction = createGroupJunctionOrRangeJunction(needsCompacting,
          new RuntimeIterator[] {(RuntimeIterator) entry.getKey()},
          true /* need a Complete expansion */, cv, sameIndexOperands);
    }
    /*
     * Asif : An AllGroupJunction or a CompositeGroupJunction can get created. An AllGroupJunction
     * will get created , if there exists a need to create more than one CompositeGroupJunction
     * AND/OR more than one GroupJunctions ( not part of CompositeGroupJunction) If suppose the
     * where clause contained two sets of conditions each dependent exclusively on a separate
     * independent group ( implying two independent groups) then we should create an
     * AllGroupJunction iff the two Groups are both filter evaluable. If an AllGroupJunction
     * contains only one filter evaluable Group & no CompositeGroupJunction then we should just
     * create a singleGroupJunction with complete expansion as true
     */
    else {
      // Asif : First create the required amount of CompositeGroupJunctions
      // A map which keeps track of no. of independent iterators pointing to the
      // same CompositeGroupJunction. The integer count will keep track of
      // number of different CompositeGroupJunctions which got created.
      Map tempMap = new HashMap();
      Set entries = compositeFilterOpsMap.entrySet();
      Set cgjs = new HashSet(compositeFilterOpsMap.size());
      for (final Object o : entries) {
        Map.Entry entry = (Map.Entry) o;
        CompiledValue op = (CompiledValue) entry.getKey();
        Set indpndtItrSet = (Set) entry.getValue();
        Iterator itr = indpndtItrSet.iterator();
        CompositeGroupJunction cgj = null;
        RuntimeIterator[] absentItrs = new RuntimeIterator[2];
        int k = 0;
        while (itr.hasNext()) {
          RuntimeIterator ritr = (RuntimeIterator) itr.next();
          CompositeGroupJunction temp = (CompositeGroupJunction) tempMap.get(ritr);
          if (temp == null) {
            absentItrs[k++] = ritr;
          }
          if (cgj == null && temp != null) {
            cgj = temp;
            cgj.addFilterableCompositeCondition(op);
          } else if (cgj != null && temp != null && cgj != temp) {
            // Asif A peculiar case wherein we have some thing like four regions
            // with relation ship conditions like r1 = r2 and r3 = r4 and r4 =
            // r1. In such situations we should just have a single
            // CompositeJunction containing r1 r2 r3 and r4 We will merge temp
            // into CompositeGroupJunction & change all the values in tempMap
            // which contained temp object to CompositeGroupJunction.
            // Also we will remove the entry of temp from CompositeGroupJunctions.
            cgj.mergeFilterableCCsAndIndependentItrs(temp);
            cgjs.remove(temp);
            Iterator entriesItr = tempMap.entrySet().iterator();
            Map.Entry tempEntry = null;
            while (entriesItr.hasNext()) {
              tempEntry = (Map.Entry) entriesItr.next();
              if (tempEntry.getValue() == temp) {
                tempEntry.setValue(cgj);
              }
            }
          }
        }
        if (cgj == null) {
          // Asif Create a new CompositeGroupJunction
          cgj = new CompositeGroupJunction(_operator, op /* CompositeFilterOperand */);
          cgjs.add(cgj);
        }
        for (int i = 0; i < k; ++i) {
          tempMap.put(absentItrs[i], cgj);
          cgj.addIndependentIterators(absentItrs[i]);
        }
      }
      // Asif :If no of cgj is zero definitely there will be an AllGroupJunction
      // If no. of cgj is more than 1 , then also there will be an AllGroupJunction.
      // But if no. of cgj is excactly one & all the GroupJunctions can be
      // accomodated within the CGJ , then we will have CompsiteGroupJunction.
      // Also , those composite conditions which are not filter evaluable will
      // be added to CompositeGroupJunction as iter operands only if we don't
      // have any additional filter & CompositeGroupJunction is the only
      // junction which will get created. If there exists any other filter then
      // it will be part of CompiledJunction's iter operand. Basically we will
      // not distinguish between various CompositeConditions which are not
      // filter evaluable & will not try to place it in various
      // ComspoiteGroupJunctions to which they belong.
      List gjs = new ArrayList(iterToOperands.size());
      Iterator itr = iterToOperands.entrySet().iterator();
      Map.Entry entry;
      while (itr.hasNext()) {
        entry = (Map.Entry) itr.next();
        List operandsList = (List) entry.getValue();
        CompiledValue[] cv = new CompiledValue[size = operandsList.size()];
        int j = 0;
        // Asif:An individual GroupJunction is Filter evaluable if at least one
        // operand is filter evaluable.
        boolean evalAsFilter = false;
        boolean needsCompacting = false;
        CompiledValue tempOp = null;
        Map sameIndexOperands = new HashMap(size);

        for (; j < size; ++j) {
          tempOp = (CompiledValue) operandsList.get(j);
          boolean isFilterevaluable = tempOp.getPlanInfo(context).evalAsFilter;
          evalAsFilter = evalAsFilter || isFilterevaluable;
          IndexInfo[] indx = null;
          Object listOrPosition = null;
          if (isFilterevaluable) {
            indx = ((Indexable) tempOp).getIndexInfo(context);
            Assert.assertTrue(indx.length == 1,
                "There should have been just one index for the condition");
            listOrPosition = sameIndexOperands.get(indx[0]._index);
          }
          if (listOrPosition != null) {
            if (listOrPosition instanceof Integer) {
              int position = (Integer) listOrPosition;
              List operands = new ArrayList(size);
              operands.add(cv[position]);
              operands.add(tempOp);
              cv[position] = null;
              sameIndexOperands.put(indx[0]._index, operands);
              needsCompacting = true;
            } else {
              List operands = (List) listOrPosition;
              operands.add(tempOp);
            }
          } else {
            cv[j] = tempOp;
            if (isFilterevaluable && _operator == OQLLexerTokenTypes.LITERAL_and) {
              sameIndexOperands.put(indx[0]._index, j);
            }
          }
        }
        if (!evalAsFilter) {
          // Asif : If there exists at least one Filter then those GroupJunction
          // which are not Filter evaluable should become part of eval operands
          // of CompiledJunction else add the Group operands which is not filter
          // evaluable to the Composite iterator oprands
          List toAddTo = null;
          if (indexCount > 0) {
            toAddTo = evalOperands;
          } else {
            toAddTo = compositeIterOperands;
          }
          for (int i = 0; i < size; ++i) {
            toAddTo.add(cv[i]);
          }
        } else {
          RuntimeIterator grpIndpndtItr = (RuntimeIterator) entry.getKey();
          AbstractGroupOrRangeJunction gj = createGroupJunctionOrRangeJunction(needsCompacting,
              new RuntimeIterator[] {(RuntimeIterator) entry.getKey()},
              false/* Expand only to Group Level */, cv, sameIndexOperands);
          CompositeGroupJunction cgj = null;
          if ((cgj = (CompositeGroupJunction) tempMap.get(grpIndpndtItr)) != null) {
            cgj.addGroupOrRangeJunction(gj);
          } else {
            gjs.add(gj);
          }
        }
      }
      if (indexCount == 0) {
        // Asif : all the eval operands of CompiledJunction should be part of
        // AllGroupJunction
        compositeIterOperands.addAll(evalOperands);
        evalOperands.clear();
      } else {
        // Asif : Add the composite operands to the eval operands so that they
        // are evaluated as part of CompiledJunction.
        if (!compositeIterOperands.isEmpty()) {
          evalOperands.addAll(compositeIterOperands);
        }
      }
      // Asif : An AllGroupJunction will be created iff either the List
      // containing GroupJunctions is not empty ot there exists multiple
      // CompositeGroupJunctions Else a CompositeGroupJunction will be created
      // Convert the List of Indpendent Runtime Iterators of
      // CompositeGroupJunction into Array of Indpendent RuntimeIterators
      for (final Object cgj : cgjs) {
        ((CompositeGroupJunction) cgj).setArrayOfIndependentItrs();
      }
      int cgjSize = cgjs.size();
      if (gjs.isEmpty() && cgjSize == 1) {
        CompositeGroupJunction compGrpJnc = (CompositeGroupJunction) cgjs.iterator().next();
        compGrpJnc.addIterOperands(compositeIterOperands);
        compGrpJnc.setCompleteExpansionOn();
        junction = compGrpJnc;
      } else if (gjs.size() == 1 && cgjSize == 0) {
        // Asif: There exists only a single Filterable GroupJunction & no
        // CompositeGroupJunction so we should just create a GroupJunction
        // instead of an AllGroupJunction . ( In this way the remaining iter
        // operands will get evaluated during the filter evaluation of
        // GroupJunction) For such GroupJunction complete expansion needs to be
        // true & the list of composite iter operands ( which at this point
        // contains the iter operands, should be added to the operands of
        // GroupJunction where they will get orgainzed as iter operands during
        // the call of organizeOperands in GroupJunction
        AbstractGroupOrRangeJunction gjTemp = (AbstractGroupOrRangeJunction) gjs.get(0);
        compositeIterOperands.addAll(gjTemp.getOperands());
        CompiledValue[] newOps = new CompiledValue[compositeIterOperands.size()];
        compositeIterOperands.toArray(newOps);
        // Asif : If gjTemp is a RangeJunction, we will get an instance of
        // RangeJunction else we will get an instance of GroupJunction.
        junction =
            gjTemp.createNewOfSameType(_operator, gjTemp.getIndependentIteratorForGroup(),
                true/* The expansion needs to be complete */, newOps);

      } else {
        gjs.addAll(cgjs);
        junction = new AllGroupJunction(gjs, _operator, compositeIterOperands);
      }
    }
    return (Filter) junction;
  }

  // Asif : This function provides package visbility for testing the
  // output of organizeOperands function.
  OrganizedOperands testOrganizedOperands(ExecutionContext context) throws FunctionDomainException,
      TypeMismatchException, NameResolutionException, QueryInvocationTargetException {
    return organizeOperands(context);
  }

  @Override
  public boolean isProjectionEvaluationAPossibility(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    for (final CompiledValue operand : _operands) {
      // LIKE gives rise to a JUNCTION in CompiledLike whether wildcard is present or not
      if ((operand.getType() == JUNCTION || operand.getType() == LIKE)
          && operand.getPlanInfo(context).evalAsFilter) {
        return false;
      }
    }
    return true;
  }

  /*
   * This method checks the limit applicability on index intermediate results for junctions and
   * optimizes the limit on index intermediate results ONLY if ONE index is used for whole query and
   * all conditions in where clause use that index. Look at the call hierarchy of this function.
   * There are two cases:
   *
   * Literal_OR: A junction with OR will contain operands which are CompiledComparison or
   * CompiledJunction (or subjunction). we recursively check if all of those use the same index and
   * if ANY one of those comparisons or subjunctions does not use the index, it retruns false.
   *
   * Literal_AND: If we get combination of comparisons and subjunctions then limit is NOT applicable
   * on index results. Like, "where ID != 10 AND (ID < 5 OR ID > 10) LIMIT 5". If we get only
   * comparisons ONLY then if all comparisons use the same index then limit is applicable and this
   * returns true. Like, "where ID != 10 AND (ID < 5 AND ID > 10) LIMIT 5".
   *
   */
  @Override
  public boolean isLimitApplicableAtIndexLevel(ExecutionContext context)
      throws FunctionDomainException, TypeMismatchException, NameResolutionException,
      QueryInvocationTargetException {
    if (_operator == LITERAL_or) {
      // There is a slight inefficiency in the sense that if the subjunction ( say AND) cannot apply
      // limit,
      // then limit would not be applied at remaining conditions of OR. But since we have single
      // flag
      // governing the behaviour of applying limit at index level, we cannot make it true for
      // specific clauses
      for (final CompiledValue operand : _operands) {
        if (!operand.getPlanInfo(context).evalAsFilter
            || ((Filter) operand).isLimitApplicableAtIndexLevel(context)) {
          return false;
        }
      }
      return true;
    } else {
      // For limit to be applicable on a AND junction, there should be only one type of index used
      // and rest iter evaluable
      // But since the selection of which index to use happens in GroupJunction created at runtimke,
      // we do not have that
      // information yet, and at this point there would be multiple indexes. Ideally we should
      // invoke this function in GroupJunction,
      // in case we want to support multi index usage again at some point. Till then since it is
      // hard coded to use 1 index
      // we can for the time being return true if there exists atleast one indexable condition
      boolean foundIndex = false;
      for (final CompiledValue operand : _operands) {
        if (operand.getPlanInfo(context).evalAsFilter
            && operand.getType() == JUNCTION) {
          return false;
        } else if (operand.getPlanInfo(context).evalAsFilter) {
          foundIndex = true;
        }
      }
      return foundIndex;
    }
  }

  @Override
  public boolean isOrderByApplicableAtIndexLevel(ExecutionContext context,
      String canonicalizedOrderByClause) throws FunctionDomainException, TypeMismatchException,
      NameResolutionException, QueryInvocationTargetException {
    if (_operator == LITERAL_and) {
      // Set<IndexProtocol> usedIndex = new HashSet<>();
      boolean foundRightIndex = false;
      for (final CompiledValue operand : _operands) {
        PlanInfo pi = operand.getPlanInfo(context);
        if (pi.evalAsFilter && operand.getType() == JUNCTION) {
          return false;
        } else if (pi.evalAsFilter) {
          if (!foundRightIndex) {
            IndexProtocol ip =
                (IndexProtocol) operand.getPlanInfo(context).indexes.get(0);
            if (ip.getCanonicalizedIndexedExpression().equals(canonicalizedOrderByClause)
                && pi.isPreferred) {
              foundRightIndex = true;
            }
          }
          // usedIndex.addAll(this._operands[i].getPlanInfo(context).indexes);
        }
      }
      return foundRightIndex;
      // if(usedIndex.size() == 1 &&
      // usedIndex.iterator().next().getCanonicalizedIndexedExpression().equals(canonicalizedOrderByClause))
      // {
      // return true;
      // }
    }
    return false;
  }

  /*
   * class CGJData { RuntimeIterator [] indpndItrs = null; List iterOperands = null; List
   * groupJunctions = null; CompiledValue [] compositeCompiledComparisons = null;
   *
   * CGJData(RuntimeIterator [] indpndItrs, List iterOperands,List groupJunctions, CompiledValue []
   * compositeCompiledComparisons) { this.indpndItrs = indpndItrs; this.iterOperands = iterOperands;
   * this.groupJunctions = groupJunctions; this.compositeCompiledComparisons =
   * compositeCompiledComparisons; } }
   */
}
