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.tree;
  
  import org.apache.calcite.linq4j.util.Compatible;
  
  import org.checkerframework.checker.nullness.qual.Nullable;
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.Type;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.nio.charset.Charset;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  import java.util.stream.Collectors;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Represents an expression that has a constant value.
   */
  public class ConstantExpression extends Expression {
    public final @Nullable Object value;
  
    public ConstantExpression(Type type, @Nullable Object value) {
      super(ExpressionType.Constant, type);
      this.value = value;
      if (value != null) {
        if (type instanceof Class) {
          Class clazz = (Class) type;
          clazz = Primitive.box(clazz);
          if (!clazz.isInstance(value)
              && !((clazz == Float.class || clazz == Double.class)
                  && value instanceof BigDecimal)) {
            throw new AssertionError(
                "value " + value + " does not match type " + type);
          }
        }
      }
    }
  
    @Override public @Nullable Object evaluate(Evaluator evaluator) {
      return value;
    }
  
    @Override public Expression accept(Shuttle shuttle) {
      return shuttle.visit(this);
    }
  
    @Override public <R> R accept(Visitor<R> visitor) {
      return visitor.visit(this);
    }
  
    @Override void accept(ExpressionWriter writer, int lprec, int rprec) {
      if (value == null) {
        if (!writer.requireParentheses(this, lprec, rprec)) {
          writer.append("(").append(type).append(") null");
        }
        return;
      }
      write(writer, value, type);
    }
  
    private static ExpressionWriter write(ExpressionWriter writer,
        final Object value, @Nullable Type type) {
      if (value == null) {
        return writer.append("null");
      }
      if (type == null) {
        type = value.getClass();
        type = Primitive.unbox(type);
      }
      if (value instanceof String) {
        escapeString(writer.getBuf(), (String) value);
        return writer;
      }
      final Primitive primitive = Primitive.of(type);
      final BigDecimal bigDecimal;
      if (primitive != null) {
        switch (primitive) {
        case BYTE:
          return writer.append("(byte)").append(((Byte) value).intValue());
        case CHAR:
          return writer.append("(char)").append((int) (Character) value);
        case SHORT:
          return writer.append("(short)").append(((Short) value).intValue());
        case LONG:
          return writer.append(value).append("L");
        case FLOAT:
          if (value instanceof BigDecimal) {
            bigDecimal = (BigDecimal) value;
          } else {
            float f = (Float) value;
            if (f == Float.POSITIVE_INFINITY) {
              return writer.append("Float.POSITIVE_INFINITY");
            } else if (f == Float.NEGATIVE_INFINITY) {
              return writer.append("Float.NEGATIVE_INFINITY");
            } else if (Float.isNaN(f)) {
              return writer.append("Float.NaN");
            }
            bigDecimal = BigDecimal.valueOf(f);
          }
          if (bigDecimal.precision() > 6) {
            return writer.append("Float.intBitsToFloat(")
                .append(Float.floatToIntBits(bigDecimal.floatValue()))
                .append(")");
          }
          return writer.append(value).append("F");
        case DOUBLE:
          if (value instanceof BigDecimal) {
            bigDecimal = (BigDecimal) value;
          } else {
            double d = (Double) value;
            if (d == Double.POSITIVE_INFINITY) {
              return writer.append("Double.POSITIVE_INFINITY");
            } else if (d == Double.NEGATIVE_INFINITY) {
              return writer.append("Double.NEGATIVE_INFINITY");
            } else if (Double.isNaN(d)) {
              return writer.append("Double.NaN");
            }
            bigDecimal = BigDecimal.valueOf(d);
          }
          if (bigDecimal.precision() > 10) {
            return writer.append("Double.longBitsToDouble(")
                .append(Double.doubleToLongBits(bigDecimal.doubleValue()))
                .append("L)");
          }
          return writer.append(value).append("D");
        default:
          return writer.append(value);
        }
      }
      final Primitive primitive2 = Primitive.ofBox(type);
      if (primitive2 != null) {
        writer.append(primitive2.boxName + ".valueOf(");
        write(writer, value, primitive2.primitiveClass);
        return writer.append(")");
      }
      Primitive primitive3 = Primitive.ofBox(value.getClass());
      if (Object.class.equals(type) && primitive3 != null) {
        return write(writer, value, primitive3.primitiveClass);
      }
      if (value instanceof Enum) {
        return writer.append(((Enum) value).getDeclaringClass())
            .append('.')
            .append(((Enum) value).name());
      }
      if (value instanceof BigDecimal) {
        bigDecimal = ((BigDecimal) value).stripTrailingZeros();
        try {
          final int scale = bigDecimal.scale();
          final long exact = bigDecimal.scaleByPowerOfTen(scale).longValueExact();
          writer.append("java.math.BigDecimal.valueOf(").append(exact).append("L");
          if (scale != 0) {
            writer.append(", ").append(scale);
          }
          return writer.append(")");
        } catch (ArithmeticException e) {
          return writer.append("new java.math.BigDecimal(\"")
              .append(bigDecimal.toString()).append("\")");
        }
      }
      if (value instanceof BigInteger) {
        BigInteger bigInteger = (BigInteger) value;
        return writer.append("new java.math.BigInteger(\"")
            .append(bigInteger.toString()).append("\")");
      }
      if (value instanceof Class) {
        Class clazz = (Class) value;
        return writer.append(clazz.getCanonicalName()).append(".class");
      }
      if (value instanceof Types.RecordType) {
        final Types.RecordType recordType = (Types.RecordType) value;
        return writer.append(recordType.getName()).append(".class");
      }
      if (value.getClass().isArray()) {
        writer.append("new ").append(requireNonNull(value.getClass().getComponentType()));
        list(writer, Primitive.asList(value), "[] {\n", ",\n", "}");
        return writer;
      }
      if (value instanceof List) {
        if (((List) value).isEmpty()) {
          writer.append("java.util.Collections.EMPTY_LIST");
          return writer;
        }
        list(writer, (List) value, "java.util.Arrays.asList(", ",\n", ")");
        return writer;
      }
      if (value instanceof Map) {
        return writeMap(writer, (Map) value);
      }
      if (value instanceof Set) {
        return writeSet(writer, (Set) value);
      }
      if (value instanceof Charset) {
        writer.append("java.nio.charset.Charset.forName(\"");
        writer.append(value);
        writer.append("\")");
        return writer;
      }
  
      final Field[] classFields = getClassFields(value.getClass());
      Constructor constructor = matchingConstructor(value, classFields);
      if (constructor != null) {
        writer.append("new ").append(value.getClass());
        list(writer,
            Arrays.stream(classFields)
                // <@Nullable Object> is needed for CheckerFramework
                .<@Nullable Object>map(field -> getFieldValue(value, field))
                .collect(Collectors.toList()),
            "(\n", ",\n", ")");
        return writer;
      }
  
      return writer.append(value);
    }
  
    private static void list(ExpressionWriter writer, List list,
        String begin, String sep, String end) {
      writer.begin(begin);
      for (int i = 0; i < list.size(); i++) {
        Object value = list.get(i);
        if (i > 0) {
          writer.append(sep).indent();
        }
        write(writer, value, null);
      }
      writer.end(end);
    }
  
    private static ExpressionWriter writeMap(ExpressionWriter writer, Map map) {
      writer.append("com.google.common.collect.ImmutableMap.");
      if (map.isEmpty()) {
        return writer.append("of()");
      }
      if (map.size() < 5) {
        return map(writer, map, "of(", ",\n", ")");
      }
      return map(writer, map, "builder().put(", ")\n.put(", ").build()");
    }
  
    private static ExpressionWriter map(ExpressionWriter writer, Map map,
        String begin, String entrySep, String end) {
      writer.append(begin);
      boolean comma = false;
      for (Object o : map.entrySet()) {
        Map.Entry entry = (Map.Entry) o;
        if (comma) {
          writer.append(entrySep).indent();
        }
        write(writer, entry.getKey(), null);
        writer.append(", ");
        write(writer, entry.getValue(), null);
        comma = true;
      }
      return writer.append(end);
    }
  
    private static ExpressionWriter writeSet(ExpressionWriter writer, Set set) {
      writer.append("com.google.common.collect.ImmutableSet.");
      if (set.isEmpty()) {
        return writer.append("of()");
      }
      if (set.size() < 5) {
        return set(writer, set, "of(", ",", ")");
      }
      return set(writer, set, "builder().add(", ")\n.add(", ").build()");
    }
  
    private static ExpressionWriter set(ExpressionWriter writer, Set set,
                                        String begin, String entrySep, String end) {
      writer.append(begin);
      boolean comma = false;
      for (Object o : set.toArray()) {
        if (comma) {
          writer.append(entrySep).indent();
        }
        write(writer, o, null);
        comma = true;
      }
      return writer.append(end);
    }
  
    private static @Nullable Constructor matchingConstructor(Object value, Field[] fields) {
      for (Constructor<?> constructor : value.getClass().getConstructors()) {
        if (argsMatchFields(fields, constructor.getParameterTypes())) {
          return constructor;
        }
      }
      return null;
    }
  
    private static boolean argsMatchFields(Field[] fields,
        Class<?>[] parameterTypes) {
      if (parameterTypes.length != fields.length) {
        return false;
      }
      for (int i = 0; i < fields.length; i++) {
        if (fields[i].getType() != parameterTypes[i]) {
          return false;
        }
      }
      return true;
    }
  
    private static void escapeString(StringBuilder buf, String s) {
      buf.append('"');
      int n = s.length();
      char lastChar = 0;
      for (int i = 0; i < n; ++i) {
        char c = s.charAt(i);
        switch (c) {
        case '\\':
          buf.append("\\\\");
          break;
        case '"':
          buf.append("\\\"");
          break;
        case '\n':
          buf.append("\\n");
          break;
        case '\r':
          if (lastChar != '\n') {
            buf.append("\\r");
          }
          break;
        default:
          buf.append(c);
          break;
        }
        lastChar = c;
      }
      buf.append('"');
    }
  
    private static @Nullable Object getFieldValue(Object source, Field field) {
      if (isRecord(source.getClass())) {
        return getValueFromGetterMethod(source, field);
      }
      return getValueFromField(source, field);
    }
  
    private static @Nullable Object getValueFromGetterMethod(Object source, Field field) {
      try {
        return findPublicGetter(field, source.getClass().getMethods()).invoke(source);
      } catch (IllegalAccessException | InvocationTargetException e) {
        throw new IllegalArgumentException("Could not invoke getter method for field: "
            + field.getName(), e);
      }
    }
  
    private static @Nullable Object getValueFromField(Object source, Field field) {
      try {
        return field.get(source);
      } catch (IllegalAccessException e) {
        throw new IllegalArgumentException("Could not get field value for field: "
            + field.getName(), e);
      }
    }
  
    private static Field[] getClassFields(Class<?> clazz) {
      return isRecord(clazz) ? clazz.getDeclaredFields() : clazz.getFields();
    }
  
    private static boolean isRecord(Class<?> clazz) {
      return Compatible.INSTANCE.isRecord(clazz);
    }
  
    private static Method findPublicGetter(Field field, Method[] methods) {
      return Arrays.stream(methods)
          .filter(method -> isFieldGetter(field, method))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("Could not get field value"));
    }
  
    private static boolean isFieldGetter(Field field, Method method) {
      return method.getReturnType().equals(field.getType())
          && Modifier.isPublic(method.getModifiers())
          && method.getParameterCount() == 0
          && nameMatchesGetter(field, method);
    }
  
    private static boolean nameMatchesGetter(Field field, Method method) {
      return method.getName().equals(field.getName());
    }
  
    @Override public boolean equals(@Nullable Object o) {
      // REVIEW: Should constants with the same value and different type
      // (e.g. 3L and 3) be considered equal.
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
      if (!super.equals(o)) {
        return false;
      }
  
      ConstantExpression that = (ConstantExpression) o;
  
      if (value != null ? !value.equals(that.value) : that.value != null) {
        return false;
      }
  
      return true;
    }
  
    @Override public int hashCode() {
      return Objects.hash(nodeType, type, value);
    }
  }
  