 operands -> 
  def termOp
  if(ctx.window >= 0) {
  	def termVal = op.getTerm()
    if (termVal.contains("*") || termVal.contains("?")) {
      def wildcardOp = new WildcardQuery(new Term(ctx.fieldOrDefault, op.term))
      termOp = new SpanMultiTermQueryWrapper<WildcardQuery>(wildcardOp);
    } else {
      termOp = new SpanTermQuery(new Term(ctx.fieldOrDefault, op.term));
    }
  } else {
    if (op.term.equals("*") && ctx.fieldOrDefault.equals("*")) {
      //"*:* all docs"
      termOp = new MatchAllDocsQuery()
    } else if (op.term.contains("*") || op.term.contains("?")) {
  	  wTerm = new Term(ctx.fieldOrDefault, op.term)
  	  termOp = new WildcardQuery(wTerm)
    } else {
      termOp = new TermQuery(new Term(ctx.fieldOrDefault, op.term))
    }
  }
  if(op.hasBoost()) termOp.setBoost(op.boost);
  return termOp;
};

builder.wildcard = {ctx, op, operands -> 
  def wildcardOp
  if (op.term.equals("*") && ctx.fieldOrDefault.equals("*")) {
    //"*:* all docs"
    wildcardOp = new MatchAllDocsQuery()
  } else {
  	if(ctx.window >= 0) {
	  wcOp = new WildcardQuery(new Term(ctx.fieldOrDefault, op.term))
      wildcardOp = new SpanMultiTermQueryWrapper<WildcardQuery>(wcOp);
  	} else {
      wildcardOp = new WildcardQuery(new Term(ctx.fieldOrDefault, op.term))
    }
  }
  
  if(op.hasBoost()) wildcardOp.setBoost(op.boost);
  return wildcardOp;
};

/****************************/
/*** HANDLING NOT QUERIES ***/
/****************************/

/**
 * not() is complicated because not() cannot stand alone. It must be bubbled up to a parent BooleanQuery in Lucene.
 * 
 * This is solved below by having not() return a BooleanClause instead of a Query object. This means that all 
 * other methods which might expect a Query object must now also look for BooleanClause and handle it appropriately. 
 */

builder.not = {ctx, op, operands ->
  def notOperand = null;
  if(operands.size() > 1) {
    notOperand = new BooleanQuery();
    operands.each{ notOperand.add(toQuery(it), BooleanClause.Occur.SHOULD) };
  }
  else if(operands.size() == 1) {
    notOperand = operands.get(0);
  }
  else {
    throw new Exception("Illegal not() operator with zero operands");
  }
  
  if(notOperand instanceof BooleanClause) {
    // convert not(not(x)) --> x
    if(notOperand.getOccur() == BooleanClause.Occur.MUST_NOT)
      return notOperand.getQuery();
    else
      throw new Exception("Illegal BooleanClause found inside not() operator (" + notOperand.getOccur() + ")");
  }

  return new BooleanClause(notOperand, BooleanClause.Occur.MUST_NOT);
};


/***********************************/
/*** RELEVANCY RANKING OPERATORS ***/
/***********************************/

// HERE HERE HERE -->  Add more addOperandToBooleanQuery(bq) methods to all items below

builder.max = {ctx, op, operands ->
  DisjunctionMaxQuery maxOp = new DisjunctionMaxQuery(0.0f);
  operands.each{ maxOp.add(toQuery(it)) };
  if(op.hasBoost()) maxOp.setBoost(op.boost);
  return maxOp;
};


builder.constant = {ctx, op, operands ->
  Query luceneOp = null;
  if(operands.size() > 1) {
    luceneOp = new BooleanQuery();
    operands.each{ luceneOp.add(toQuery(it), BooleanClause.Occur.SHOULD) };
  }
  else if(operands.size() == 1) {
    luceneOp = toQuery(operands.get(0));
  }
  else {
    throw new Exception("Illegal constant() operator with zero operands");
  }
  
  ConstantScoreQuery constantOp = new ConstantScoreQuery(luceneOp);
  if(op.hasBoost()) constantOp.setBoost(op.boost);
  return constantOp;
};


builder.boostPlus = {ctx, op, operands ->
  BooleanQuery bq = new BooleanQuery();
  boolean first = true;
  operands.each {
    bq.add(toQuery(it), first? BooleanClause.Occur.MUST : BooleanClause.Occur.SHOULD);
    first = false;
  };
  if(op.hasBoost()) bq.setBoost(op.boost);
  return bq;
};


builder.boostMul = {QPLBuilder ctx, Operator op, List<Object> operands ->
  Query positiveQ = null;
  Query otherQ = null;

  for(int operandCount = 0 ; operandCount < operands.size() ; operandCount++) {
    Object operand = operands.get(operandCount);
    
    if(operandCount == 0) positiveQ = toQuery(operand);
    else if(operandCount == 1) {
      otherQ = toQuery(operand);
    }
    else if(operandCount == 2) {
      BooleanQuery tmpBq = new BooleanQuery();
      tmpBq.add(otherQ, BooleanClause.Occur.SHOULD);
      tmpBq.add(toQuery(operand), BooleanClause.Occur.SHOULD);
      otherQ = tmpBq;
    }
    else {
      otherQ.add(toQuery(operand), BooleanClause.Occur.SHOULD);
    }
  };
  
  if(otherQ == null)
    return positiveQ;
  else
    return new BoostingQuery(positiveQ, otherQ, op.boostOrDefault);
};



builder.orMin = {ctx, op, operands -> 
  BooleanQuery bq = new BooleanQuery();
  operands.each{ bq.add(toQuery(it), BooleanClause.Occur.SHOULD) };
  if(op.hasBoost()) bq.setBoost(op.boost);
  def minClauses = op.get("minClauses");
  bq.setMinimumNumberShouldMatch(minClauses.intValue());
  return bq;
};




/**********************/
/** HANDLING PHRASES **/
/**********************/

/***
 * In order to handle Lucene MultiPhrase, I wanted to allow for phrases with nested OR's, as well as phrases
 * with nested phrases, not just a list of embedded terms. Also, I didn't want the system to build a lot of
 * non-essential TermQuery objects (the children of the phrase), since MultiPhraseQuery just takes Term() objects
 * (and not TermQuery objects).
 * 
 * Therefore, we intercept the phrase at the parent level and accumulate all of the children as needed, in other
 * words, a top-down construction rather than a bottoms-up construction.
 */

def accumulateNestedOrTerms(QPLBuilder ctx, termsList, Operator orOp) {
  if(orOp.numOperands > 0) {
    orOp.operands.each { 
      if(it.type == TERM) {
        termsList.add(new Term(ctx.fieldOrDefault, it.term));
      }
      else if(it.type == OR) {
        accumulateNestedOrTerms(ctx, termsList, it);
      }
      else {
        throw new Exception("Lucene Builder:  Phrase expression contains nested or() which has something other than nested terms and other nested or()s. (" + it.type.name + ")");
      }
    };
  }
}

def processPhraseItem(QPLBuilder ctx, operandsList, item) {
  if(item.type == TERM) {
    if