blob: 3dc6c3684fb4fa82a465d7c2e7f8f9708623a2c3 [file] [log] [blame]
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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 net.hydromatic.linq4j.test;
import net.hydromatic.linq4j.expressions.*;
import net.hydromatic.linq4j.function.Function1;
import org.junit.Test;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import static org.junit.Assert.*;
/**
* Unit test for {@link net.hydromatic.linq4j.expressions.Expression}
* and subclasses.
*/
public class ExpressionTest {
@Test public void testLambdaCallsBinaryOp() {
// A parameter for the lambda expression.
ParameterExpression paramExpr =
Expressions.parameter(Double.TYPE, "arg");
// This expression represents a lambda expression
// that adds 1 to the parameter value.
FunctionExpression lambdaExpr = Expressions.lambda(
Expressions.add(
paramExpr,
Expressions.constant(2d)),
Arrays.asList(paramExpr));
// Print out the expression.
String s = Expressions.toString(lambdaExpr);
assertEquals(
"new net.hydromatic.linq4j.function.Function1() {\n"
+ " public double apply(double arg) {\n"
+ " return arg + 2.0D;\n"
+ " }\n"
+ " public Object apply(Double arg) {\n"
+ " return apply(\n"
+ " arg.doubleValue());\n"
+ " }\n"
+ " public Object apply(Object arg) {\n"
+ " return apply(\n"
+ " (Double) arg);\n"
+ " }\n"
+ "}\n",
s);
// Compile and run the lambda expression.
// The value of the parameter is 1.5.
double n = (Double) lambdaExpr.compile().dynamicInvoke(1.5d);
// This code example produces the following output:
//
// arg => (arg +2)
// 3
assertEquals(3.5D, n, 0d);
}
@Test public void testLambdaPrimitiveTwoArgs() {
// Parameters for the lambda expression.
ParameterExpression paramExpr =
Expressions.parameter(int.class, "key");
ParameterExpression param2Expr =
Expressions.parameter(int.class, "key2");
FunctionExpression lambdaExpr = Expressions.lambda(
Expressions.block(
(Type) null,
Expressions.return_(
null, paramExpr)),
Arrays.asList(paramExpr, param2Expr));
// Print out the expression.
String s = Expressions.toString(lambdaExpr);
assertEquals("new net.hydromatic.linq4j.function.Function2() {\n"
+ " public int apply(int key, int key2) {\n"
+ " return key;\n"
+ " }\n"
+ " public Integer apply(Integer key, Integer key2) {\n"
+ " return apply(\n"
+ " key.intValue(),\n"
+ " key2.intValue());\n"
+ " }\n"
+ " public Integer apply(Object key, Object key2) {\n"
+ " return apply(\n"
+ " (Integer) key,\n"
+ " (Integer) key2);\n"
+ " }\n"
+ "}\n",
s);
}
@Test public void testLambdaCallsTwoArgMethod() throws NoSuchMethodException {
// A parameter for the lambda expression.
ParameterExpression paramS =
Expressions.parameter(String.class, "s");
ParameterExpression paramBegin =
Expressions.parameter(Integer.TYPE, "begin");
ParameterExpression paramEnd =
Expressions.parameter(Integer.TYPE, "end");
// This expression represents a lambda expression
// that adds 1 to the parameter value.
FunctionExpression lambdaExpr =
Expressions.lambda(
Expressions.call(
paramS,
String.class.getMethod(
"substring", Integer.TYPE, Integer.TYPE),
paramBegin,
paramEnd), paramS, paramBegin, paramEnd);
// Compile and run the lambda expression.
String s =
(String) lambdaExpr.compile().dynamicInvoke("hello world", 3, 7);
assertEquals("lo w", s);
}
@Test public void testFoldAnd() {
// empty list yields true
final List<Expression> list0 = Collections.emptyList();
assertEquals(
"true",
Expressions.toString(
Expressions.foldAnd(list0)));
assertEquals(
"false",
Expressions.toString(
Expressions.foldOr(list0)));
final List<Expression> list1 =
Arrays.asList(
Expressions.equal(Expressions.constant(1), Expressions.constant(2)),
Expressions.equal(Expressions.constant(3), Expressions.constant(4)),
Expressions.constant(true),
Expressions.equal(Expressions.constant(5),
Expressions.constant(6)));
// true is eliminated from AND
assertEquals(
"1 == 2 && 3 == 4 && 5 == 6",
Expressions.toString(
Expressions.foldAnd(list1)));
// a single true makes OR true
assertEquals(
"true",
Expressions.toString(
Expressions.foldOr(list1)));
final List<Expression> list2 =
Collections.<Expression>singletonList(
Expressions.constant(true));
assertEquals(
"true",
Expressions.toString(
Expressions.foldAnd(list2)));
assertEquals(
"true",
Expressions.toString(
Expressions.foldOr(list2)));
final List<Expression> list3 =
Arrays.asList(
Expressions.equal(Expressions.constant(1), Expressions.constant(2)),
Expressions.constant(false),
Expressions.equal(Expressions.constant(5),
Expressions.constant(6)));
// false causes whole list to be false
assertEquals(
"false",
Expressions.toString(
Expressions.foldAnd(list3)));
assertEquals(
"1 == 2 || 5 == 6",
Expressions.toString(
Expressions.foldOr(list3)));
}
@Test public void testWrite() {
assertEquals(
"1 + 2.0F + 3L + Long.valueOf(4L)",
Expressions.toString(
Expressions.add(
Expressions.add(
Expressions.add(
Expressions.constant(1),
Expressions.constant(2F, Float.TYPE)),
Expressions.constant(3L, Long.TYPE)),
Expressions.constant(4L, Long.class))));
assertEquals(
"new java.math.BigDecimal(31415926L, 7)",
Expressions.toString(
Expressions.constant(
BigDecimal.valueOf(314159260, 8))));
// Parentheses needed, to override the left-associativity of +.
assertEquals(
"1 + (2 + 3)",
Expressions.toString(
Expressions.add(
Expressions.constant(1),
Expressions.add(
Expressions.constant(2),
Expressions.constant(3)))));
// No parentheses needed; higher precedence of * achieves the desired
// effect.
assertEquals(
"1 + 2 * 3",
Expressions.toString(
Expressions.add(
Expressions.constant(1),
Expressions.multiply(
Expressions.constant(2),
Expressions.constant(3)))));
assertEquals(
"1 * (2 + 3)",
Expressions.toString(
Expressions.multiply(
Expressions.constant(1),
Expressions.add(
Expressions.constant(2),
Expressions.constant(3)))));
// Parentheses needed, to overcome right-associativity of =.
assertEquals(
"(1 = 2) = 3",
Expressions.toString(
Expressions.assign(
Expressions.assign(
Expressions.constant(1), Expressions.constant(2)),
Expressions.constant(3))));
// Ternary operator.
assertEquals(
"1 < 2 ? (3 < 4 ? 5 : 6) : 7 < 8 ? 9 : 10",
Expressions.toString(
Expressions.condition(
Expressions.lessThan(
Expressions.constant(1),
Expressions.constant(2)),
Expressions.condition(
Expressions.lessThan(
Expressions.constant(3),
Expressions.constant(4)),
Expressions.constant(5),
Expressions.constant(6)),
Expressions.condition(
Expressions.lessThan(
Expressions.constant(7),
Expressions.constant(8)),
Expressions.constant(9),
Expressions.constant(10)))));
assertEquals(
"0 + (double) (2 + 3)",
Expressions.toString(
Expressions.add(
Expressions.constant(0),
Expressions.convert_(
Expressions.add(
Expressions.constant(2), Expressions.constant(3)),
Double.TYPE))));
assertEquals(
"a.empno",
Expressions.toString(
Expressions.field(
Expressions.parameter(Linq4jTest.Employee.class, "a"),
"empno")));
assertEquals(
"java.util.Collections.EMPTY_LIST",
Expressions.toString(
Expressions.field(
null, Collections.class, "EMPTY_LIST")));
final ParameterExpression paramX =
Expressions.parameter(String.class, "x");
assertEquals(
"new net.hydromatic.linq4j.function.Function1() {\n"
+ " public int apply(String x) {\n"
+ " return x.length();\n"
+ " }\n"
+ " public Object apply(Object x) {\n"
+ " return apply(\n"
+ " (String) x);\n"
+ " }\n"
+ "}\n",
Expressions.toString(
Expressions.lambda(
Function1.class,
Expressions.call(
paramX, "length", Collections.<Expression>emptyList()),
Arrays.asList(paramX))));
assertEquals(
"new String[] {\n"
+ " \"foo\",\n"
+ " null,\n"
+ " \"bar\\\"baz\"}",
Expressions.toString(
Expressions.newArrayInit(
String.class,
Arrays.<Expression>asList(
Expressions.constant("foo"),
Expressions.constant(null),
Expressions.constant("bar\"baz")))));
assertEquals(
"(int) ((String) (Object) \"foo\").length()",
Expressions.toString(
Expressions.convert_(
Expressions.call(
Expressions.convert_(
Expressions.convert_(
Expressions.constant("foo"),
Object.class),
String.class),
"length",
Collections.<Expression>emptyList()),
Integer.TYPE)));
// resolving a static method
assertEquals(
"Integer.valueOf(\"0123\")",
Expressions.toString(
Expressions.call(
Integer.class,
"valueOf",
Collections.<Expression>singletonList(
Expressions.constant("0123")))));
// precedence of not and instanceof
assertEquals(
"!(o instanceof String)",
Expressions.toString(
Expressions.not(
Expressions.typeIs(
Expressions.parameter(Object.class, "o"),
String.class))));
// not not
assertEquals(
"!!(o instanceof String)",
Expressions.toString(
Expressions.not(
Expressions.not(
Expressions.typeIs(
Expressions.parameter(Object.class, "o"),
String.class)))));
}
@Test public void testWriteConstant() {
// array of primitives
assertEquals(
"new int[] {\n"
+ " 1,\n"
+ " 2,\n"
+ " -1}",
Expressions.toString(
Expressions.constant(new int[]{1, 2, -1})));
// primitive
assertEquals(
"-12",
Expressions.toString(
Expressions.constant(-12)));
assertEquals(
"(short)-12",
Expressions.toString(
Expressions.constant((short) -12)));
assertEquals(
"(byte)-12",
Expressions.toString(
Expressions.constant((byte) -12)));
// boxed primitives
assertEquals(
"Integer.valueOf(1)",
Expressions.toString(
Expressions.constant(1, Integer.class)));
assertEquals(
"Double.valueOf(-3.14D)",
Expressions.toString(
Expressions.constant(-3.14, Double.class)));
assertEquals(
"Boolean.valueOf(true)",
Expressions.toString(
Expressions.constant(true, Boolean.class)));
// primitive with explicit class
assertEquals(
"1",
Expressions.toString(
Expressions.constant(1, int.class)));
assertEquals(
"(short)1",
Expressions.toString(
Expressions.constant(1, short.class)));
assertEquals(
"(byte)1",
Expressions.toString(
Expressions.constant(1, byte.class)));
assertEquals(
"-3.14D",
Expressions.toString(
Expressions.constant(-3.14, double.class)));
assertEquals(
"true",
Expressions.toString(
Expressions.constant(true, boolean.class)));
// objects and nulls
assertEquals(
"new String[] {\n"
+ " \"foo\",\n"
+ " null}",
Expressions.toString(
Expressions.constant(new String[] {"foo", null})));
// string
assertEquals(
"\"hello, \\\"world\\\"!\"",
Expressions.toString(
Expressions.constant("hello, \"world\"!")));
// enum
assertEquals(
"net.hydromatic.linq4j.test.ExpressionTest.MyEnum.X",
Expressions.toString(
Expressions.constant(MyEnum.X)));
// array of enum
assertEquals(
"new net.hydromatic.linq4j.test.ExpressionTest.MyEnum[] {\n"
+ " net.hydromatic.linq4j.test.ExpressionTest.MyEnum.X,\n"
+ " net.hydromatic.linq4j.test.ExpressionTest.MyEnum.Y}",
Expressions.toString(
Expressions.constant(new MyEnum[]{MyEnum.X, MyEnum.Y})));
// class
assertEquals(
"java.lang.String.class",
Expressions.toString(
Expressions.constant(String.class)));
// array class
assertEquals(
"int[].class",
Expressions.toString(
Expressions.constant(int[].class)));
assertEquals(
"java.util.List[][].class",
Expressions.toString(
Expressions.constant(List[][].class)));
// automatically call constructor if it matches fields
assertEquals(
"new net.hydromatic.linq4j.test.Linq4jTest.Employee[] {\n"
+ " new net.hydromatic.linq4j.test.Linq4jTest.Employee(\n"
+ " 100,\n"
+ " \"Fred\",\n"
+ " 10),\n"
+ " new net.hydromatic.linq4j.test.Linq4jTest.Employee(\n"
+ " 110,\n"
+ " \"Bill\",\n"
+ " 30),\n"
+ " new net.hydromatic.linq4j.test.Linq4jTest.Employee(\n"
+ " 120,\n"
+ " \"Eric\",\n"
+ " 10),\n"
+ " new net.hydromatic.linq4j.test.Linq4jTest.Employee(\n"
+ " 130,\n"
+ " \"Janet\",\n"
+ " 10)}",
Expressions.toString(
Expressions.constant(Linq4jTest.emps)));
}
@Test public void testWriteArray() {
assertEquals(
"1 + integers[2 + index]",
Expressions.toString(
Expressions.add(
Expressions.constant(1),
Expressions.arrayIndex(
Expressions.variable(int[].class, "integers"),
Expressions.add(
Expressions.constant(2),
Expressions.variable(int.class, "index"))))));
}
@Test public void testWriteAnonymousClass() {
// final List<String> baz = Arrays.asList("foo", "bar");
// new AbstractList<String>() {
// public int size() {
// return baz.size();
// }
// public String get(int index) {
// return ((String) baz.get(index)).toUpperCase();
// }
// }
final ParameterExpression bazParameter =
Expressions.parameter(
Types.of(List.class, String.class),
"baz");
final ParameterExpression indexParameter =
Expressions.parameter(
Integer.TYPE,
"index");
BlockExpression e =
Expressions.block(
Expressions.declare(
Modifier.FINAL,
bazParameter,
Expressions.call(
Arrays.class,
"asList",
Arrays.<Expression>asList(
Expressions.constant("foo"),
Expressions.constant("bar")))),
Expressions.statement(
Expressions.new_(
Types.of(AbstractList.class, String.class),
Collections.<Expression>emptyList(),
Arrays.<MemberDeclaration>asList(
Expressions.fieldDecl(
Modifier.PUBLIC | Modifier.FINAL,
Expressions.parameter(
String.class,
"qux"),
Expressions.constant("xyzzy")),
Expressions.methodDecl(
Modifier.PUBLIC,
Integer.TYPE,
"size",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
bazParameter,
"size",
Collections.<Expression>emptyList()))),
Expressions.methodDecl(
Modifier.PUBLIC,
String.class,
"get",
Arrays.asList(indexParameter),
Blocks.toFunctionBlock(
Expressions.call(
Expressions.convert_(
Expressions.call(
bazParameter,
"get",
Arrays.<Expression>asList(
indexParameter)),
String.class),
"toUpperCase",
Collections
.<Expression>emptyList())))))));
assertEquals(
"{\n"
+ " final java.util.List<String> baz = java.util.Arrays.asList(\"foo\", \"bar\");\n"
+ " new java.util.AbstractList<String>(){\n"
+ " public final String qux = \"xyzzy\";\n"
+ " public int size() {\n"
+ " return baz.size();\n"
+ " }\n"
+ "\n"
+ " public String get(int index) {\n"
+ " return ((String) baz.get(index)).toUpperCase();\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n",
Expressions.toString(e));
}
@Test public void testWriteWhile() {
DeclarationExpression xDecl, yDecl;
Node node =
Expressions.block(
xDecl = Expressions.declare(
0,
"x",
Expressions.constant(10)),
yDecl = Expressions.declare(
0,
"y",
Expressions.constant(0)),
Expressions.while_(
Expressions.lessThan(
xDecl.parameter,
Expressions.constant(5)),
Expressions.statement(
Expressions.preIncrementAssign(yDecl.parameter))));
assertEquals(
"{\n"
+ " int x = 10;\n"
+ " int y = 0;\n"
+ " while (x < 5) {\n"
+ " ++y;\n"
+ " }\n"
+ "}\n",
Expressions.toString(node));
}
@Test public void testType() {
// Type of ternary operator is the gcd of its arguments.
assertEquals(
long.class,
Expressions.condition(
Expressions.constant(true),
Expressions.constant(5),
Expressions.constant(6L)).getType());
assertEquals(
long.class,
Expressions.condition(
Expressions.constant(true),
Expressions.constant(5L),
Expressions.constant(6)).getType());
// If one of the arguments is null constant, it is implicitly coerced.
assertEquals(
String.class,
Expressions.condition(
Expressions.constant(true),
Expressions.constant("xxx"),
Expressions.constant(null)).getType());
assertEquals(
Integer.class,
Expressions.condition(
Expressions.constant(true),
Expressions.constant(0),
Expressions.constant(null)).getType());
}
@Test public void testCompile() throws NoSuchMethodException {
// Creating a parameter for the expression tree.
ParameterExpression param = Expressions.parameter(String.class);
// Creating an expression for the method call and specifying its
// parameter.
MethodCallExpression methodCall =
Expressions.call(
Integer.class,
"valueOf",
Collections.<Expression>singletonList(param));
// The following statement first creates an expression tree,
// then compiles it, and then runs it.
int x =
Expressions.<Function1<String, Integer>>lambda(
methodCall,
new ParameterExpression[] { param })
.getFunction()
.apply("1234");
assertEquals(1234, x);
}
@Test public void testBlockBuilder() {
checkBlockBuilder(
false,
"{\n"
+ " final int three = 1 + 2;\n"
+ " final int six = three * 2;\n"
+ " final int nine = three * three;\n"
+ " final int eighteen = three + six + nine;\n"
+ " return eighteen;\n"
+ "}\n");
checkBlockBuilder(
true,
"{\n"
+ " final int three = 1 + 2;\n"
+ " return three + three * 2 + three * three;\n"
+ "}\n");
}
public void checkBlockBuilder(boolean optimizing, String expected) {
BlockBuilder statements = new BlockBuilder(optimizing);
Expression one =
statements.append(
"one", Expressions.constant(1));
Expression two =
statements.append(
"two", Expressions.constant(2));
Expression three =
statements.append(
"three", Expressions.add(one, two));
Expression six =
statements.append(
"six",
Expressions.multiply(three, two));
Expression nine =
statements.append(
"nine",
Expressions.multiply(three, three));
Expression eighteen =
statements.append(
"eighteen",
Expressions.add(
Expressions.add(three, six),
nine));
statements.add(Expressions.return_(null, eighteen));
assertEquals(expected, Expressions.toString(statements.toBlock()));
}
@Test public void testBlockBuilder2() {
BlockBuilder statements = new BlockBuilder();
Expression element =
statements.append(
"element", Expressions.constant(null));
Expression comparator =
statements.append(
"comparator", Expressions.constant(null, Comparator.class));
Expression treeSet =
statements.append(
"treeSet",
Expressions.new_(
TreeSet.class,
Arrays.asList(comparator)));
statements.add(
Expressions.return_(
null,
Expressions.call(
treeSet,
"add",
element)));
assertEquals(
"{\n"
+ " final java.util.Comparator comparator = null;\n"
+ " return new java.util.TreeSet(\n"
+ " comparator).add(null);\n"
+ "}\n",
Expressions.toString(statements.toBlock()));
}
@Test public void testClassDecl() {
final NewExpression newExpression =
Expressions.new_(
Object.class,
Collections.<Expression>emptyList(),
Arrays.<MemberDeclaration>asList(
new FieldDeclaration(
Modifier.PUBLIC | Modifier.FINAL,
Expressions.parameter(String.class, "foo"),
Expressions.constant("bar")),
new ClassDeclaration(
Modifier.PUBLIC | Modifier.STATIC,
"MyClass",
null,
Collections.<Type>emptyList(),
Arrays.<MemberDeclaration>asList(
new FieldDeclaration(
0,
Expressions.parameter(int.class, "x"),
Expressions.constant(0)))),
new FieldDeclaration(
0,
Expressions.parameter(int.class, "i"),
null)));
assertEquals(
"new Object(){\n"
+ " public final String foo = \"bar\";\n"
+ " public static class MyClass {\n"
+ " int x = 0;\n"
+ " }\n"
+ " int i;\n"
+ "}",
Expressions.toString(newExpression));
}
@Test public void testReturn() {
assertEquals(
"if (true) {\n"
+ " return;\n"
+ "}\n",
Expressions.toString(
Expressions.ifThen(
Expressions.constant(true),
Expressions.return_(null))));
}
/** Test for common sub-expression elimination. */
@Test public void testSubExpressionElimination() {
final BlockBuilder builder = new BlockBuilder(true);
ParameterExpression x = Expressions.parameter(Object.class, "p");
Expression current4 = builder.append(
"current4",
Expressions.convert_(x, Object[].class));
Expression v = builder.append(
"v",
Expressions.convert_(
Expressions.arrayIndex(
current4,
Expressions.constant(4)), Short.class));
Expression v0 = builder.append(
"v0",
Expressions.convert_(v, Integer.class));
Expression v1 = builder.append(
"v1",
Expressions.convert_(
Expressions.arrayIndex(
current4,
Expressions.constant(4)), Short.class));
Expression v2 = builder.append(
"v2",
Expressions.convert_(v, Integer.class));
Expression v3 = builder.append(
"v3",
Expressions.convert_(
Expressions.arrayIndex(
current4,
Expressions.constant(4)), Short.class));
Expression v4 = builder.append(
"v4",
Expressions.convert_(v3, Integer.class));
Expression v5 = builder.append("v5", Expressions.call(v4, "intValue"));
Expression v6 = builder.append(
"v6",
Expressions.condition(
Expressions.equal(v2, Expressions.constant(null)),
Expressions.constant(null),
Expressions.equal(v5, Expressions.constant(1997))));
builder.add(Expressions.return_(null, v6));
assertEquals(
"{\n"
+ " final Integer v0 = (Integer) (Short) ((Object[]) p)[4];\n"
+ " return v0 == null ? null : v0.intValue() == 1997;\n"
+ "}\n",
Expressions.toString(builder.toBlock()));
}
enum MyEnum {
X,
Y {
public String toString() {
return "YYY";
}
}
}
}
// End ExpressionTest.java