blob: ab4b3f91e8640684c723624f186d691768296981 [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.Deterministic;
import net.hydromatic.linq4j.function.NonDeterministic;
import org.junit.Test;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.Collections;
import java.util.concurrent.Callable;
import static net.hydromatic.linq4j.test.BlockBuilderBase.*;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
/**
* Tests factoring out deterministic expressions.
*/
public class DeterministicTest {
/**
* Class to test @Deterministic annotation
*/
public static class TestClass {
@Deterministic
public static int deterministic(int a) {
return a + 1;
}
public static int nonDeterministic(int a) {
return a + 2;
}
}
/**
* Class to test @NonDeterministic annotation
*/
@Deterministic
public static class TestDeterministicClass {
public static int deterministic(int a) {
return a + 1;
}
@NonDeterministic
public static int nonDeterministic(int a) {
return a + 2;
}
}
private boolean isAtomic(Expression e) {
/** Subclass to make a protected method public. */
class MyDeterministicCodeOptimizer extends DeterministicCodeOptimizer {
public MyDeterministicCodeOptimizer() {
super(ClassDeclarationFinder.create());
}
@Override public boolean isConstant(Expression expression) {
return super.isConstant(expression);
}
}
return new MyDeterministicCodeOptimizer().isConstant(e);
}
private static Method getMethod(Class<?> thisClass, String methodName,
Class<?>... paramClasses) {
try {
return thisClass.getMethod(methodName, paramClasses);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private boolean isConstant(Expression e) {
Expression e2 =
e.accept(
new DeterministicCodeOptimizer(ClassDeclarationFinder.create()));
return !e.equals(e2);
}
@Test public void testConstantIsConstant() {
// Small expressions are atomic.
assertThat(isAtomic(Expressions.constant(0)), is(true));
assertThat(isAtomic(Expressions.constant("xxx")), is(true));
assertThat(isAtomic(Expressions.constant(null)), is(true));
Expression e =
Expressions.call(getMethod(Integer.class, "valueOf", int.class),
Expressions.constant(-100));
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
e = Expressions.call(
Integer.class, "valueOf", Expressions.constant(0));
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
e = Expressions.call(Expressions.constant("xxx"), "length");
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
}
@Test public void testFactorOutBinaryAdd() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.add(ONE, TWO))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testFactorOutBinaryAddSurvivesMultipleOptimizations() {
assertThat(
optimize(optimizeExpression(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.add(ONE, TWO)))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testFactorOutBinaryAddNameCollision() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.multiply(
Expressions.add(ONE, TWO),
Expressions.subtract(ONE, TWO)))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2_1_20;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_20 = 1 - 2;\n"
+ " static final int $L4J$C$1_2_1_20 = $L4J$C$1_2 * $L4J$C$1_20;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testFactorOutBinaryAddMul() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.multiply(Expressions.add(ONE, TWO),
THREE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2_3;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_2_3 = $L4J$C$1_2 * 3;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testFactorOutNestedClasses() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.add(
Expressions.add(ONE, FOUR),
Expressions.call(
Expressions.new_(
Callable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
Object.class,
"call",
Collections
.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.multiply(
Expressions.add(ONE, TWO),
THREE)))),
"call",
Collections.<Expression>emptyList())))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_4 + new java.util.concurrent.Callable(){\n"
+ " Object call() {\n"
+ " return $L4J$C$1_2_3;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_2_3 = $L4J$C$1_2 * 3;\n"
+ " }.call();\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_4 = 1 + 4;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testNewBigInteger() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.new_(BigInteger.class,
Expressions.constant("42")))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$new_java_math_BigInteger_42_;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$new_java_math_BigInteger_42_ = new java.math.BigInteger(\n"
+ " \"42\");\n"
+ " };\n"
+ "}\n"));
}
@Test public void testInstanceofTest() {
// Single instanceof is not optimized
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.typeIs(ONE, Boolean.class))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return 1 instanceof Boolean;\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
@Test public void testInstanceofComplexTest() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.orElse(
Expressions.typeIs(ONE, Boolean.class),
Expressions.typeIs(TWO, Integer.class)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_instanceof_Boolean_2_instanceof_Integer;\n"
+ " }\n"
+ "\n"
+ " static final boolean "
+ "$L4J$C$1_instanceof_Boolean_2_instanceof_Integer = 1 instanceof "
+ "Boolean || 2 instanceof Integer;\n"
+ " };\n"
+ "}\n"));
}
@Test public void testIntegerValueOfZeroComplexTest() {
// Integer.valueOf(0) is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
getMethod(Integer.class, "valueOf", int.class),
Expressions.constant(0)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$Integer_valueOf_0_;\n"
+ " }\n"
+ "\n"
+ " static final Integer $L4J$C$Integer_valueOf_0_ = Integer.valueOf(0);\n"
+ " };\n"
+ "}\n"));
}
@Test public void testStaticField() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
Expressions.field(null, BigInteger.class, "ONE"),
"add",
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L))))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return "
+ "$L4J$C$java_math_BigInteger_ONE_add_java_math_BigInteger_valueOf_42L_;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_ = java.math.BigInteger"
+ ".valueOf(42L);\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_ONE_add_java_math_BigInteger_valueOf_42L_ = java.math.BigInteger.ONE.add($L4J$C$java_math_BigInteger_valueOf_42L_);\n"
+ " };\n"
+ "}\n"));
}
@Test public void testBigIntegerValueOf() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L)),
"add",
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L))))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_add_java_math_BigInteger_valued8d57d69;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_ = java.math.BigInteger"
+ ".valueOf(42L);\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_add_java_math_BigInteger_valued8d57d69 = $L4J$C$java_math_BigInteger_valueOf_42L_.add($L4J$C$java_math_BigInteger_valueOf_42L_);\n"
+ " };\n"
+ "}\n"));
}
@Test public void testDeterministicMethodCall() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.call(null,
Types.lookupMethod(TestClass.class,
"deterministic", int.class), ONE))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$net_hydromatic_linq4j_test_DeterministicTest_TestClass_determin1da033bf;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$net_hydromatic_linq4j_test_DeterministicTest_TestClass_determin1da033bf = net.hydromatic.linq4j.test.DeterministicTest.TestClass.deterministic(1);\n"
+ " };\n"
+ "}\n"));
}
@Test public void testNonDeterministicMethodCall() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.call(null,
Types.lookupMethod(TestClass.class,
"nonDeterministic", int.class), ONE))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return net.hydromatic.linq4j.test.DeterministicTest.TestClass.nonDeterministic(1);\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
@Test public void testDeterministicClassDefaultMethod() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.call(null,
Types.lookupMethod(TestDeterministicClass.class,
"deterministic", int.class), ONE))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$net_hydromatic_linq4j_test_DeterministicTest_TestDeterministicCa1bc6d17;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$net_hydromatic_linq4j_test_DeterministicTest_TestDeterministicCa1bc6d17 = net.hydromatic.linq4j.test.DeterministicTest.TestDeterministicClass.deterministic(1);\n"
+ " };\n"
+ "}\n"));
}
@Test public void testDeterministicClassNonDeterministicMethod() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.<Expression>emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.<ParameterExpression>emptyList(),
Blocks.toFunctionBlock(Expressions.call(null,
Types.lookupMethod(TestDeterministicClass.class,
"nonDeterministic", int.class), ONE))))),
equalTo(
"{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return net.hydromatic.linq4j.test.DeterministicTest.TestDeterministicClass.nonDeterministic(1);\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
}
// End DeterministicTest.java