content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF 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 org.apache.calcite.linq4j.test;
  
  import org.apache.calcite.linq4j.function.Deterministic;
  import org.apache.calcite.linq4j.function.NonDeterministic;
  import org.apache.calcite.linq4j.tree.Blocks;
  import org.apache.calcite.linq4j.tree.ClassDeclarationFinder;
  import org.apache.calcite.linq4j.tree.DeterministicCodeOptimizer;
  import org.apache.calcite.linq4j.tree.Expression;
  import org.apache.calcite.linq4j.tree.Expressions;
  import org.apache.calcite.linq4j.tree.Types;
  
  import org.junit.jupiter.api.Test;
  
  import java.lang.reflect.Method;
  import java.math.BigInteger;
  import java.util.Collections;
  import java.util.concurrent.Callable;
  
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.FOUR;
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.ONE;
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.THREE;
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.TWO;
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.optimize;
  import static org.apache.calcite.linq4j.test.BlockBuilderBase.optimizeExpression;
  
  import static org.hamcrest.CoreMatchers.equalTo;
  import static org.hamcrest.CoreMatchers.is;
  import static org.hamcrest.MatcherAssert.assertThat;
  
  /**
   * Tests factoring out deterministic expressions.
   */
  class DeterministicTest {
    /** Class to test {@code @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 {@code @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 {
        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 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 void testFactorOutBinaryAdd() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.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 void testFactorOutBinaryAddSurvivesMultipleOptimizations() {
      assertThat(
          optimize(
              optimizeExpression(
                  Expressions.new_(Runnable.class,
                      Collections.emptyList(),
                      Expressions.methodDecl(0,
                          int.class,
                          "test",
                          Collections.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 void testFactorOutBinaryAddNameCollision() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.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 void testFactorOutBinaryAddMul() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.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 void testFactorOutNestedClasses() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.emptyList(),
                      Blocks.toFunctionBlock(
                          Expressions.add(
                              Expressions.add(ONE, FOUR),
                              Expressions.call(
                                  Expressions.new_(
                                      Callable.class,
                                      Collections.emptyList(),
                                      Expressions.methodDecl(
                                          0,
                                          Object.class,
                                          "call",
                                          Collections
                                              .emptyList(),
                                          Blocks.toFunctionBlock(
                                              Expressions.multiply(
                                                  Expressions.add(ONE, TWO),
                                                  THREE)))),
                                  "call",
                                  Collections.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 void testNewBigInteger() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0, int.class, "test",
                      Collections.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 void testInstanceofTest() {
      // Single instanceof is not optimized
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0, int.class, "test",
                      Collections.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 void testInstanceofComplexTest() {
      // instanceof is optimized in complex expressions
      assertThat(
          optimize(
              Expressions.new_(Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(0, int.class, "test",
                      Collections.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 void testIntegerValueOfZeroComplexTest() {
      // Integer.valueOf(0) is optimized in complex expressions
      assertThat(
          optimize(
              Expressions.new_(Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(0, int.class, "test",
                      Collections.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 void testStaticField() {
      // instanceof is optimized in complex expressions
      assertThat(
          optimize(
              Expressions.new_(Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(0, int.class, "test",
                      Collections.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 void testBigIntegerValueOf() {
      // instanceof is optimized in complex expressions
      assertThat(
          optimize(
              Expressions.new_(Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(0, int.class, "test",
                      Collections.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 void testDeterministicMethodCall() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.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$org_apache_calcite_linq4j_test_DeterministicTest_TestClass_dete33e8af1c;\n"
              + "      }\n"
              + "\n"
              + "      static final int $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestClass_dete33e8af1c = org.apache.calcite.linq4j.test.DeterministicTest.TestClass.deterministic(1);\n"
              + "    };\n"
              + "}\n"));
    }
  
    @Test void testNonDeterministicMethodCall() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.emptyList(),
                      Blocks.toFunctionBlock(
                          Expressions.call(null,
                              Types.lookupMethod(TestClass.class,
                                  "nonDeterministic", int.class),
                              ONE))))),
          equalTo("{\n"
              + "  return new Runnable(){\n"
              + "      int test() {\n"
              + "        return org.apache.calcite.linq4j.test.DeterministicTest.TestClass.nonDeterministic(1);\n"
              + "      }\n"
              + "\n"
              + "    };\n"
              + "}\n"));
    }
  
    @Test void testDeterministicClassDefaultMethod() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.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$org_apache_calcite_linq4j_test_DeterministicTest_TestDeterminis9de610da;\n"
              + "      }\n"
              + "\n"
              + "      static final int $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestDeterminis9de610da = org.apache.calcite.linq4j.test.DeterministicTest.TestDeterministicClass.deterministic(1);\n"
              + "    };\n"
              + "}\n"));
    }
  
    @Test void testDeterministicClassNonDeterministicMethod() {
      assertThat(
          optimize(
              Expressions.new_(
                  Runnable.class,
                  Collections.emptyList(),
                  Expressions.methodDecl(
                      0,
                      int.class,
                      "test",
                      Collections.emptyList(),
                      Blocks.toFunctionBlock(
                          Expressions.call(null,
                              Types.lookupMethod(TestDeterministicClass.class,
                                  "nonDeterministic", int.class),
                              ONE))))),
          equalTo("{\n"
              + "  return new Runnable(){\n"
              + "      int test() {\n"
              + "        return org.apache.calcite.linq4j.test.DeterministicTest.TestDeterministicClass.nonDeterministic(1);\n"
              + "      }\n"
              + "\n"
              + "    };\n"
              + "}\n"));
    }
  }
  