/*
 * 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.Function1;
import org.apache.calcite.linq4j.tree.BlockBuilder;
import org.apache.calcite.linq4j.tree.BlockStatement;
import org.apache.calcite.linq4j.tree.Blocks;
import org.apache.calcite.linq4j.tree.ClassDeclaration;
import org.apache.calcite.linq4j.tree.DeclarationStatement;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.linq4j.tree.FieldDeclaration;
import org.apache.calcite.linq4j.tree.FunctionExpression;
import org.apache.calcite.linq4j.tree.MemberDeclaration;
import org.apache.calcite.linq4j.tree.MethodCallExpression;
import org.apache.calcite.linq4j.tree.NewExpression;
import org.apache.calcite.linq4j.tree.Node;
import org.apache.calcite.linq4j.tree.ParameterExpression;
import org.apache.calcite.linq4j.tree.Shuttle;
import org.apache.calcite.linq4j.tree.Types;

import com.google.common.collect.ImmutableMap;

import org.junit.Test;

import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;

/**
 * Unit test for {@link org.apache.calcite.linq4j.tree.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 org.apache.calcite.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 org.apache.calcite.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))));

    // "--5" would be a syntax error
    assertEquals(
        "- - 5",
        Expressions.toString(
            Expressions.negate(
                Expressions.negate(
                    Expressions.constant(5)))));

    assertEquals(
        "a.empno",
        Expressions.toString(
            Expressions.field(
                Expressions.parameter(Linq4jTest.Employee.class, "a"),
                "empno")));

    assertEquals(
        "a.length",
        Expressions.toString(
            Expressions.field(
                Expressions.parameter(Object[].class, "a"),
                "length")));

    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 org.apache.calcite.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))));

    // 1-dimensional array with initializer
    assertEquals(
        "new String[] {\n"
            + "  \"foo\",\n"
            + "  null,\n"
            + "  \"bar\\\"baz\"}",
        Expressions.toString(
            Expressions.newArrayInit(
                String.class,
                Expressions.constant("foo"),
                Expressions.constant(null),
                Expressions.constant("bar\"baz"))));

    // 2-dimensional array with initializer
    assertEquals(
        "new String[][] {\n"
            + "  new String[] {\n"
            + "    \"foo\",\n"
            + "    \"bar\"},\n"
            + "  null,\n"
            + "  new String[] {\n"
            + "    null}}",
        Expressions.toString(
            Expressions.newArrayInit(
                String.class,
                2,
                Expressions.constant(new String[] {"foo", "bar"}),
                Expressions.constant(null),
                Expressions.constant(new String[] {null}))));

    // 1-dimensional array
    assertEquals(
        "new String[x + 1]",
        Expressions.toString(
            Expressions.newArrayBounds(
                String.class,
                1,
                Expressions.add(
                    Expressions.parameter(0, int.class, "x"),
                    Expressions.constant(1)))));

    // 3-dimensional array
    assertEquals(
        "new String[x + 1][][]",
        Expressions.toString(
            Expressions.newArrayBounds(
                String.class,
                3,
                Expressions.add(
                    Expressions.parameter(0, int.class, "x"),
                    Expressions.constant(1)))));

    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(
        "org.apache.calcite.linq4j.test.ExpressionTest.MyEnum.X",
        Expressions.toString(
            Expressions.constant(MyEnum.X)));

    // array of enum
    assertEquals(
        "new org.apache.calcite.linq4j.test.ExpressionTest.MyEnum[] {\n"
            + "  org.apache.calcite.linq4j.test.ExpressionTest.MyEnum.X,\n"
            + "  org.apache.calcite.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 org.apache.calcite.linq4j.test.Linq4jTest.Employee[] {\n"
            + "  new org.apache.calcite.linq4j.test.Linq4jTest.Employee(\n"
            + "    100,\n"
            + "    \"Fred\",\n"
            + "    10),\n"
            + "  new org.apache.calcite.linq4j.test.Linq4jTest.Employee(\n"
            + "    110,\n"
            + "    \"Bill\",\n"
            + "    30),\n"
            + "  new org.apache.calcite.linq4j.test.Linq4jTest.Employee(\n"
            + "    120,\n"
            + "    \"Eric\",\n"
            + "    10),\n"
            + "  new org.apache.calcite.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");
    BlockStatement 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() {
    DeclarationStatement xDecl;
    DeclarationStatement 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 testWriteTryCatchFinally() {
    final ParameterExpression cce_ =
        Expressions.parameter(Modifier.FINAL, ClassCastException.class, "cce");
    final ParameterExpression re_ =
        Expressions.parameter(0, RuntimeException.class, "re");
    Node node =
        Expressions.tryCatchFinally(
            Expressions.block(
                Expressions.return_(null,
                    Expressions.call(
                        Expressions.constant("foo"),
                        "length"))),
            Expressions.statement(
                Expressions.call(
                    Expressions.constant("foo"),
                    "toUpperCase")),
            Expressions.catch_(cce_,
                Expressions.return_(null, Expressions.constant(null))),
            Expressions.catch_(re_,
                Expressions.throw_(
                    Expressions.new_(IndexOutOfBoundsException.class))));
    assertEquals(
        "try {\n"
            + "  return \"foo\".length();\n"
            + "} catch (final ClassCastException cce) {\n"
            + "  return null;\n"
            + "} catch (RuntimeException re) {\n"
            + "  throw new IndexOutOfBoundsException();\n"
            + "} finally {\n"
            + "  \"foo\".toUpperCase();\n"
            + "}\n",
        Expressions.toString(node));
  }

  @Test public void testWriteTryFinally() {
    final ParameterExpression cce_ =
        Expressions.parameter(Modifier.FINAL, ClassCastException.class, "cce");
    final ParameterExpression re_ =
        Expressions.parameter(0, RuntimeException.class, "re");
    Node node =
        Expressions.ifThen(
            Expressions.constant(true),
            Expressions.tryFinally(
                Expressions.block(
                    Expressions.return_(null,
                        Expressions.call(
                            Expressions.constant("foo"),
                            "length"))),
                Expressions.statement(
                    Expressions.call(
                        Expressions.constant("foo"),
                        "toUpperCase"))));
    assertEquals(
        "if (true) {\n"
            + "  try {\n"
            + "    return \"foo\".length();\n"
            + "  } finally {\n"
            + "    \"foo\".toUpperCase();\n"
            + "  }\n"
            + "}\n",
        Expressions.toString(node));
  }

  @Test public void testWriteTryCatch() {
    final ParameterExpression cce_ =
        Expressions.parameter(Modifier.FINAL, ClassCastException.class, "cce");
    final ParameterExpression re_ =
        Expressions.parameter(0, RuntimeException.class, "re");
    Node node =
        Expressions.tryCatch(
            Expressions.block(
                Expressions.return_(null,
                    Expressions.call(Expressions.constant("foo"), "length"))),
            Expressions.catch_(cce_,
                Expressions.return_(null, Expressions.constant(null))),
            Expressions.catch_(re_,
                Expressions.return_(null,
                    Expressions.call(re_, "toString"))));
    assertEquals(
        "try {\n"
            + "  return \"foo\".length();\n"
            + "} catch (final ClassCastException cce) {\n"
            + "  return null;\n"
            + "} catch (RuntimeException re) {\n"
            + "  return re.toString();\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));
    BlockStatement expression = statements.toBlock();
    assertEquals(expected, Expressions.toString(expression));
    expression.accept(new Shuttle());
  }

  @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)));
    BlockStatement expression = statements.toBlock();
    final String expected = "{\n"
        + "  final java.util.TreeSet treeSet = new java.util.TreeSet(\n"
        + "    (java.util.Comparator) null);\n"
        + "  return treeSet.add(null);\n"
        + "}\n";
    assertThat(Expressions.toString(expression), is(expected));
    expression.accept(new Shuttle());
  }

  @Test public void testBlockBuilder3() {
/*
    int a = 1;
    int b = a + 2;
    int c = a + 3;
    int d = a + 4;
    int e = {
      int b = a + 3;
      foo(b);
    }
    bar(a, b, c, d, e);
*/
    BlockBuilder builder0 = new BlockBuilder();
    final Expression a = builder0.append("_a", Expressions.constant(1));
    final Expression b =
        builder0.append("_b", Expressions.add(a, Expressions.constant(2)));
    final Expression c =
        builder0.append("_c", Expressions.add(a, Expressions.constant(3)));
    final Expression d =
        builder0.append("_d", Expressions.add(a, Expressions.constant(4)));

    BlockBuilder builder1 = new BlockBuilder();
    final Expression b1 =
        builder1.append("_b", Expressions.add(a, Expressions.constant(3)));
    builder1.add(
        Expressions.statement(
            Expressions.call(ExpressionTest.class, "foo", b1)));
    final Expression e = builder0.append("e", builder1.toBlock());
    builder0.add(
        Expressions.statement(
            Expressions.call(ExpressionTest.class, "bar", a, b, c, d, e)));
    // With the bug in BlockBuilder.append(String, BlockExpression),
    //    bar(1, _b, _c, _d, foo(_d));
    // Correct result is
    //    bar(1, _b, _c, _d, foo(_c));
    // because _c has the same expression (a + 3) as inner b.
    BlockStatement expression = builder0.toBlock();
    assertEquals(
        "{\n"
            + "  final int _b = 1 + 2;\n"
            + "  final int _c = 1 + 3;\n"
            + "  final int _d = 1 + 4;\n"
            + "  org.apache.calcite.linq4j.test.ExpressionTest.bar(1, _b, _c, _d, org.apache.calcite.linq4j.test.ExpressionTest.foo(_c));\n"
            + "}\n",
        Expressions.toString(expression));
    expression.accept(new Shuttle());
  }

  @Test public void testConstantExpression() {
    final Expression constant = Expressions.constant(
        new Object[] {
            1,
            new Object[] {
                (byte) 1, (short) 2, (int) 3, (long) 4,
                (float) 5, (double) 6, (char) 7, true, "string", null
            },
            new AllType(true, (byte) 100, (char) 101, (short) 102, 103,
                (long) 104, (float) 105, (double) 106, new BigDecimal(107),
                new BigInteger("108"), "109", null)
        });
    assertEquals(
        "new Object[] {\n"
            + "  1,\n"
            + "  new Object[] {\n"
            + "    (byte)1,\n"
            + "    (short)2,\n"
            + "    3,\n"
            + "    4L,\n"
            + "    5.0F,\n"
            + "    6.0D,\n"
            + "    (char)7,\n"
            + "    true,\n"
            + "    \"string\",\n"
            + "    null},\n"
            + "  new org.apache.calcite.linq4j.test.ExpressionTest.AllType(\n"
            + "    true,\n"
            + "    (byte)100,\n"
            + "    (char)101,\n"
            + "    (short)102,\n"
            + "    103,\n"
            + "    104L,\n"
            + "    105.0F,\n"
            + "    106.0D,\n"
            + "    new java.math.BigDecimal(107L),\n"
            + "    new java.math.BigInteger(\"108\"),\n"
            + "    \"109\",\n"
            + "    null)}",
        constant.toString());
    constant.accept(new Shuttle());
  }

  @Test public void testClassDecl() {
    final NewExpression newExpression =
        Expressions.new_(
            Object.class,
            Collections.<Expression>emptyList(),
            Arrays.<MemberDeclaration>asList(
                Expressions.fieldDecl(
                    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)))),
                Expressions.fieldDecl(
                    0,
                    Expressions.parameter(int.class, "i"))));
    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));
    newExpression.accept(new Shuttle());
  }

  @Test public void testReturn() {
    assertEquals(
        "if (true) {\n"
            + "  return;\n"
            + "} else {\n"
            + "  return 1;\n"
            + "}\n",
        Expressions.toString(
            Expressions.ifThenElse(
                Expressions.constant(true),
                Expressions.return_(null),
                Expressions.return_(null, Expressions.constant(1)))));
  }

  @Test public void testIfElseIfElse() {
    assertEquals(
        "if (true) {\n"
            + "  return;\n"
            + "} else if (false) {\n"
            + "  return;\n"
            + "} else {\n"
            + "  return 1;\n"
            + "}\n",
        Expressions.toString(
            Expressions.ifThenElse(
                Expressions.constant(true),
                Expressions.return_(null),
                Expressions.constant(false),
                Expressions.return_(null),
                Expressions.return_(null, Expressions.constant(1)))));
  }

  /** 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, Number.class));
    Expression v1 = builder.append(
        "v1",
        Expressions.convert_(
            Expressions.arrayIndex(
                current4,
                Expressions.constant(4)), Short.class));
    Expression v2 = builder.append(
        "v2",
        Expressions.convert_(v, Number.class));
    Expression v3 = builder.append(
        "v3",
        Expressions.convert_(
            Expressions.arrayIndex(
                current4,
                Expressions.constant(4)), Short.class));
    Expression v4 = builder.append(
        "v4",
        Expressions.convert_(v3, Number.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 Short v = (Short) ((Object[]) p)[4];\n"
            + "  return (Number) v == null ? (Boolean) null : ("
            + "(Number) v).intValue() == 1997;\n"
            + "}\n",
        Expressions.toString(builder.toBlock()));
  }

  @Test public void testFor() throws NoSuchFieldException {
    final BlockBuilder builder = new BlockBuilder();
    final ParameterExpression i_ = Expressions.parameter(int.class, "i");
    builder.add(
        Expressions.for_(
            Expressions.declare(
                0, i_, Expressions.constant(0)),
            Expressions.lessThan(i_, Expressions.constant(10)),
            Expressions.postIncrementAssign(i_),
            Expressions.block(
                Expressions.statement(
                    Expressions.call(
                        Expressions.field(
                            null, System.class.getField("out")),
                        "println",
                        i_)))));
    assertEquals(
        "{\n"
            + "  for (int i = 0; i < 10; i++) {\n"
            + "    System.out.println(i);\n"
            + "  }\n"
            + "}\n",
        Expressions.toString(builder.toBlock()));
  }

  @Test public void testFor2() throws NoSuchFieldException {
    final BlockBuilder builder = new BlockBuilder();
    final ParameterExpression i_ = Expressions.parameter(int.class, "i");
    final ParameterExpression j_ = Expressions.parameter(int.class, "j");
    builder.add(
        Expressions.for_(
            Arrays.asList(
                Expressions.declare(
                    0, i_, Expressions.constant(0)),
                Expressions.declare(
                    0, j_, Expressions.constant(10))),
            null,
            null,
            Expressions.block(
                Expressions.ifThen(
                    Expressions.lessThan(
                        Expressions.preIncrementAssign(i_),
                        Expressions.preDecrementAssign(j_)),
                    Expressions.break_(null)))));
    assertEquals(
        "{\n"
            + "  for (int i = 0, j = 10; ; ) {\n"
            + "    if (++i < --j) {\n"
            + "      break;\n"
            + "    }\n"
            + "  }\n"
            + "}\n",
        Expressions.toString(builder.toBlock()));
  }

  @Test public void testEmptyListLiteral() throws Exception {
    assertEquals("java.util.Collections.EMPTY_LIST",
        Expressions.toString(Expressions.constant(Arrays.asList())));
  }

  @Test public void testEneElementListLiteral() throws Exception {
    assertEquals("java.util.Arrays.asList(1)",
        Expressions.toString(Expressions.constant(Arrays.asList(1))));
  }

  @Test public void testTwoElementListLiteral() throws Exception {
    assertEquals("java.util.Arrays.asList(1,\n"
            + "  2)",
        Expressions.toString(Expressions.constant(Arrays.asList(1, 2))));
  }

  @Test public void testNestedListsLiteral() throws Exception {
    assertEquals("java.util.Arrays.asList(java.util.Arrays.asList(1,\n"
            + "    2),\n"
            + "  java.util.Arrays.asList(3,\n"
            + "    4))",
        Expressions.toString(
            Expressions.constant(
                Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4)))));
  }

  @Test public void testEmptyMapLiteral() throws Exception {
    assertEquals("com.google.common.collect.ImmutableMap.of()",
        Expressions.toString(Expressions.constant(new HashMap())));
  }

  @Test public void testOneElementMapLiteral() throws Exception {
    assertEquals("com.google.common.collect.ImmutableMap.of(\"abc\", 42)",
        Expressions.toString(Expressions.constant(Collections.singletonMap("abc", 42))));
  }

  @Test public void testTwoElementMapLiteral() throws Exception {
    assertEquals("com.google.common.collect.ImmutableMap.of(\"abc\", 42,\n"
            + "\"def\", 43)",
        Expressions.toString(Expressions.constant(ImmutableMap.of("abc", 42, "def", 43))));
  }

  @Test public void testTenElementMapLiteral() throws Exception {
    Map<String, String> map = new LinkedHashMap<>(); // for consistent output
    for (int i = 0; i < 10; i++) {
      map.put("key_" + i, "value_" + i);
    }
    assertEquals("com.google.common.collect.ImmutableMap.builder().put(\"key_0\", \"value_0\")\n"
            + ".put(\"key_1\", \"value_1\")\n"
            + ".put(\"key_2\", \"value_2\")\n"
            + ".put(\"key_3\", \"value_3\")\n"
            + ".put(\"key_4\", \"value_4\")\n"
            + ".put(\"key_5\", \"value_5\")\n"
            + ".put(\"key_6\", \"value_6\")\n"
            + ".put(\"key_7\", \"value_7\")\n"
            + ".put(\"key_8\", \"value_8\")\n"
            + ".put(\"key_9\", \"value_9\").build()",
        Expressions.toString(Expressions.constant(map)));
  }

  /** An enum. */
  enum MyEnum {
    X,
    Y {
      public String toString() {
        return "YYY";
      }
    }
  }

  public static int foo(int x) {
    return 0;
  }

  public static int bar(int v, int w, int x, int y, int z) {
    return 0;
  }

  /** A class with a field for each type of interest. */
  public static class AllType {
    public final boolean b;
    public final byte y;
    public final char c;
    public final short s;
    public final int i;
    public final long l;
    public final float f;
    public final double d;
    public final BigDecimal bd;
    public final BigInteger bi;
    public final String str;
    public final Object o;

    public AllType(boolean b, byte y, char c, short s, int i, long l, float f,
        double d, BigDecimal bd, BigInteger bi, String str, Object o) {
      this.b = b;
      this.y = y;
      this.c = c;
      this.s = s;
      this.i = i;
      this.l = l;
      this.f = f;
      this.d = d;
      this.bd = bd;
      this.bi = bi;
      this.str = str;
      this.o = o;
    }
  }
}

// End ExpressionTest.java
