blob: e7ae56fd12258ee6544c8cf0cce6a7410a80185a [file] [log] [blame]
/*
// Licensed to Julian Hyde under one or more contributor license
// agreements. See the NOTICE file distributed with this work for
// additional information regarding copyright ownership.
//
// Julian Hyde 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 net.hydromatic.linq4j.expressions;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
/**
* 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}).</p>
*/
public enum Primitive {
BOOLEAN(Boolean.TYPE, Boolean.class, 1, false, false, true),
BYTE(Byte.TYPE, Byte.class, 2, (byte) 0, Byte.MIN_VALUE, Byte.MAX_VALUE),
CHAR(Character.TYPE, Character.class, 2, (char) 0, Character.MIN_VALUE,
Character.MAX_VALUE),
SHORT(Short.TYPE, Short.class, 2, (short) 0, Short.MIN_VALUE,
Short.MAX_VALUE),
INT(Integer.TYPE, Integer.class, 2, 0, Integer.MIN_VALUE, Integer.MAX_VALUE),
LONG(Long.TYPE, Long.class, 2, 0L, Long.MIN_VALUE, Long.MAX_VALUE),
FLOAT(Float.TYPE, Float.class, 3, 0F, Float.MIN_VALUE, Float.MAX_VALUE),
DOUBLE(Double.TYPE, Double.class, 3, 0D, Double.MIN_VALUE, Double.MAX_VALUE),
VOID(Void.TYPE, Void.class, 4, null, null, null),
OTHER(null, null, 5, null, null, null);
public final Class primitiveClass;
public final Class boxClass;
public final String primitiveName; // e.g. "int"
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. */
public final Object defaultValue;
/** The minimum value of this primitive class. */
public final Object min;
/** The maximum value of this primitive class. */
public final Object max;
private static final Map<Class, Primitive> PRIMITIVE_MAP =
new HashMap<Class, Primitive>();
private static final Map<Class, Primitive> BOX_MAP =
new HashMap<Class, Primitive>();
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(Class primitiveClass, Class boxClass, int family,
Object defaultValue, Object min, Object max) {
this.primitiveClass = primitiveClass;
this.family = family;
this.primitiveName =
primitiveClass != null ? primitiveClass.getSimpleName() : null;
this.boxClass = boxClass;
this.defaultValue = defaultValue;
this.min = min;
this.max = max;
}
/**
* 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 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 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 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 : 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 : 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 : 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 : primitive.primitiveClass;
}
/**
* Adapts a primitive array into a {@link List}. For example,
* {@code asList(new double[2])} returns a {@code List&lt;Double&gt;}.
*/
public static List<?> asList(final Object array) {
// REVIEW: A per-type list might be more efficient. (Or might not.)
return new AbstractList() {
public Object get(int index) {
return Array.get(array, index);
}
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);
}
/**
* 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 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.
*/
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 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(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();
Object getObject();
}
/** What kind of type? */
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
}
}
// End Primitive.java