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.apiguardian.api.API;
  import org.checkerframework.checker.nullness.qual.Nullable;
  import org.joou.UByte;
  import org.joou.UInteger;
  import org.joou.ULong;
  import org.joou.UShort;
  
  import java.lang.reflect.Array;
  import java.lang.reflect.Field;
  import java.lang.reflect.Type;
  import java.math.BigDecimal;
  import java.math.RoundingMode;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.util.AbstractList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * Enumeration of Java's primitive types.
   *
   * <p>There are fields for the native class (e.g. <code>int</code>, also
   * known as {@link Integer#TYPE}) and the boxing class
   * (e.g. {@link Integer}).
   */
  public enum Primitive {
    BOOLEAN(Boolean.TYPE, Boolean.class, 1, false, false, null, null, true, -1),
    BYTE(Byte.TYPE, Byte.class, 2, (byte) 0, Byte.MIN_VALUE, null, null,
        Byte.MAX_VALUE, Byte.SIZE),
    CHAR(Character.TYPE, Character.class, 2, (char) 0, Character.MIN_VALUE,
        null, null, Character.MAX_VALUE, Character.SIZE),
    SHORT(Short.TYPE, Short.class, 2, (short) 0, Short.MIN_VALUE, null, null,
        Short.MAX_VALUE, Short.SIZE),
    INT(Integer.TYPE, Integer.class, 2, 0, Integer.MIN_VALUE, null, null,
        Integer.MAX_VALUE, Integer.SIZE),
    LONG(Long.TYPE, Long.class, 2, 0L, Long.MIN_VALUE, null, null,
        Long.MAX_VALUE, Long.SIZE),
    FLOAT(Float.TYPE, Float.class, 2, 0F, -Float.MAX_VALUE, -Float.MIN_VALUE,
        Float.MIN_VALUE, Float.MAX_VALUE, Float.SIZE),
    DOUBLE(Double.TYPE, Double.class, 2, 0D, -Double.MAX_VALUE, -Double.MIN_VALUE,
        Double.MIN_VALUE, Double.MAX_VALUE, Double.SIZE),
    VOID(Void.TYPE, Void.class, 3, null, null, null, null, null, -1),
    OTHER(null, null, 4, null, null, null, null, null, -1);
  
    public final @Nullable Class primitiveClass;
    public final @Nullable Class boxClass;
    public final @Nullable String primitiveName; // e.g. "int"
    public final @Nullable String boxName;
    private final int family;
  
    /** The default value of this primitive class. This is the value
     * taken by uninitialized fields, for instance; 0 for {@code int}, false for
     * {@code boolean}, etc. */
    @SuppressWarnings("ImmutableEnumChecker")
    public final @Nullable Object defaultValue;
  
    /** The minimum value of this primitive class. */
    @SuppressWarnings("ImmutableEnumChecker")
    public final @Nullable Object min;
  
    /** The largest value that is less than zero. Null if not applicable for this
     * type. */
    @SuppressWarnings("ImmutableEnumChecker")
    public final @Nullable Object maxNegative;
  
    /** The smallest value that is greater than zero. Null if not applicable for
     * this type. */
    @SuppressWarnings("ImmutableEnumChecker")
    public final @Nullable Object minPositive;
  
    /** The maximum value of this primitive class. */
    @SuppressWarnings("ImmutableEnumChecker")
    public final @Nullable Object max;
  
    /** The size of a value of this type, in bits. Null if not applicable for this
     * type. */
    public final int size;
  
    private static final Map<Class, Primitive> PRIMITIVE_MAP = new HashMap<>();
    private static final Map<Class, Primitive> BOX_MAP = new HashMap<>();
  
    static {
      Primitive[] values = Primitive.values();
      for (Primitive value : values) {
        if (value.primitiveClass != null) {
          PRIMITIVE_MAP.put(value.primitiveClass, value);
        }
        if (value.boxClass != null) {
          BOX_MAP.put(value.boxClass, value);
        }
      }
    }
  
    Primitive(@Nullable Class primitiveClass, @Nullable Class boxClass, int family,
        @Nullable Object defaultValue, @Nullable Object min, @Nullable Object maxNegative,
        @Nullable Object minPositive, @Nullable Object max, int size) {
      this.primitiveClass = primitiveClass;
      this.family = family;
      this.primitiveName =
          primitiveClass != null ? primitiveClass.getSimpleName() : null;
      this.boxClass = boxClass;
      this.boxName = boxClass != null ? boxClass.getSimpleName() : null;
      this.defaultValue = defaultValue;
      this.min = min;
      this.maxNegative = maxNegative;
      this.minPositive = minPositive;
      this.max = max;
      this.size = size;
    }
  
    /**
     * Returns the Primitive object for a given primitive class.
     *
     * <p>For example, <code>of(long.class)</code> returns {@link #LONG}.
     * Returns {@code null} when applied to a boxing or other class; for example
     * <code>of(Long.class)</code> and <code>of(String.class)</code> return
     * {@code null}.
     */
    public static @Nullable Primitive of(Type type) {
      //noinspection SuspiciousMethodCalls
      return PRIMITIVE_MAP.get(type);
    }
  
    /**
     * Returns the Primitive object for a given boxing class.
     *
     * <p>For example, <code>ofBox(java.util.Long.class)</code>
     * returns {@link #LONG}.
     */
    public static @Nullable Primitive ofBox(Type type) {
      //noinspection SuspiciousMethodCalls
      return BOX_MAP.get(type);
    }
  
    /**
     * Returns the Primitive object for a given primitive or boxing class.
     *
     * <p>For example, <code>ofBoxOr(Long.class)</code> and
     * <code>ofBoxOr(long.class)</code> both return {@link #LONG}.
     */
    public static @Nullable Primitive ofBoxOr(Type type) {
      Primitive primitive = of(type);
      if (primitive == null) {
        primitive = ofBox(type);
      }
      return primitive;
    }
  
    /**
     * Returns whether a given type is primitive.
     */
    public static boolean is(Type type) {
      //noinspection SuspiciousMethodCalls
      return PRIMITIVE_MAP.containsKey(type);
    }
  
    /**
     * Returns whether a given type is a box type (e.g. {@link Integer}).
     */
    public static boolean isBox(Type type) {
      //noinspection SuspiciousMethodCalls
      return BOX_MAP.containsKey(type);
    }
  
    /** Returns whether this type is a primitive, box or other type. Useful for
     * switch statements. */
    public static Flavor flavor(Type type) {
      if (is(type)) {
        return Flavor.PRIMITIVE;
      } else if (isBox(type)) {
        return Flavor.BOX;
      } else {
        return Flavor.OBJECT;
      }
    }
  
    /** Returns whether this Primitive is a numeric type. */
    public boolean isNumeric() {
      // Per Java: Boolean and Character do not extend Number
      switch (this) {
      case BYTE:
      case SHORT:
      case INT:
      case LONG:
      case FLOAT:
      case DOUBLE:
        return true;
      default:
        return false;
      }
    }
  
    /** Returns whether this Primitive is a fixed-point numeric type. */
    public boolean isFixedNumeric() {
      switch (this) {
      case BYTE:
      case SHORT:
      case INT:
      case LONG:
        return true;
      default:
        return false;
      }
    }
  
    /**
     * Converts a primitive type to a boxed type; returns other types
     * unchanged.
     */
    public static Type box(Type type) {
      Primitive primitive = of(type);
      return primitive == null ? type : requireNonNull(primitive.boxClass);
    }
  
    /**
     * Converts a primitive class to a boxed class; returns other classes
     * unchanged.
     */
    public static Class box(Class type) {
      Primitive primitive = of(type);
      return primitive == null ? type : requireNonNull(primitive.boxClass);
    }
  
    /**
     * Converts a boxed type to a primitive type; returns other types
     * unchanged.
     */
    public static Type unbox(Type type) {
      Primitive primitive = ofBox(type);
      return primitive == null ? type : requireNonNull(primitive.primitiveClass);
    }
  
    /**
     * Converts a boxed class to a primitive class; returns other types
     * unchanged.
     */
    public static Class unbox(Class type) {
      Primitive primitive = ofBox(type);
      return primitive == null ? type : requireNonNull(primitive.primitiveClass);
    }
  
  
    @API(since = "1.27", status = API.Status.EXPERIMENTAL)
    public Class<?> getPrimitiveClass() {
      return requireNonNull(primitiveClass, () -> "no primitiveClass for " + this);
    }
  
    @API(since = "1.27", status = API.Status.EXPERIMENTAL)
    public Class<?> getBoxClass() {
      return requireNonNull(boxClass, () -> "no boxClass for " + this);
    }
  
    @API(since = "1.27", status = API.Status.EXPERIMENTAL)
    public String getPrimitiveName() {
      return requireNonNull(primitiveName, () -> "no primitiveName for " + this);
    }
  
    @API(since = "1.27", status = API.Status.EXPERIMENTAL)
    public String getBoxName() {
      return requireNonNull(boxName, () -> "no boxName for " + this);
    }
  
    /**
     * Adapts a primitive array into a {@link List}. For example,
     * {@code asList(new double[2])} returns a {@code List<Double>}.
     */
    public static List<?> asList(final Object array) {
      // REVIEW: A per-type list might be more efficient. (Or might not.)
      return new AbstractList() {
        @Override public Object get(int index) {
          return Array.get(array, index);
        }
  
        @Override public int size() {
          return Array.getLength(array);
        }
      };
    }
  
    /**
     * Adapts an array of {@code boolean} into a {@link List} of
     * {@link Boolean}.
     */
    public static List<Boolean> asList(boolean[] elements) {
      //noinspection unchecked
      return (List<Boolean>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code byte} into a {@link List} of
     * {@link Byte}.
     */
    public static List<Byte> asList(byte[] elements) {
      //noinspection unchecked
      return (List<Byte>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code char} into a {@link List} of
     * {@link Character}.
     */
    public static List<Character> asList(char[] elements) {
      //noinspection unchecked
      return (List<Character>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code short} into a {@link List} of
     * {@link Short}.
     */
    public static List<Short> asList(short[] elements) {
      //noinspection unchecked
      return (List<Short>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code int} into a {@link List} of
     * {@link Integer}.
     */
    public static List<Integer> asList(int[] elements) {
      //noinspection unchecked
      return (List<Integer>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code long} into a {@link List} of
     * {@link Long}.
     */
    public static List<Long> asList(long[] elements) {
      //noinspection unchecked
      return (List<Long>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code float} into a {@link List} of
     * {@link Float}.
     */
    public static List<Float> asList(float[] elements) {
      //noinspection unchecked
      return (List<Float>) asList((Object) elements);
    }
  
    /**
     * Adapts an array of {@code double} into a {@link List} of
     * {@link Double}.
     */
    public static List<Double> asList(double[] elements) {
      //noinspection unchecked
      return (List<Double>) asList((Object) elements);
    }
  
    /**
     * Check if a value after rounding falls within a specified range.
     *
     * @param value  Value to compare.
     * @param min    Minimum value allowed.
     * @param max    Maximum value allowed.
     */
    static void checkRoundedRange(Number value, double min, double max) {
      double dbl = value.doubleValue();
      // The equivalent of DOWN rounding for BigDecimal
      dbl = dbl > 0 ? Math.floor(dbl) : Math.ceil(dbl);
      if (dbl < min || dbl > max) {
        throw new ArithmeticException("Value " + value + " out of range");
      }
    }
  
    /** Called from BuiltInMethod.INTEGER_CAST */
    public static @Nullable Object integerCast(Primitive primitive, final Object value) {
      return requireNonNull(primitive, "primitive").numberValue((Number) value, RoundingMode.DOWN);
    }
  
    /** Called from BuiltInMethod.INTEGER_CAST_ROUNDING_MODE */
    public static @Nullable Object integerCast(Primitive primitive, final Object value,
        RoundingMode roundingMode) {
      return requireNonNull(primitive, "primitive").numberValue((Number) value, roundingMode);
    }
  
    static BigDecimal checkOverflow(BigDecimal value, int precision, int scale,
        RoundingMode roundingMode) {
      BigDecimal result = value.setScale(scale, roundingMode);
      result = result.stripTrailingZeros();
      if (result.scale() < scale) {
        // stripTrailingZeros also removes zeros if there is no
        // decimal point, converting 1000 to 1e+3, using a negative scale.
        // Here we undo this change.
        result = result.setScale(scale, roundingMode);
      }
      int actualPrecision = result.precision();
      if (actualPrecision > precision) {
        throw new ArithmeticException("Value " + value
            + " cannot be represented as a DECIMAL(" + precision + ", " + scale + ")");
      }
      if (scale < 0) {
        // The result maybe scientific notation string,e.g. 1.234E+6,
        // we need to convert it to 1234000
        return new BigDecimal(result.toPlainString());
      }
      return result;
    }
  
    /** Called from BuiltInMethod.CHAR_DECIMAL_CAST */
    public static @Nullable Object charToDecimalCast(
        @Nullable String value, int precision, int scale) {
      return charToDecimalCast(value, precision, scale, RoundingMode.DOWN);
    }
  
    /** Called from BuiltInMethod.CHAR_DECIMAL_CAST_ROUNDING_MODE */
    public static @Nullable Object charToDecimalCast(
        @Nullable String value, int precision, int scale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      BigDecimal result = new BigDecimal(value.trim());
      return checkOverflow(result, precision, scale, roundingMode);
    }
  
    /**
     * Convert a short time interval to a decimal value.
     * Called from BuiltInMethod.SHORT_INTERVAL_DECIMAL_CAST.
     * @param unitScale Scale describing source interval type */
    public static @Nullable Object shortIntervalToDecimalCast(
        @Nullable Long value, int precision, int scale,
        BigDecimal unitScale) {
      return shortIntervalToDecimalCast(value, precision, scale, unitScale, RoundingMode.DOWN);
    }
  
    /**
     * Convert a short time interval to a decimal value.
     * Called from BuiltInMethod.SHORT_INTERVAL_DECIMAL_CAST_ROUNDING_MODE.
     * @param unitScale Scale describing source interval type */
    public static @Nullable Object shortIntervalToDecimalCast(
        @Nullable Long value, int precision, int scale,
        BigDecimal unitScale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      // Divide with the scale expected of the result
      BigDecimal result = new BigDecimal(value).divide(unitScale, scale, roundingMode);
      return checkOverflow(result, precision, scale, roundingMode);
    }
  
    /**
     * Convert a long time interval to a decimal value.
     * Called from BuiltInMethod.LONG_INTERVAL_DECIMAL_CAST.
     * @param unitScale Scale describing source interval type */
    public static @Nullable Object longIntervalToDecimalCast(
        @Nullable Integer value, int precision, int scale,
        BigDecimal unitScale) {
      return longIntervalToDecimalCast(value, precision, scale, unitScale, RoundingMode.DOWN);
    }
  
    /**
     * Convert a long time interval to a decimal value.
     * Called from BuiltInMethod.LONG_INTERVAL_DECIMAL_CAST_ROUNDING_MODE.
     * @param unitScale Scale describing source interval type */
    public static @Nullable Object longIntervalToDecimalCast(
        @Nullable Integer value, int precision, int scale,
        BigDecimal unitScale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      // Divide with the scale expected of the result
      BigDecimal result = new BigDecimal(value).divide(unitScale, scale, roundingMode);
      return checkOverflow(result, precision, scale, roundingMode);
    }
  
    /** Called from BuiltInMethod.DECIMAL_DECIMAL_CAST */
    public static @Nullable Object decimalDecimalCast(
        @Nullable BigDecimal value, int precision, int scale) {
      return decimalDecimalCast(value, precision, scale, RoundingMode.DOWN);
    }
  
    /** Called from BuiltInMethod.DECIMAL_DECIMAL_CAST_ROUNDING_MODE */
    public static @Nullable Object decimalDecimalCast(
        @Nullable BigDecimal value, int precision, int scale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      return checkOverflow(value, precision, scale, roundingMode);
    }
  
    /** Called from BuiltInMethod.INTEGER_DECIMAL_CAST */
    public static @Nullable Object integerDecimalCast(
        @Nullable Number value, int precision, int scale) {
      return integerDecimalCast(value, precision, scale, RoundingMode.DOWN);
    }
  
    /** Called from BuiltInMethod.INTEGER_DECIMAL_CAST_ROUNDING_MODE */
    public static @Nullable Object integerDecimalCast(
        @Nullable Number value, int precision, int scale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      final BigDecimal decimal = new BigDecimal(value.longValue());
      return checkOverflow(decimal, precision, scale, roundingMode);
    }
  
    /** Called from BuiltInMethod.FP_DECIMAL_CAST */
    public static @Nullable Object fpDecimalCast(
        @Nullable Number value, int precision, int scale) {
      return fpDecimalCast(value, precision, scale, RoundingMode.DOWN);
    }
  
    /** Called from BuiltInMethod.FP_DECIMAL_CAST_ROUNDING_MODE */
    public static @Nullable Object fpDecimalCast(
        @Nullable Number value, int precision, int scale, RoundingMode roundingMode) {
      if (value == null) {
        return null;
      }
      final BigDecimal decimal = BigDecimal.valueOf(value.doubleValue());
      return checkOverflow(decimal, precision, scale, roundingMode);
    }
  
    public @Nullable Object numberValueRoundDown(Number value) {
      return numberValue(value, RoundingMode.DOWN);
    }
  
    /**
     * Converts a number into a value of the type specified by this primitive
     * using the SQL CAST rules.  If the value conversion causes loss of significant digits,
     * an exception is thrown.
     *
     * @param value  Value to convert.
     * @param roundingMode Rounding behavior.
     * @return       The converted value, or null if the type of the result is not a number.
     */
    public @Nullable Object numberValue(Number value, RoundingMode roundingMode) {
      switch (this) {
      case BYTE:
        checkRoundedRange(value, Byte.MIN_VALUE, Byte.MAX_VALUE);
        if (value instanceof BigDecimal) {
          return ((BigDecimal) value).setScale(0, roundingMode).byteValue();
        }
        return value.byteValue();
      case CHAR:
        // No overflow checks for char values.
        // For example, Postgres has this behavior.
        return (char) value.intValue();
      case SHORT:
        checkRoundedRange(value, Short.MIN_VALUE, Short.MAX_VALUE);
        if (value instanceof BigDecimal) {
          return ((BigDecimal) value).setScale(0, roundingMode).shortValue();
        }
        return value.shortValue();
      case INT:
        checkRoundedRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
        if (value instanceof BigDecimal) {
          return ((BigDecimal) value).setScale(0, roundingMode).intValue();
        }
        return value.intValue();
      case LONG:
        if (value instanceof Byte
            || value instanceof Short
            || value instanceof Integer
            || value instanceof Long
            || value instanceof UByte
            || value instanceof UShort
            || value instanceof UInteger) {
          return value.longValue();
        }
        if (value instanceof ULong) {
          if (value.longValue() < 0) {
            throw new ArithmeticException("Value " + value + " out of range");
          }
          return value.longValue();
        }
        if (value instanceof Float
            || value instanceof Double) {
          // The value Long.MAX_VALUE cannot be represented exactly as a double,
          // so we cannot use checkRoundedRange.
          BigDecimal decimal = BigDecimal.valueOf(value.doubleValue())
              // Round to an integer
              .setScale(0, roundingMode);
          // longValueExact will throw ArithmeticException if out of range
          return decimal.longValueExact();
        }
        if (value instanceof BigDecimal) {
          BigDecimal decimal = ((BigDecimal) value)
              // Round to an integer
              .setScale(0, roundingMode);
          // longValueExact will throw ArithmeticException if out of range
          return decimal.longValueExact();
        }
        throw new AssertionError("Unexpected Number type "
            + value.getClass().getSimpleName());
      case FLOAT:
        // out of range values will be represented as infinities
        return value.floatValue();
      case DOUBLE:
        // out of range values will be represented as infinities
        return value.doubleValue();
      default:
        return null;
      }
    }
  
    /**
     * Converts a collection of boxed primitives into an array of primitives.
     *
     * @param collection Collection of boxed primitives
     *
     * @return array of primitives
     * @throws ClassCastException   if any element is not of the box type
     * @throws NullPointerException if any element is null
     */
    @SuppressWarnings("unchecked")
    public Object toArray(Collection collection) {
      int i = 0;
      switch (this) {
      case DOUBLE:
        double[] doubles = new double[collection.size()];
        for (double v : (Collection<Double>) collection) {
          doubles[i++] = v;
        }
        return doubles;
      case FLOAT:
        float[] floats = new float[collection.size()];
        for (float v : (Collection<Float>) collection) {
          floats[i++] = v;
        }
        return floats;
      case INT:
        int[] ints = new int[collection.size()];
        for (int v : (Collection<Integer>) collection) {
          ints[i++] = v;
        }
        return ints;
      case LONG:
        long[] longs = new long[collection.size()];
        for (long v : (Collection<Long>) collection) {
          longs[i++] = v;
        }
        return longs;
      case SHORT:
        short[] shorts = new short[collection.size()];
        for (short v : (Collection<Short>) collection) {
          shorts[i++] = v;
        }
        return shorts;
      case BOOLEAN:
        boolean[] booleans = new boolean[collection.size()];
        for (boolean v : (Collection<Boolean>) collection) {
          booleans[i++] = v;
        }
        return booleans;
      case BYTE:
        byte[] bytes = new byte[collection.size()];
        for (byte v : (Collection<Byte>) collection) {
          bytes[i++] = v;
        }
        return bytes;
      case CHAR:
        char[] chars = new char[collection.size()];
        for (char v : (Collection<Character>) collection) {
          chars[i++] = v;
        }
        return chars;
      default:
        throw new RuntimeException("unexpected: " + this);
      }
    }
  
    /**
     * Converts a collection of {@link Number} to a primitive array.
     */
    public Object toArray2(Collection<Number> collection) {
      int i = 0;
      switch (this) {
      case DOUBLE:
        double[] doubles = new double[collection.size()];
        for (Number number : collection) {
          doubles[i++] = number.doubleValue();
        }
        return doubles;
      case FLOAT:
        float[] floats = new float[collection.size()];
        for (Number number : collection) {
          floats[i++] = number.floatValue();
        }
        return floats;
      case INT:
        int[] ints = new int[collection.size()];
        for (Number number : collection) {
          ints[i++] = number.intValue();
        }
        return ints;
      case LONG:
        long[] longs = new long[collection.size()];
        for (Number number : collection) {
          longs[i++] = number.longValue();
        }
        return longs;
      case SHORT:
        short[] shorts = new short[collection.size()];
        for (Number number : collection) {
          shorts[i++] = number.shortValue();
        }
        return shorts;
      case BOOLEAN:
        boolean[] booleans = new boolean[collection.size()];
        for (Number number : collection) {
          booleans[i++] = number.byteValue() != 0;
        }
        return booleans;
      case BYTE:
        byte[] bytes = new byte[collection.size()];
        for (Number number : collection) {
          bytes[i++] = number.byteValue();
        }
        return bytes;
      case CHAR:
        char[] chars = new char[collection.size()];
        for (Number number : collection) {
          chars[i++] = (char) number.shortValue();
        }
        return chars;
      default:
        throw new RuntimeException("unexpected: " + this);
      }
    }
  
    /** Permutes an array. */
    public Object permute(Object array, int[] sources) {
      int i;
      switch (this) {
      case DOUBLE:
        double[] doubles0 = (double[]) array;
        double[] doubles = new double[doubles0.length];
        for (i = 0; i < doubles.length; i++) {
          doubles[i] = doubles0[sources[i]];
        }
        return doubles;
      case FLOAT:
        float[] floats0 = (float[]) array;
        float[] floats = new float[floats0.length];
        for (i = 0; i < floats.length; i++) {
          floats[i] = floats0[sources[i]];
        }
        return floats;
      case INT:
        int[] ints0 = (int[]) array;
        int[] ints = new int[ints0.length];
        for (i = 0; i < ints.length; i++) {
          ints[i] = ints0[sources[i]];
        }
        return ints;
      case LONG:
        long[] longs0 = (long[]) array;
        long[] longs = new long[longs0.length];
        for (i = 0; i < longs.length; i++) {
          longs[i] = longs0[sources[i]];
        }
        return longs;
      case SHORT:
        short[] shorts0 = (short[]) array;
        short[] shorts = new short[shorts0.length];
        for (i = 0; i < shorts.length; i++) {
          shorts[i] = shorts0[sources[i]];
        }
        return shorts;
      case BOOLEAN:
        boolean[] booleans0 = (boolean[]) array;
        boolean[] booleans = new boolean[booleans0.length];
        for (i = 0; i < booleans.length; i++) {
          booleans[i] = booleans0[sources[i]];
        }
        return booleans;
      case BYTE:
        byte[] bytes0 = (byte[]) array;
        byte[] bytes = new byte[bytes0.length];
        for (i = 0; i < bytes.length; i++) {
          bytes[i] = bytes0[sources[i]];
        }
        return bytes;
      case CHAR:
        char[] chars0 = (char[]) array;
        char[] chars = new char[chars0.length];
        for (i = 0; i < chars.length; i++) {
          chars[i] = chars0[sources[i]];
        }
        return chars;
      default:
        throw new RuntimeException("unexpected: " + this);
      }
    }
  
    /**
     * Converts an array to a string.
     *
     * @param array Array of this primitive type
     *
     * @return String representation of array
     */
    public String arrayToString(Object array) {
      switch (this) {
      case BOOLEAN:
        return Arrays.toString((boolean[]) array);
      case BYTE:
        return Arrays.toString((byte[]) array);
      case CHAR:
        return Arrays.toString((char[]) array);
      case DOUBLE:
        return Arrays.toString((double[]) array);
      case FLOAT:
        return Arrays.toString((float[]) array);
      case INT:
        return Arrays.toString((int[]) array);
      case LONG:
        return Arrays.toString((long[]) array);
      case SHORT:
        return Arrays.toString((short[]) array);
      case OTHER:
      case VOID:
        return Arrays.toString((Object[]) array);
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    /**
     * Sorts an array of this primitive type.
     *
     * @param array Array of this primitive type
     */
    public void sortArray(Object array) {
      switch (this) {
      case BOOLEAN:
        // there is no Arrays.sort(boolean[])
        final boolean[] booleans = (boolean[]) array;
        sortBooleanArray(booleans, 0, booleans.length);
        return;
      case BYTE:
        Arrays.sort((byte[]) array);
        return;
      case CHAR:
        Arrays.sort((char[]) array);
        return;
      case DOUBLE:
        Arrays.sort((double[]) array);
        return;
      case FLOAT:
        Arrays.sort((float[]) array);
        return;
      case INT:
        Arrays.sort((int[]) array);
        return;
      case LONG:
        Arrays.sort((long[]) array);
        return;
      case SHORT:
        Arrays.sort((short[]) array);
        return;
      case OTHER:
      case VOID:
        Arrays.sort((Object[]) array);
        return;
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    /**
     * Sorts a specified range of an array of this primitive type.
     *
     * @param array Array of this primitive type
     * @param fromIndex the index of the first element, inclusive, to be sorted
     * @param toIndex the index of the last element, exclusive, to be sorted
     */
    public void sortArray(Object array, int fromIndex, int toIndex) {
      switch (this) {
      case BOOLEAN:
        // there is no Arrays.sort(boolean[], int, int)
        sortBooleanArray((boolean[]) array, fromIndex, toIndex);
        return;
      case BYTE:
        Arrays.sort((byte[]) array, fromIndex, toIndex);
        return;
      case CHAR:
        Arrays.sort((char[]) array, fromIndex, toIndex);
        return;
      case DOUBLE:
        Arrays.sort((double[]) array, fromIndex, toIndex);
        return;
      case FLOAT:
        Arrays.sort((float[]) array, fromIndex, toIndex);
        return;
      case INT:
        Arrays.sort((int[]) array, fromIndex, toIndex);
        return;
      case LONG:
        Arrays.sort((long[]) array, fromIndex, toIndex);
        return;
      case SHORT:
        Arrays.sort((short[]) array, fromIndex, toIndex);
        return;
      case OTHER:
      case VOID:
        Arrays.sort((Object[]) array, fromIndex, toIndex);
        return;
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    private static void sortBooleanArray(boolean[] booleans, int fromIndex,
        int toIndex) {
      // The sorted array will be like [false, false, ..., false, true, ... true].
      // Every time we see a "false", the transition point moves up one.
      int midIndex = fromIndex;
      for (int i = fromIndex; i < toIndex; i++) {
        if (!booleans[i]) {
          ++midIndex;
        }
      }
      Arrays.fill(booleans, fromIndex, midIndex, false);
      Arrays.fill(booleans, midIndex, toIndex, true);
    }
  
    /**
     * Sends a field value to a sink.
     */
    public void send(Field field, Object o, Sink sink)
        throws IllegalAccessException {
      switch (this) {
      case BOOLEAN:
        sink.set(field.getBoolean(o));
        break;
      case BYTE:
        sink.set(field.getByte(o));
        break;
      case CHAR:
        sink.set(field.getChar(o));
        break;
      case SHORT:
        sink.set(field.getShort(o));
        break;
      case INT:
        sink.set(field.getInt(o));
        break;
      case LONG:
        sink.set(field.getLong(o));
        break;
      case FLOAT:
        sink.set(field.getFloat(o));
        break;
      case DOUBLE:
        sink.set(field.getDouble(o));
        break;
      default:
        sink.set(field.get(o));
        break;
      }
    }
  
    /**
     * Gets an item from an array.
     */
    public @Nullable Object arrayItem(Object dataSet, int ordinal) {
      // Plain old Array.get doesn't cut it when you have an array of
      // Integer values but you want to read Short values. Array.getShort
      // does the right thing.
      switch (this) {
      case DOUBLE:
        return Array.getDouble(dataSet, ordinal);
      case FLOAT:
        return Array.getFloat(dataSet, ordinal);
      case BOOLEAN:
        return Array.getBoolean(dataSet, ordinal);
      case BYTE:
        return Array.getByte(dataSet, ordinal);
      case CHAR:
        return Array.getChar(dataSet, ordinal);
      case SHORT:
        return Array.getShort(dataSet, ordinal);
      case INT:
        return Array.getInt(dataSet, ordinal);
      case LONG:
        return Array.getLong(dataSet, ordinal);
      case OTHER:
        return Array.get(dataSet, ordinal);
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    /**
     * Reads value from a source into an array.
     */
    @SuppressWarnings("argument.type.incompatible")
    public void arrayItem(Source source, Object dataSet, int ordinal) {
      switch (this) {
      case DOUBLE:
        Array.setDouble(dataSet, ordinal, source.getDouble());
        return;
      case FLOAT:
        Array.setFloat(dataSet, ordinal, source.getFloat());
        return;
      case BOOLEAN:
        Array.setBoolean(dataSet, ordinal, source.getBoolean());
        return;
      case BYTE:
        Array.setByte(dataSet, ordinal, source.getByte());
        return;
      case CHAR:
        Array.setChar(dataSet, ordinal, source.getChar());
        return;
      case SHORT:
        Array.setShort(dataSet, ordinal, source.getShort());
        return;
      case INT:
        Array.setInt(dataSet, ordinal, source.getInt());
        return;
      case LONG:
        Array.setLong(dataSet, ordinal, source.getLong());
        return;
      case OTHER:
        Array.set(dataSet, ordinal, source.getObject());
        return;
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    /**
     * Sends to a sink an from an array.
     */
    public void arrayItem(Object dataSet, int ordinal, Sink sink) {
      switch (this) {
      case DOUBLE:
        sink.set(Array.getDouble(dataSet, ordinal));
        return;
      case FLOAT:
        sink.set(Array.getFloat(dataSet, ordinal));
        return;
      case BOOLEAN:
        sink.set(Array.getBoolean(dataSet, ordinal));
        return;
      case BYTE:
        sink.set(Array.getByte(dataSet, ordinal));
        return;
      case CHAR:
        sink.set(Array.getChar(dataSet, ordinal));
        return;
      case SHORT:
        sink.set(Array.getShort(dataSet, ordinal));
        return;
      case INT:
        sink.set(Array.getInt(dataSet, ordinal));
        return;
      case LONG:
        sink.set(Array.getLong(dataSet, ordinal));
        return;
      case OTHER:
        sink.set(Array.get(dataSet, ordinal));
        return;
      default:
        throw new AssertionError("unexpected " + this);
      }
    }
  
    /**
     * Gets a value from a given column in a JDBC result set.
     *
     * @param resultSet Result set
     * @param i Ordinal of column (1-based, per JDBC)
     */
    public @Nullable Object jdbcGet(ResultSet resultSet, int i) throws SQLException {
      switch (this) {
      case BOOLEAN:
        return resultSet.getBoolean(i);
      case BYTE:
        return resultSet.getByte(i);
      case CHAR:
        return (char) resultSet.getShort(i);
      case DOUBLE:
        return resultSet.getDouble(i);
      case FLOAT:
        return resultSet.getFloat(i);
      case INT:
        return resultSet.getInt(i);
      case LONG:
        return resultSet.getLong(i);
      case SHORT:
        return resultSet.getShort(i);
      default:
        return resultSet.getObject(i);
      }
    }
  
    /**
     * Sends to a sink a value from a given column in a JDBC result set.
     *
     * @param resultSet Result set
     * @param i Ordinal of column (1-based, per JDBC)
     * @param sink Sink
     */
    public void jdbc(ResultSet resultSet, int i, Sink sink) throws SQLException {
      switch (this) {
      case BOOLEAN:
        sink.set(resultSet.getBoolean(i));
        break;
      case BYTE:
        sink.set(resultSet.getByte(i));
        break;
      case CHAR:
        sink.set((char) resultSet.getShort(i));
        break;
      case DOUBLE:
        sink.set(resultSet.getDouble(i));
        break;
      case FLOAT:
        sink.set(resultSet.getFloat(i));
        break;
      case INT:
        sink.set(resultSet.getInt(i));
        break;
      case LONG:
        sink.set(resultSet.getLong(i));
        break;
      case SHORT:
        sink.set(resultSet.getShort(i));
        break;
      default:
        sink.set(resultSet.getObject(i));
        break;
      }
    }
  
    /**
     * Sends a value from a source to a sink.
     */
    public void send(Source source, Sink sink) {
      switch (this) {
      case BOOLEAN:
        sink.set(source.getBoolean());
        break;
      case BYTE:
        sink.set(source.getByte());
        break;
      case CHAR:
        sink.set(source.getChar());
        break;
      case DOUBLE:
        sink.set(source.getDouble());
        break;
      case FLOAT:
        sink.set(source.getFloat());
        break;
      case INT:
        sink.set(source.getInt());
        break;
      case LONG:
        sink.set(source.getLong());
        break;
      case SHORT:
        sink.set(source.getShort());
        break;
      default:
        sink.set(source.getObject());
        break;
      }
    }
  
    /**
     * Calls the appropriate {@link Integer#valueOf(String) valueOf(String)}
     * method.
     */
    public Object parse(String stringValue) {
      switch (this) {
      case BOOLEAN:
        return Boolean.valueOf(stringValue);
      case BYTE:
        return Byte.valueOf(stringValue);
      case CHAR:
        return Character.valueOf(stringValue.charAt(0));
      case DOUBLE:
        return Double.valueOf(stringValue);
      case FLOAT:
        return Float.valueOf(stringValue);
      case INT:
        return Integer.valueOf(stringValue);
      case LONG:
        return Long.valueOf(stringValue);
      case SHORT:
        return Short.valueOf(stringValue);
      default:
        throw new AssertionError(stringValue);
      }
    }
  
    public boolean assignableFrom(Primitive primitive) {
      return family == primitive.family
          && ordinal() >= primitive.ordinal()
          && !(this == SHORT && primitive == CHAR)
          && !(this == CHAR && primitive == BYTE);
    }
  
    /** Creates a number value of this primitive's box type. For example,
     * {@code SHORT.number(Integer(0))} will return {@code Short(0)}. */
    public Number number(Number value) {
      switch (this) {
      case BYTE:
        return Byte.valueOf(value.byteValue());
      case DOUBLE:
        return Double.valueOf(value.doubleValue());
      case FLOAT:
        return Float.valueOf(value.floatValue());
      case INT:
        return Integer.valueOf(value.intValue());
      case LONG:
        return Long.valueOf(value.longValue());
      case SHORT:
        return Short.valueOf(value.shortValue());
      default:
        throw new AssertionError(this + ": " + value);
      }
    }
  
    /**
     * A place to send a value.
     */
    public interface Sink {
      void set(boolean v);
  
      void set(byte v);
  
      void set(char v);
  
      void set(short v);
  
      void set(int v);
  
      void set(long v);
  
      void set(float v);
  
      void set(double v);
  
      void set(@Nullable Object v);
    }
  
    /**
     * A place from which to read a value.
     */
    public interface Source {
      boolean getBoolean();
  
      byte getByte();
  
      char getChar();
  
      short getShort();
  
      int getInt();
  
      long getLong();
  
      float getFloat();
  
      double getDouble();
  
      @Nullable Object getObject();
    }
  
    /** Whether a type is primitive (e.g. {@code int}),
     * a box type for a primitive (e.g. {@code java.lang.Integer}),
     * or something else. */
    public enum Flavor {
      /** A primitive type, e.g. {@code int}. */
      PRIMITIVE,
      /** A type that boxes a primitive, e.g. {@link Integer}. */
      BOX,
      /** Neither a primitive nor a boxing type. */
      OBJECT
    }
  }
  