/**
   * Create an Evaluator for the given query sources and projection
   *
   * @param <T>
   * @param metadata query metadata
   * @param sources sources of the query
   * @param projection projection of the query
   * @return evaluator
   */
  public <T> Evaluator<T> create(
      QueryMetadata metadata, List<? extends Expression<?>> sources, Expression<T> projection) {
    final CollQuerySerializer serializer = new CollQuerySerializer(templates);
    serializer.append("return ");
    if (projection instanceof FactoryExpression<?>) {
      serializer.append("(");
      serializer.append(ClassUtils.getName(projection.getType()));
      serializer.append(")(");
      serializer.handle(projection);
      serializer.append(")");
    } else {
      serializer.handle(projection);
    }
    serializer.append(";");

    Map<Object, String> constantToLabel = serializer.getConstantToLabel();
    Map<String, Object> constants = getConstants(metadata, constantToLabel);
    Class<?>[] types = new Class<?>[sources.size()];
    String[] names = new String[sources.size()];
    for (int i = 0; i < sources.size(); i++) {
      types[i] = sources.get(i).getType();
      names[i] = sources.get(i).toString();
    }

    // normalize types
    for (int i = 0; i < types.length; i++) {
      if (Primitives.isWrapperType(types[i])) {
        types[i] = Primitives.unwrap(types[i]);
      }
    }

    return factory.createEvaluator(
        serializer.toString(), projection.getType(), names, types, constants);
  }
  /**
   * Create an Evaluator for the given source and filter
   *
   * @param <T>
   * @param source source of the query
   * @param filter filter of the query
   * @return evaluator
   */
  @SuppressWarnings("unchecked")
  public <T> Evaluator<List<T>> createEvaluator(
      QueryMetadata metadata, Expression<? extends T> source, Predicate filter) {
    String typeName = ClassUtils.getName(source.getType());
    CollQuerySerializer ser = new CollQuerySerializer(templates);
    ser.append(
        "java.util.List<" + typeName + "> rv = new java.util.ArrayList<" + typeName + ">();\n");
    ser.append("for (" + typeName + " " + source + " : " + source + "_) {\n");
    ser.append("    try {\n");
    ser.append("        if (").handle(filter).append(") {\n");
    ser.append("            rv.add(" + source + ");\n");
    ser.append("        }\n");
    ser.append("    } catch (NullPointerException npe) { }\n");
    ser.append("}\n");
    ser.append("return rv;");

    Map<Object, String> constantToLabel = ser.getConstantToLabel();
    Map<String, Object> constants = getConstants(metadata, constantToLabel);

    Type sourceType = new ClassType(TypeCategory.SIMPLE, source.getType());
    ClassType sourceListType = new ClassType(TypeCategory.SIMPLE, Iterable.class, sourceType);

    return factory.createEvaluator(
        ser.toString(),
        sourceListType,
        new String[] {source + "_"},
        new Type[] {sourceListType},
        new Class[] {Iterable.class},
        constants);
  }
  protected Query range(
      Path<?> leftHandSide,
      String field,
      @Nullable Expression<?> min,
      @Nullable Expression<?> max,
      boolean minInc,
      boolean maxInc,
      QueryMetadata metadata) {
    if (min != null && Number.class.isAssignableFrom(min.getType())
        || max != null && Number.class.isAssignableFrom(max.getType())) {
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> minConstant = (Constant<? extends Number>) min;
      @SuppressWarnings("unchecked") // guarded by previous check
      Constant<? extends Number> maxConstant = (Constant<? extends Number>) max;

      Class<? extends Number> numType =
          minConstant != null ? minConstant.getType() : maxConstant.getType();
      // this is not necessarily safe, but compile time checking
      // on the user side mandates these types to be interchangeable
      @SuppressWarnings("unchecked")
      Class<Number> unboundedNumType = (Class<Number>) numType;
      return numericRange(
          unboundedNumType,
          field,
          minConstant == null ? null : minConstant.getConstant(),
          maxConstant == null ? null : maxConstant.getConstant(),
          minInc,
          maxInc);
    }
    return stringRange(leftHandSide, field, min, max, minInc, maxInc, metadata);
  }
 /**
  * Translates the geometry to a new location using the numeric parameters as offsets.
  *
  * @param expr geometry
  * @param deltax x offset
  * @param deltay y offset
  * @param <T>
  * @return geometry
  */
 public static <T extends Geometry> JTSGeometryExpression<T> translate(
     Expression<T> expr, float deltax, float deltay) {
   return geometryOperation(
       expr.getType(),
       SpatialOps.TRANSLATE,
       expr,
       ConstantImpl.create(deltax),
       ConstantImpl.create(deltay));
 }
  /**
   * template method
   *
   * @param leftHandSide left hand side
   * @param rightHandSide right hand side
   * @return results
   */
  protected String[] convert(
      Path<?> leftHandSide, Expression<?> rightHandSide, QueryMetadata metadata) {
    if (rightHandSide instanceof Operation) {
      Operation<?> operation = (Operation<?>) rightHandSide;
      if (operation.getOperator() == LuceneOps.PHRASE) {
        return Iterables.toArray(WS_SPLITTER.split(operation.getArg(0).toString()), String.class);
      } else if (operation.getOperator() == LuceneOps.TERM) {
        return new String[] {operation.getArg(0).toString()};
      } else {
        throw new IllegalArgumentException(rightHandSide.toString());
      }
    } else if (rightHandSide instanceof ParamExpression<?>) {
      Object value = metadata.getParams().get(rightHandSide);
      if (value == null) {
        throw new ParamNotSetException((ParamExpression<?>) rightHandSide);
      }
      return convert(leftHandSide, value);

    } else if (rightHandSide instanceof Constant<?>) {
      return convert(leftHandSide, ((Constant<?>) rightHandSide).getConstant());
    } else {
      throw new IllegalArgumentException(rightHandSide.toString());
    }
  }
 /**
  * Sets the SRID on a geometry to a particular integer value.
  *
  * @param expr geometry
  * @param srid SRID
  * @param <T>
  * @return geometry
  */
 public static <T extends Geometry> JTSGeometryExpression<T> setSRID(
     Expression<T> expr, int srid) {
   return geometryOperation(expr.getType(), SpatialOps.SET_SRID, expr, ConstantImpl.create(srid));
 }
  /**
   * Create an Evaluator for the given sources and the given optional filter
   *
   * @param metadata query metadata
   * @param joins joins
   * @param filter where condition
   * @return evaluator
   */
  @SuppressWarnings("unchecked")
  public Evaluator<List<Object[]>> createEvaluator(
      QueryMetadata metadata, List<JoinExpression> joins, @Nullable Predicate filter) {
    List<String> sourceNames = new ArrayList<String>();
    List<Type> sourceTypes = new ArrayList<Type>();
    List<Class<?>> sourceClasses = new ArrayList<Class<?>>();
    StringBuilder vars = new StringBuilder();
    CollQuerySerializer ser = new CollQuerySerializer(templates);
    ser.append("java.util.List<Object[]> rv = new java.util.ArrayList<Object[]>();\n");

    List<String> anyJoinMatchers = new ArrayList<String>();

    // creating context
    for (JoinExpression join : joins) {
      Expression<?> target = join.getTarget();
      String typeName = com.mysema.codegen.support.ClassUtils.getName(target.getType());
      if (vars.length() > 0) {
        vars.append(",");
      }
      switch (join.getType()) {
        case DEFAULT:
          ser.append("for (" + typeName + " " + target + " : " + target + "_) {\n");
          vars.append(target);
          sourceNames.add(target + "_");
          sourceTypes.add(
              new SimpleType(Types.ITERABLE, new ClassType(TypeCategory.SIMPLE, target.getType())));
          sourceClasses.add(Iterable.class);
          break;

        case INNERJOIN:
        case LEFTJOIN:
          Operation<?> alias = (Operation<?>) join.getTarget();
          boolean colAnyJoin =
              join.getCondition() != null && join.getCondition().toString().equals("any");
          boolean leftJoin = join.getType() == JoinType.LEFTJOIN;
          String matcher = null;
          if (colAnyJoin) {
            matcher = alias.getArg(1).toString() + "_matched";
            ser.append("boolean " + matcher + " = false;\n");
            anyJoinMatchers.add(matcher);
          }
          ser.append("for (" + typeName + " " + alias.getArg(1) + " : ");
          if (leftJoin) {
            ser.append(CollQueryFunctions.class.getName() + ".leftJoin(");
          }
          if (colAnyJoin) {
            Context context = new Context();
            Expression<?> replacement = alias.getArg(0).accept(collectionAnyVisitor, context);
            ser.handle(replacement);
          } else {
            ser.handle(alias.getArg(0));
          }
          if (alias.getArg(0).getType().equals(Map.class)) {
            ser.append(".values()");
          }
          if (leftJoin) {
            ser.append(")");
          }
          ser.append(") {\n");
          if (matcher != null) {
            ser.append("if (!" + matcher + ") {\n");
          }
          vars.append(alias.getArg(1));
          break;

        default:
          throw new IllegalArgumentException("Illegal join expression " + join);
      }
    }

    // filter
    if (filter != null) {
      ser.append("try {\n");
      ser.append("if (");
      ser.handle(filter).append(") {\n");
      for (String matcher : anyJoinMatchers) {
        ser.append("    " + matcher + " = true;\n");
      }
      ser.append("    rv.add(new Object[]{" + vars + "});\n");
      ser.append("}\n");
      ser.append("} catch (NullPointerException npe) { }\n");
    } else {
      ser.append("rv.add(new Object[]{" + vars + "});\n");
    }

    // closing context
    int amount = joins.size() + anyJoinMatchers.size();
    for (int i = 0; i < amount; i++) {
      ser.append("}\n");
    }
    ser.append("return rv;");

    Map<Object, String> constantToLabel = ser.getConstantToLabel();
    Map<String, Object> constants = getConstants(metadata, constantToLabel);

    ClassType projectionType = new ClassType(TypeCategory.LIST, List.class, Types.OBJECTS);
    return factory.createEvaluator(
        ser.toString(),
        projectionType,
        sourceNames.toArray(new String[sourceNames.size()]),
        sourceTypes.toArray(new Type[sourceTypes.size()]),
        sourceClasses.toArray(new Class[sourceClasses.size()]),
        constants);
  }