Primitive.java

/*
 * 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
  }
}