/** Evaluate isUnique expression. */
  protected final Value evalIsUnique(EvalContext ctx) {
    // evaluate range
    Value v = fRangeExp.eval(ctx);
    if (v.isUndefined()) return UndefinedValue.instance;
    CollectionValue rangeVal = (CollectionValue) v;

    // collect values for finding duplicates
    Set<Value> values = new HashSet<Value>();

    // loop over range elements
    for (Value elemVal : rangeVal) {

      // bind element variable to range element, if variable was
      // declared
      if (!fElemVarDecls.isEmpty()) ctx.pushVarBinding(fElemVarDecls.varDecl(0).name(), elemVal);

      // evaluate collect expression
      Value val = fQueryExp.eval(ctx);

      if (!fElemVarDecls.isEmpty()) ctx.popVarBinding();

      // stop if duplicate element is found
      if (values.contains(val)) return BooleanValue.FALSE;
      else values.add(val);
    }

    // result is true if no duplicates where found
    return BooleanValue.TRUE;
  }
  /** Evaluate collect expressions. */
  protected final Value evalCollectNested(EvalContext ctx) {
    // evaluate range
    Value v = fRangeExp.eval(ctx);
    if (v.isUndefined()) return UndefinedValue.instance;
    CollectionValue rangeVal = (CollectionValue) v;

    // prepare result value
    ArrayList<Value> resValues = new ArrayList<Value>();

    // loop over range elements
    for (Value elemVal : rangeVal) {

      // bind element variable to range element, if variable was
      // declared
      if (!fElemVarDecls.isEmpty()) ctx.pushVarBinding(fElemVarDecls.varDecl(0).name(), elemVal);

      // evaluate collect expression
      Value val = fQueryExp.eval(ctx);

      // add element to result
      resValues.add(val);

      if (!fElemVarDecls.isEmpty()) ctx.popVarBinding();
    }

    // result is collection with mapped values
    if (fRangeExp.type().isSequence() || fRangeExp.type().isOrderedSet())
      return new SequenceValue(fQueryExp.type(), resValues);
    else return new BagValue(fQueryExp.type(), resValues);
  }
  /** Evaluate sortedBy expressions. */
  protected final Value evalSortedBy(EvalContext ctx) {
    // evaluate range
    Value v = fRangeExp.eval(ctx);
    if (v.isUndefined()) return UndefinedValue.instance;
    CollectionValue rangeVal = (CollectionValue) v;

    ArrayList<KeyValPair> keyValList = new ArrayList<KeyValPair>();

    // loop over range elements
    for (Value elemVal : rangeVal) {

      // bind element variable to range element, if variable was
      // declared
      if (!fElemVarDecls.isEmpty()) ctx.pushVarBinding(fElemVarDecls.varDecl(0).name(), elemVal);

      // evaluate sortedBy expression and store the result as a
      // key together with elemVal
      Value key = fQueryExp.eval(ctx);
      keyValList.add(new KeyValPair(key, elemVal));

      if (!fElemVarDecls.isEmpty()) ctx.popVarBinding();
    }

    // sort elements by key
    Collections.sort(
        keyValList,
        new Comparator<KeyValPair>() {
          public int compare(KeyValPair o1, KeyValPair o2) {
            return o1.fKey.compareTo(o2.fKey);
          }
        });

    // drop the keys from the list
    ListIterator<KeyValPair> listIter = keyValList.listIterator();
    Collection<Value> result = new ArrayList<Value>(keyValList.size());

    while (listIter.hasNext()) {
      KeyValPair kvp = listIter.next();
      result.add(kvp.fElem);
    }

    Type rangeElemType = ((CollectionType) fRangeExp.type()).elemType();
    return new SequenceValue(rangeElemType, result);
  }
  /** Evaluate select and reject expressions. */
  protected final Value evalSelectOrReject(EvalContext ctx, boolean doSelect) {
    // evaluate range
    Value v = fRangeExp.eval(ctx);
    if (v.isUndefined()) return UndefinedValue.instance;
    CollectionValue rangeVal = (CollectionValue) v;

    // prepare result value
    ArrayList<Value> resValues = new ArrayList<Value>();
    Type elemType = rangeVal.elemType();
    if (!rangeVal.type().isInstantiableCollection())
      throw new RuntimeException("rangeVal is not of collection type: " + rangeVal.type());

    // loop over range elements
    for (Value elemVal : rangeVal) {

      // bind element variable to range element, if variable was
      // declared
      if (!fElemVarDecls.isEmpty()) ctx.pushVarBinding(fElemVarDecls.varDecl(0).name(), elemVal);

      // evaluate select expression
      Value queryVal = fQueryExp.eval(ctx);

      // undefined query values default to false
      if (queryVal.isUndefined()) queryVal = BooleanValue.FALSE;

      if (((BooleanValue) queryVal).value() == doSelect) resValues.add(elemVal);

      if (!fElemVarDecls.isEmpty()) ctx.popVarBinding();
    }

    CollectionValue res;
    if (rangeVal.type().isSet()) res = new SetValue(elemType, resValues);
    else if (rangeVal.type().isSequence()) res = new SequenceValue(elemType, resValues);
    else if (rangeVal.type().isBag()) res = new BagValue(elemType, resValues);
    else if (rangeVal.type().isOrderedSet()) res = new OrderedSetValue(elemType, resValues);
    else {
      // should not happen
      throw new RuntimeException("rangeVal is not of collection type: " + rangeVal.type());
    }
    // result is collection with selected/rejected values
    return res;
  }
  /**
   * Evaluate exists and forAll expressions. The array <code>moreElemVars</code> may be null if
   * there is at most one element variable declared.
   */
  protected final Value evalExistsOrForAll(EvalContext ctx, boolean doExists) {
    // evaluate range
    Value v = fRangeExp.eval(ctx);
    if (v.isUndefined()) return UndefinedValue.instance;
    CollectionValue rangeVal = (CollectionValue) v;

    // we need recursion for the permutation of assignments of
    // range values to all element variables.
    boolean res = evalExistsOrForAll0(0, rangeVal, ctx, doExists);
    return BooleanValue.get(res);
  }
  private final boolean evalExistsOrForAll0(
      int nesting, CollectionValue rangeVal, EvalContext ctx, boolean doExists) {
    // loop over range elements
    boolean res = !doExists;

    for (Value elemVal : rangeVal) {

      // bind element variable to range element, if variable was
      // declared
      if (!fElemVarDecls.isEmpty())
        ctx.pushVarBinding(fElemVarDecls.varDecl(nesting).name(), elemVal);

      if (!fElemVarDecls.isEmpty() && nesting < fElemVarDecls.size() - 1) {
        // call recursively to iterate over range while
        // assigning each value to each element variable
        // eventually
        if (res != doExists) res = evalExistsOrForAll0(nesting + 1, rangeVal, ctx, doExists);
        else
          // don't change the result value when expression is true
          // (exists) or
          // false (forAll) and continue iteration
          evalExistsOrForAll0(nesting + 1, rangeVal, ctx, doExists);
      } else {
        // evaluate predicate expression
        Value queryVal = fQueryExp.eval(ctx);

        // undefined query values default to false
        if (queryVal.isUndefined()) queryVal = BooleanValue.FALSE;

        // don't change the result value when expression is true
        // (exists) or
        // false (forAll) and continue iteration
        if (res != doExists && ((BooleanValue) queryVal).value() == doExists) res = doExists;
      }

      if (!fElemVarDecls.isEmpty()) ctx.popVarBinding();
    }

    return res;
  }