return Stmt.invokeStatic(
Comparisons.class, "nullSafeLessThanOrEqualTo",
generateExpression(traverser, dotNodeResolver, containingMethod), generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.BETWEEN: {
Statement middle = generateExpression(traverser, dotNodeResolver, containingMethod);
Statement small = generateExpression(traverser, dotNodeResolver, containingMethod);
Statement big = generateExpression(traverser, dotNodeResolver, containingMethod);
return Bool.and(
Stmt.invokeStatic(Comparisons.class, "nullSafeLessThanOrEqualTo", small, middle),
Stmt.invokeStatic(Comparisons.class, "nullSafeLessThanOrEqualTo", middle, big));
}
case HqlSqlTokenTypes.NOT_BETWEEN: {
Statement outside = generateExpression(traverser, dotNodeResolver, containingMethod);
Statement small = generateExpression(traverser, dotNodeResolver, containingMethod);
Statement big = generateExpression(traverser, dotNodeResolver, containingMethod);
return Bool.or(
Stmt.invokeStatic(Comparisons.class, "nullSafeLessThan", outside, small),
Stmt.invokeStatic(Comparisons.class, "nullSafeGreaterThan", outside, big));
}
case HqlSqlTokenTypes.NOT_IN:
case HqlSqlTokenTypes.IN: {
final boolean notIn = ast.getType() == HqlSqlTokenTypes.NOT_IN;
Statement thingToTest = generateExpression(traverser, dotNodeResolver, containingMethod);
ast = traverser.next();
if (ast.getType() != HqlSqlTokenTypes.IN_LIST) {
throw new GenerationException("Expected IN_LIST node but found " + ast.getText());
}
List<Statement> collection = new ArrayList<Statement>(ast.getNumberOfChildren());
for (int i = 0; i < ast.getNumberOfChildren(); i++) {
collection.add(Cast.to(Object.class, generateExpression(traverser, dotNodeResolver, containingMethod)));
}
Statement callToComparisonsIn = Stmt.invokeStatic(Comparisons.class, "in", thingToTest, collection.toArray());
return notIn ? Bool.notExpr(callToComparisonsIn) : callToComparisonsIn;
}
case HqlSqlTokenTypes.NOT_LIKE:
case HqlSqlTokenTypes.LIKE: {
Statement valueExpr = Cast.to(String.class, generateExpression(traverser, dotNodeResolver, containingMethod));
Statement patternExpr = Cast.to(String.class, generateExpression(traverser, dotNodeResolver, containingMethod));
Statement escapeCharExpr = Cast.to(String.class, Stmt.loadLiteral(null));
if (ast.getNumberOfChildren() == 3) {
traverser.next();
escapeCharExpr = Cast.to(String.class, generateExpression(traverser, dotNodeResolver, containingMethod));
}
Statement likeStmt = Stmt.invokeStatic(
Comparisons.class, "like", valueExpr, patternExpr, escapeCharExpr);
return ast.getType() == HqlSqlTokenTypes.LIKE ? likeStmt : Bool.notExpr(likeStmt);
}
case HqlSqlTokenTypes.IS_NULL:
return Bool.isNull(generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.IS_NOT_NULL:
return Bool.isNotNull(generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.OR:
return Bool.or(generateExpression(traverser, dotNodeResolver, containingMethod), generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.AND:
return Bool.and(generateExpression(traverser, dotNodeResolver, containingMethod), generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.NOT:
return Bool.notExpr(generateExpression(traverser, dotNodeResolver, containingMethod));
//
// VALUE EXPRESSIONS
//
case HqlSqlTokenTypes.DOT:
DotNode dotNode = (DotNode) ast;
traverser.fastForwardToNextSiblingOf(dotNode);
return dotNodeResolver.resolve(dotNode);
case HqlSqlTokenTypes.NAMED_PARAM:
ParameterNode paramNode = (ParameterNode) ast;
NamedParameterSpecification namedParamSpec = (NamedParameterSpecification) paramNode.getHqlParameterSpecification();
return Stmt.loadVariable("this").invoke("getParameterValue", namedParamSpec.getName());
case HqlSqlTokenTypes.QUOTED_STRING:
return Stmt.loadLiteral(SqlUtil.parseStringLiteral(ast.getText()));
case HqlSqlTokenTypes.UNARY_MINUS:
return ArithmeticExpressionBuilder.create(ArithmeticOperator.Subtraction, generateExpression(traverser, dotNodeResolver, containingMethod));
case HqlSqlTokenTypes.NUM_INT:
case HqlSqlTokenTypes.NUM_DOUBLE:
case HqlSqlTokenTypes.NUM_FLOAT:
// all numeric literals (except longs) are generated as doubles
// (and correspondingly, all "dot nodes" (entity attributes) are retrieved as doubles)
// this allows us to compare almost any numeric type to any other numeric type
// (long and char are the exceptions)
return Stmt.loadLiteral(Double.valueOf(ast.getText()));
case HqlSqlTokenTypes.NUM_LONG:
return Stmt.loadLiteral(Long.valueOf(ast.getText()));
case HqlSqlTokenTypes.TRUE:
case HqlSqlTokenTypes.FALSE:
return Stmt.loadLiteral(((BooleanLiteralNode) ast).getValue());
case HqlSqlTokenTypes.JAVA_CONSTANT:
return Stmt.loadLiteral(MVEL.eval(ast.getText()));
case HqlSqlTokenTypes.METHOD_CALL:
IdentNode methodNameNode = (IdentNode) traverser.next();
SqlNode exprList = (SqlNode) traverser.next();
// trim is weird because it can take keywords (IDENT nodes) in its arg list
if ("trim".equals(methodNameNode.getOriginalText())) {
String trimType = "BOTH";
Statement trimChar = Stmt.loadLiteral(' ');
Statement untrimmedStr;
ast = traverser.next();
if (ast.getType() == HqlSqlTokenTypes.IDENT) {
if (ast.getText().equalsIgnoreCase("BOTH")) {
trimType = "BOTH";
ast = traverser.next();
}
else if (ast.getText().equalsIgnoreCase("LEADING")) {
trimType = "LEADING";
ast = traverser.next();
}
else if (ast.getText().equalsIgnoreCase("TRAILING")) {
trimType = "TRAILING";
ast = traverser.next();
}
}
if (exprList.getNumberOfChildren() == 4 ||
(exprList.getNumberOfChildren() == 3 && ast.getType() != HqlSqlTokenTypes.IDENT)) {
// [[IDENT('LEADING|TRAILING|BOTH')], [<expression:trimchar>], IDENT(FROM),] <expression:untrimmedStr>
// ^^^ you are here
Statement trimStr = generateExpression(new AstInorderTraversal(ast), dotNodeResolver, containingMethod);
trimChar = Stmt.nestedCall(trimStr).invoke("charAt", 0);
ast = traverser.fastForwardTo(ast.getNextSibling());
}
if (ast.getType() == HqlSqlTokenTypes.IDENT) {
if (ast.getText().equalsIgnoreCase("FROM")) {
ast = traverser.next();
}
else {
throw new GenerationException("Found unexpected JPQL keyword " + ast.getText() + " in query (expected FROM)");
}
}
untrimmedStr = generateExpression(new AstInorderTraversal(ast), dotNodeResolver, containingMethod);
traverser.fastForwardToNextSiblingOf(ast);
// declare a local variable with the regex pattern in it
int uniq = uniqueNumber.incrementAndGet();
StringBuilderBuilder trimPattern = Implementations.newStringBuilder();
trimPattern.append("^");
if (trimType.equals("LEADING") || trimType.equals("BOTH")) {
trimPattern.append(Stmt.invokeStatic(Comparisons.class, "escapeRegexChar", trimChar));
trimPattern.append("*");
}
trimPattern.append("(.*?)");
if (trimType.equals("TRAILING") || trimType.equals("BOTH")) {
trimPattern.append(Stmt.invokeStatic(Comparisons.class, "escapeRegexChar", trimChar));
trimPattern.append("*");
}
trimPattern.append("$");
containingMethod.append(
Stmt.declareFinalVariable(
"trimmer" + uniq,
RegExp.class,
Stmt.invokeStatic(RegExp.class, "compile", Stmt.load(trimPattern).invoke("toString"))));
return Stmt.nestedCall(
Stmt.loadVariable("trimmer" + uniq).invoke("exec", Stmt.castTo(String.class, Stmt.load(untrimmedStr))
).invoke("getGroup", 1));
}
// for all other functions, we can pre-process the arguments like this:
Statement[] args = new Statement[exprList.getNumberOfChildren()];
for (int i = 0; i < args.length; i++) {
args[i] = generateExpression(traverser, dotNodeResolver, containingMethod);
}
if ("lower".equals(methodNameNode.getOriginalText())) {
return Stmt.castTo(String.class, Stmt.load(args[0])).invoke("toLowerCase");
}
else if ("upper".equals(methodNameNode.getOriginalText())) {
return Stmt.castTo(String.class, Stmt.load(args[0])).invoke("toUpperCase");
}
else if ("concat".equals(methodNameNode.getOriginalText())) {
StringBuilderBuilder sb = Implementations.newStringBuilder();
for (Statement s : args) {
sb.append(s);
}
return Stmt.load(sb).invoke("toString");
}
else if ("substring".equals(methodNameNode.getOriginalText())) {
int uniq = uniqueNumber.incrementAndGet();
containingMethod.append(Stmt.declareFinalVariable("substrOrig" + uniq, String.class,
Cast.to(String.class, args[0])));
containingMethod.append(Stmt.declareFinalVariable("substrStart" + uniq, int.class,
Arith.expr(Cast.to(Integer.class, args[1]), ArithmeticOperator.Subtraction, 1)));
if (args.length == 2) {
return Stmt.loadVariable("substrOrig" + uniq).invoke("substring", Stmt.loadVariable("substrStart" + uniq));
}
else if (args.length == 3) {
containingMethod.append(Stmt.declareFinalVariable("substrEnd" + uniq, int.class,
Arith.expr(Cast.to(Integer.class, args[2]), ArithmeticOperator.Addition, Stmt.loadVariable("substrStart" + uniq))));
return Stmt.loadVariable("substrOrig" + uniq).invoke(
"substring", Stmt.loadVariable("substrStart" + uniq), Stmt.loadVariable("substrEnd" + uniq));
}
else {
throw new GenerationException("Found " + args.length + " arguments to concat() function. Expected 2 or 3.");
}
}
else if ("length".equals(methodNameNode.getOriginalText())) {
// all numerics must be double for purposes of comparison in this JPQL implementation
return Stmt.castTo(double.class, Stmt.nestedCall(Stmt.castTo(String.class, Stmt.load(args[0])).invoke("length")));
}
else if ("locate".equals(methodNameNode.getOriginalText())) {
// all numerics must be double for purposes of comparison in this JPQL implementation
Statement startIndex = Stmt.loadLiteral(0);
if (args.length == 3) {
// For the 3-arg variant, JPA spec doesn't say whether we return the index in the original string or the index in the substring.
// I'm just guessing it's the same as Java's rule.
startIndex = Arith.expr(Stmt.castTo(int.class, Stmt.load(args[2])), ArithmeticOperator.Subtraction, 1);
}
Statement indexOf = Stmt.castTo(String.class, Stmt.load(args[1])).invoke("indexOf", Stmt.castTo(String.class, Stmt.load(args[0])), startIndex);
return Stmt.castTo(double.class, Stmt.nestedCall(Arith.expr(indexOf, ArithmeticOperator.Addition, 1)));
}
throw new UnsupportedOperationException("The JPQL function " + methodNameNode.getOriginalText() + " is not supported");
default: