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.adapter.enumerable;
  
  import org.apache.calcite.adapter.java.JavaTypeFactory;
  import org.apache.calcite.interpreter.Row;
  import org.apache.calcite.linq4j.tree.Expression;
  import org.apache.calcite.linq4j.tree.Expressions;
  import org.apache.calcite.linq4j.tree.IndexExpression;
  import org.apache.calcite.linq4j.tree.MemberExpression;
  import org.apache.calcite.linq4j.tree.MethodCallExpression;
  import org.apache.calcite.linq4j.tree.ParameterExpression;
  import org.apache.calcite.linq4j.tree.Statement;
  import org.apache.calcite.linq4j.tree.Types;
  import org.apache.calcite.rel.type.RelDataType;
  import org.apache.calcite.runtime.FlatLists;
  import org.apache.calcite.runtime.Unit;
  import org.apache.calcite.sql.type.SqlTypeName;
  import org.apache.calcite.util.BuiltInMethod;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.List;
  
  import static org.apache.calcite.util.BuiltInMethod.ARRAY_COPY;
  import static org.apache.calcite.util.BuiltInMethod.LIST_TO_ARRAY;
  import static org.apache.calcite.util.BuiltInMethod.ROW_COPY_VALUES;
  
  /**
   * How a row is represented as a Java value.
   */
  public enum JavaRowFormat {
    CUSTOM {
      @Override Type javaRowClass(
          JavaTypeFactory typeFactory,
          RelDataType type) {
        assert type.getFieldCount() > 1;
        return typeFactory.getJavaClass(type);
      }
  
      @Override Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
          int index) {
        return typeFactory.getJavaClass(type.getFieldList().get(index).getType());
      }
  
      @Override public Expression record(
          Type javaRowClass, List<Expression> expressions) {
        switch (expressions.size()) {
        case 0:
          assert javaRowClass == Unit.class;
          return Expressions.field(null, javaRowClass, "INSTANCE");
        default:
          return Expressions.new_(javaRowClass, expressions);
        }
      }
  
      @Override public MemberExpression field(Expression expression, int field,
          @Nullable Type fromType, Type fieldType) {
        final Type type = expression.getType();
        if (type instanceof Types.RecordType) {
          Types.RecordType recordType = (Types.RecordType) type;
          Types.RecordField recordField =
              recordType.getRecordFields().get(field);
          return Expressions.field(expression, recordField.getDeclaringClass(),
              recordField.getName());
        } else {
          return Expressions.field(expression, Types.nthField(field, type));
        }
      }
  
      @Override public List<Statement> copy(ParameterExpression parameter,
          ParameterExpression outputArray, int outputStartIndex, int length) {
        // Parameter holds an expression representing a POJO Object
        // Results in:
        // outputArray[outputStartIndex] = parameter.field{1};
        // ...
        // outputArray[outputStartIndex + length - 1] = parameter.field{length - 1};
        final List<Statement> statements = new ArrayList<>(length);
        for (int i = 0; i < length; i++) {
          statements.add(
              Expressions.statement(
                  Expressions.assign(
              Expressions.arrayIndex(outputArray, Expressions.constant(outputStartIndex + i)),
              field(parameter, i, null, Object.class))));
        }
        return statements;
      }
    },
  
    SCALAR {
      @Override Type javaRowClass(
          JavaTypeFactory typeFactory,
          RelDataType type) {
        assert type.getFieldCount() == 1;
        RelDataType field0Type = type.getFieldList().get(0).getType();
        // nested ROW type is always represented as array.
        if (field0Type.getSqlTypeName() == SqlTypeName.ROW) {
          return Object[].class;
        }
        return typeFactory.getJavaClass(
            type.getFieldList().get(0).getType());
      }
  
      @Override Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
          int index) {
        return javaRowClass(typeFactory, type);
      }
  
      @Override public Expression record(Type javaRowClass, List<Expression> expressions) {
        assert expressions.size() == 1;
        return expressions.get(0);
      }
  
      @Override public Expression field(Expression expression, int field, @Nullable Type fromType,
          Type fieldType) {
        assert field == 0;
        return expression;
      }
  
      @Override public List<Statement> copy(ParameterExpression parameter,
          ParameterExpression outputArray, int outputStartIndex, int length) {
        // Parameter holds an expression representing a scalar Object
        // Results in:
        // outputArray[outputStartIndex] = parameter;
        assert length == 1;
        return FlatLists.of(
            Expressions.statement(
                Expressions.assign(
                Expressions.arrayIndex(outputArray,
                    Expressions.constant(outputStartIndex)), parameter)));
      }
    },
  
    /** A list that is comparable and immutable. Useful for records with 0 fields
     * (empty list is a good singleton) but sometimes also for records with 2 or
     * more fields that you need to be comparable, say as a key in a lookup. */
    LIST {
      @Override Type javaRowClass(
          JavaTypeFactory typeFactory,
          RelDataType type) {
        return FlatLists.ComparableList.class;
      }
  
      @Override Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
          int index) {
        return Object.class;
      }
  
      @Override public Expression record(
          Type javaRowClass, List<Expression> expressions) {
        switch (expressions.size()) {
        case 0:
          return Expressions.field(
            null,
            FlatLists.class,
            "COMPARABLE_EMPTY_LIST");
        case 2:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST2.method,
                  expressions),
              List.class);
        case 3:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST3.method,
                  expressions),
              List.class);
        case 4:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST4.method,
                  expressions),
              List.class);
        case 5:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST5.method,
                  expressions),
              List.class);
        case 6:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST6.method,
                  expressions),
              List.class);
        default:
          return Expressions.convert_(
              Expressions.call(
                  List.class,
                  null,
                  BuiltInMethod.LIST_N.method,
                  Expressions.newArrayInit(
                      Comparable.class,
                      expressions)),
              List.class);
        }
      }
  
      @Override public Expression field(Expression expression, int field,
          @Nullable Type fromType, Type fieldType) {
        final MethodCallExpression e =
            Expressions.call(expression, BuiltInMethod.LIST_GET.method,
                Expressions.constant(field));
        if (fromType == null) {
          fromType = e.getType();
        }
        return EnumUtils.convert(e, fromType, fieldType);
      }
  
      @Override public List<Statement> copy(ParameterExpression parameter,
          ParameterExpression outputArray, int outputStartIndex, int length) {
        // Parameter holds an expression representing a List
        // Results in:
        // System.arraycopy(parameter.toArray(), 0, outputArray, outputStartIndex, length);
        return FlatLists.of(
            Expressions.statement(
                Expressions.call(ARRAY_COPY.method, Expressions.call(parameter, LIST_TO_ARRAY.method),
                Expressions.constant(0), outputArray, Expressions.constant(outputStartIndex),
                Expressions.constant(length))));
      }
    },
  
    /**
     * See {@link org.apache.calcite.interpreter.Row}.
     */
    ROW {
      @Override Type javaRowClass(JavaTypeFactory typeFactory, RelDataType type) {
        return Row.class;
      }
  
      @Override Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
          int index) {
        return Object.class;
      }
  
      @Override public Expression record(Type javaRowClass,
          List<Expression> expressions) {
        return Expressions.call(BuiltInMethod.ROW_AS_COPY.method, expressions);
      }
  
      @Override public Expression field(Expression expression, int field,
          @Nullable Type fromType, Type fieldType) {
        final Expression e =
            Expressions.call(expression,
                BuiltInMethod.ROW_VALUE.method, Expressions.constant(field));
        if (fromType == null) {
          fromType = e.getType();
        }
        return EnumUtils.convert(e, fromType, fieldType);
      }
  
      @Override public List<Statement> copy(ParameterExpression parameter,
        ParameterExpression outputArray, int outputStartIndex, int length) {
        // Parameter holds an expression representing a org.apache.calcite.interpreter.Row
        // Results in:
        // System.arraycopy(parameter.copyValues(), 0, outputArray, outputStartIndex, length);
        return FlatLists.of(
            Expressions.statement(
            Expressions.call(ARRAY_COPY.method,
                Expressions.call(Object[].class, parameter, ROW_COPY_VALUES.method),
                Expressions.constant(0), outputArray, Expressions.constant(outputStartIndex),
                Expressions.constant(length))));
      }
    },
  
    ARRAY {
      @Override Type javaRowClass(
          JavaTypeFactory typeFactory,
          RelDataType type) {
        return Object[].class;
      }
  
      @Override Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
          int index) {
        return Object.class;
      }
  
      @Override public Expression record(Type javaRowClass, List<Expression> expressions) {
        return Expressions.newArrayInit(Object.class, expressions);
      }
  
      @Override public Expression comparer() {
        return Expressions.call(BuiltInMethod.ARRAY_COMPARER.method);
      }
  
      @Override public Expression field(Expression expression, int field,
          @Nullable Type fromType, Type fieldType) {
        final IndexExpression e =
            Expressions.arrayIndex(expression, Expressions.constant(field));
        if (fromType == null) {
          fromType = e.getType();
        }
        return EnumUtils.convert(e, fromType, fieldType);
      }
  
      @Override public List<Statement> copy(ParameterExpression parameter,
          ParameterExpression outputArray, int outputStartIndex, int length) {
        // Parameter holds an expression representing an Object[]
        // Results in:
        // System.arraycopy(parameter, 0, outputArray, outputStartIndex, length);
        return FlatLists.of(
            Expressions.statement(
            Expressions.call(ARRAY_COPY.method, parameter, Expressions.constant(0),
                outputArray, Expressions.constant(outputStartIndex), Expressions.constant(length))));
      }
    };
  
    public JavaRowFormat optimize(RelDataType rowType) {
      switch (rowType.getFieldCount()) {
      case 0:
        return LIST;
      case 1:
        return SCALAR;
      default:
        if (this == SCALAR) {
          return LIST;
        }
        return this;
      }
    }
  
    abstract Type javaRowClass(JavaTypeFactory typeFactory, RelDataType type);
  
    /**
     * Returns the java class that is used to physically store the given field.
     * For instance, a non-null int field can still be stored in a field of type
     * {@code Object.class} in {@link JavaRowFormat#ARRAY} case.
     *
     * @param typeFactory type factory to resolve java types
     * @param type row type
     * @param index field index
     * @return java type used to store the field
     */
    abstract Type javaFieldClass(JavaTypeFactory typeFactory, RelDataType type,
        int index);
  
    public abstract Expression record(
        Type javaRowClass, List<Expression> expressions);
  
    public @Nullable Expression comparer() {
      return null;
    }
  
    /** Returns a reference to a particular field.
     *
     * <p>{@code fromType} may be null; if null, uses the natural type of the
     * field.
     */
    public abstract Expression field(Expression expression, int field,
        @Nullable Type fromType, Type fieldType);
  
    /**
     * Returns an expression that copies the fields of a row of this type to the array.
     */
    public abstract List<Statement> copy(ParameterExpression parameter,
        ParameterExpression outputArray, int outputStartIndex, int length);
  }
  