Various fixes related to literals and type inference. Move some utility methods over from optiq.
diff --git a/src/main/java/net/hydromatic/linq4j/expressions/ConstantExpression.java b/src/main/java/net/hydromatic/linq4j/expressions/ConstantExpression.java
index 20a40c1..abf482d 100644
--- a/src/main/java/net/hydromatic/linq4j/expressions/ConstantExpression.java
+++ b/src/main/java/net/hydromatic/linq4j/expressions/ConstantExpression.java
@@ -92,14 +92,18 @@
final Primitive primitive = Primitive.of(type);
if (primitive != null) {
switch (primitive) {
+ case BYTE:
+ return writer.append("(byte)").append(value);
+ case CHAR:
+ return writer.append("(char)").append(value);
+ case SHORT:
+ return writer.append("(short)").append(value);
+ case LONG:
+ return writer.append(value).append("L");
case FLOAT:
return writer.append(value).append("F");
case DOUBLE:
return writer.append(value).append("D");
- case LONG:
- return writer.append(value).append("L");
- case SHORT:
- return writer.append("(short)").append(value);
default:
return writer.append(value);
}
diff --git a/src/main/java/net/hydromatic/linq4j/expressions/Expressions.java b/src/main/java/net/hydromatic/linq4j/expressions/Expressions.java
index 9fc0f28..b934271 100644
--- a/src/main/java/net/hydromatic/linq4j/expressions/Expressions.java
+++ b/src/main/java/net/hydromatic/linq4j/expressions/Expressions.java
@@ -1391,15 +1391,79 @@
type = Boolean.TYPE;
break;
default:
- // curiously, "short + short" has type "int". Who knew?
- type = left.getType() == short.class || left.getType() == Short.class
- ? int.class
- : left.getType();
+ type = larger(left.type, right.type);
break;
}
return new BinaryExpression(binaryType, type, left, right);
}
+ /** Returns an expression to box the value of a primitive expression.
+ * E.g. {@code box(e, Primitive.INT)} returns {@code Integer.valueOf(e)}. */
+ public static Expression box(Expression expression, Primitive primitive) {
+ return call(primitive.boxClass, "valueOf", expression);
+ }
+
+ /** Converts e.g. "anInteger" to "Integer.valueOf(anInteger)". */
+ public static Expression box(Expression expression) {
+ Primitive primitive = Primitive.of(expression.getType());
+ if (primitive == null) {
+ return expression;
+ }
+ return box(expression, primitive);
+ }
+
+ /** Returns an expression to unbox the value of a boxed-primitive expression.
+ * E.g. {@code unbox(e, Primitive.INT)} returns {@code e.intValue()}.
+ * It is assumed that e is of the right box type (or {@link Number})."Value */
+ public static Expression unbox(Expression expression, Primitive primitive) {
+ return call(expression, primitive.primitiveName + "Value");
+ }
+
+ /** Converts e.g. "anInteger" to "anInteger.intValue()". */
+ public static Expression unbox(Expression expression) {
+ Primitive primitive = Primitive.ofBox(expression.getType());
+ if (primitive == null) {
+ return expression;
+ }
+ return unbox(expression, primitive);
+ }
+
+ private Type largest(Type... types) {
+ Type max = types[0];
+ for (int i = 1; i < types.length; i++) {
+ max = larger(max, types[i]);
+ }
+ return max;
+ }
+
+ private static Type larger(Type type0, Type type1) {
+ // curiously, "short + short" has type "int".
+ // similarly, "byte + byte" has type "int".
+ // "byte / long" has type "long".
+ if (type0 == double.class
+ || type0 == Double.class
+ || type1 == double.class
+ || type1 == Double.class)
+ {
+ return double.class;
+ }
+ if (type0 == float.class
+ || type0 == Float.class
+ || type1 == float.class
+ || type1 == Float.class)
+ {
+ return float.class;
+ }
+ if (type0 == long.class
+ || type0 == Long.class
+ || type1 == long.class
+ || type1 == Long.class)
+ {
+ return long.class;
+ }
+ return int.class;
+ }
+
/**
* Creates a BinaryExpression, given the left operand, right
* operand and implementing method, by calling the appropriate
@@ -2801,6 +2865,54 @@
return new GotoExpression(GotoExpressionKind.Sequence, null, expression);
}
+ /** Combines a list of expressions using AND. Returns TRUE if the list is
+ * empty. */
+ public static Expression foldAnd(List<Expression> conditions) {
+ Expression e = null;
+ for (Expression condition : conditions) {
+ if (condition instanceof ConstantExpression) {
+ if ((Boolean) ((ConstantExpression) condition).value) {
+ continue;
+ } else {
+ return constant(false);
+ }
+ }
+ if (e == null) {
+ e = condition;
+ } else {
+ e = andAlso(e, condition);
+ }
+ }
+ if (e == null) {
+ return constant(true);
+ }
+ return e;
+ }
+
+ /** Combines a list of expressions using OR. Returns FALSE if the list is
+ * empty. */
+ public static Expression foldOr(List<Expression> conditions) {
+ Expression e = null;
+ for (Expression condition : conditions) {
+ if (condition instanceof ConstantExpression) {
+ if ((Boolean) ((ConstantExpression) condition).value) {
+ return constant(true);
+ } else {
+ continue;
+ }
+ }
+ if (e == null) {
+ e = condition;
+ } else {
+ e = orElse(e, condition);
+ }
+ }
+ if (e == null) {
+ return constant(false);
+ }
+ return e;
+ }
+
/**
* Creates an empty fluent list.
*/
diff --git a/src/main/java/net/hydromatic/linq4j/expressions/Primitive.java b/src/main/java/net/hydromatic/linq4j/expressions/Primitive.java
index 6df860e..3d4fa2a 100644
--- a/src/main/java/net/hydromatic/linq4j/expressions/Primitive.java
+++ b/src/main/java/net/hydromatic/linq4j/expressions/Primitive.java
@@ -32,25 +32,22 @@
* (e.g. {@link Integer}).</p>
*/
public enum Primitive {
- BOOLEAN(Boolean.TYPE, Boolean.class, 1, true, Boolean.FALSE, Boolean.TRUE),
- BYTE(Byte.TYPE, Byte.class, 2, true, Byte.MIN_VALUE, Byte.MAX_VALUE),
- CHAR(Character.TYPE, Character.class, 2, true, Character.MIN_VALUE,
+ BOOLEAN(Boolean.TYPE, Boolean.class, 1, Boolean.FALSE, Boolean.TRUE),
+ BYTE(Byte.TYPE, Byte.class, 2, Byte.MIN_VALUE, Byte.MAX_VALUE),
+ CHAR(Character.TYPE, Character.class, 2, Character.MIN_VALUE,
Character.MAX_VALUE),
- SHORT(Short.TYPE, Short.class, 2, true, Short.MIN_VALUE, Short.MAX_VALUE),
- INT(Integer.TYPE, Integer.class, 2, true, Integer.MIN_VALUE,
- Integer.MAX_VALUE),
- LONG(Long.TYPE, Long.class, 2, true, Long.MIN_VALUE, Long.MAX_VALUE),
- FLOAT(Float.TYPE, Float.class, 3, false, Float.MIN_VALUE, Float.MAX_VALUE),
- DOUBLE(Double.TYPE, Double.class, 3, false, Double.MIN_VALUE,
- Double.MAX_VALUE),
- VOID(Void.TYPE, Void.class, 4, false, null, null),
- OTHER(null, null, 5, false, null, null);
+ SHORT(Short.TYPE, Short.class, 2, Short.MIN_VALUE, Short.MAX_VALUE),
+ INT(Integer.TYPE, Integer.class, 2, Integer.MIN_VALUE, Integer.MAX_VALUE),
+ LONG(Long.TYPE, Long.class, 2, Long.MIN_VALUE, Long.MAX_VALUE),
+ FLOAT(Float.TYPE, Float.class, 3, Float.MIN_VALUE, Float.MAX_VALUE),
+ DOUBLE(Double.TYPE, Double.class, 3, Double.MIN_VALUE, Double.MAX_VALUE),
+ VOID(Void.TYPE, Void.class, 4, null, null),
+ OTHER(null, null, 5, null, null);
public final Class primitiveClass;
public final Class boxClass;
public final String primitiveName; // e.g. "int"
private final int family;
- public final boolean fixed;
public final Object min;
public final Object max;
@@ -71,14 +68,13 @@
}
}
- Primitive(Class primitiveClass, Class boxClass, int family, boolean fixed,
- Object min, Object max) {
+ Primitive(Class primitiveClass, Class boxClass, int family, Object min,
+ Object max) {
this.primitiveClass = primitiveClass;
this.family = family;
this.primitiveName =
primitiveClass != null ? primitiveClass.getSimpleName() : null;
this.boxClass = boxClass;
- this.fixed = fixed;
this.min = min;
this.max = max;
}
@@ -86,8 +82,10 @@
/**
* Returns the Primitive object for a given primitive class.
*
- * <p>For example, <code>of(Long.TYPE)</code> or <code>of(long.class)</code>
- * returns {@link #LONG}.
+ * <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
@@ -106,6 +104,20 @@
}
/**
+ * 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) {
@@ -133,6 +145,35 @@
}
}
+ /** 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.
diff --git a/src/main/java/net/hydromatic/linq4j/expressions/Types.java b/src/main/java/net/hydromatic/linq4j/expressions/Types.java
index 91e4e2a..ed01dbe 100644
--- a/src/main/java/net/hydromatic/linq4j/expressions/Types.java
+++ b/src/main/java/net/hydromatic/linq4j/expressions/Types.java
@@ -421,18 +421,16 @@
// Integer foo(BigDecimal o) {
// return o.intValue();
// }
- return Expressions.call(expression,
- Primitive.ofBox(returnType).primitiveName + "Value");
+ return Expressions.unbox(expression, Primitive.ofBox(returnType));
}
if (Primitive.is(returnType) && !Primitive.is(type)) {
// E.g.
// int foo(Object o) {
- // return (int) (Integer) o;
+ // return ((Integer) o).intValue();
// }
- return Expressions.convert_(
- Expressions.convert_(expression,
- Types.box(returnType)),
- returnType);
+ return Expressions.unbox(
+ Expressions.convert_(expression, Types.box(returnType)),
+ Primitive.of(returnType));
}
if (!Primitive.is(returnType) && Primitive.is(type)) {
// E.g.
diff --git a/src/main/java/net/hydromatic/linq4j/function/Functions.java b/src/main/java/net/hydromatic/linq4j/function/Functions.java
index d55157f..d996a68 100644
--- a/src/main/java/net/hydromatic/linq4j/function/Functions.java
+++ b/src/main/java/net/hydromatic/linq4j/function/Functions.java
@@ -67,6 +67,13 @@
private static final EqualityComparer<Object[]> ARRAY_COMPARER =
new ArrayEqualityComparer();
+ private static final Function1 CONSTANT_NULL_FUNCTION1 =
+ new Function1() {
+ public Object apply(Object s) {
+ return null;
+ }
+ };
+
@SuppressWarnings("unchecked")
private static <K, V> Map<K, V> map(K k, V v, Object... rest) {
final Map<K, V> map = new HashMap<K, V>();
@@ -85,6 +92,21 @@
return inverseMap;
}
+ /** Returns a 1-parameter function that always returns the same value. */
+ public static <T, R> Function1<T, R> constant(final R r) {
+ return new Function1<T, R>() {
+ public R apply(T s) {
+ return r;
+ }
+ };
+ }
+
+ /** Returns a 1-parameter function that always returns null. */
+ @SuppressWarnings("unchecked")
+ public static <T, R> Function1<T, R> constantNull() {
+ return CONSTANT_NULL_FUNCTION1;
+ }
+
/**
* A predicate with one parameter that always returns {@code true}.
*
diff --git a/src/test/java/net/hydromatic/linq4j/test/ExpressionTest.java b/src/test/java/net/hydromatic/linq4j/test/ExpressionTest.java
index 5e61a4e..9face23 100644
--- a/src/test/java/net/hydromatic/linq4j/test/ExpressionTest.java
+++ b/src/test/java/net/hydromatic/linq4j/test/ExpressionTest.java
@@ -135,6 +135,65 @@
assertEquals("lo w", s);
}
+ public void testFoldAnd() {
+ // empty list yields true
+ final List<Expression> list0 = Collections.emptyList();
+ assertEquals(
+ "true",
+ Expressions.toString(
+ Expressions.foldAnd(list0)));
+ assertEquals(
+ "false",
+ Expressions.toString(
+ Expressions.foldOr(list0)));
+
+ final List<Expression> list1 =
+ Arrays.asList(
+ Expressions.equal(Expressions.constant(1), Expressions.constant(2)),
+ Expressions.equal(Expressions.constant(3), Expressions.constant(4)),
+ Expressions.constant(true),
+ Expressions.equal(Expressions.constant(5),
+ Expressions.constant(6)));
+ // true is eliminated from AND
+ assertEquals(
+ "1 == 2 && 3 == 4 && 5 == 6",
+ Expressions.toString(
+ Expressions.foldAnd(list1)));
+ // a single true makes OR true
+ assertEquals(
+ "true",
+ Expressions.toString(
+ Expressions.foldOr(list1)));
+
+ final List<Expression> list2 =
+ Collections.<Expression>singletonList(
+ Expressions.constant(true));
+ assertEquals(
+ "true",
+ Expressions.toString(
+ Expressions.foldAnd(list2)));
+ assertEquals(
+ "true",
+ Expressions.toString(
+ Expressions.foldOr(list2)));
+
+ final List<Expression> list3 =
+ Arrays.asList(
+ Expressions.equal(Expressions.constant(1), Expressions.constant(2)),
+ Expressions.constant(false),
+ Expressions.equal(Expressions.constant(5),
+ Expressions.constant(6)));
+ // false causes whole list to be false
+ assertEquals(
+ "false",
+ Expressions.toString(
+ Expressions.foldAnd(list3)));
+ assertEquals(
+ "1 == 2 || 5 == 6",
+ Expressions.toString(
+ Expressions.foldOr(list3)));
+ }
+
public void testWrite() {
assertEquals(
"1 + 2.0F + 3L + Long.valueOf(4L)",
diff --git a/src/test/java/net/hydromatic/linq4j/test/Linq4jTest.java b/src/test/java/net/hydromatic/linq4j/test/Linq4jTest.java
index bc1b94e..2a634ae 100644
--- a/src/test/java/net/hydromatic/linq4j/test/Linq4jTest.java
+++ b/src/test/java/net/hydromatic/linq4j/test/Linq4jTest.java
@@ -1003,6 +1003,50 @@
.toString());
}
+ public void testList0() {
+ final List<Employee> employees = Arrays.asList(
+ new Employee(100, "Fred", 10),
+ new Employee(110, "Bill", 30),
+ new Employee(120, "Eric", 10),
+ new Employee(130, "Janet", 10));
+ final List<Employee> result = new ArrayList<Employee>();
+ Linq4j.asEnumerable(employees)
+ .where(
+ new Predicate1<Employee>() {
+ public boolean apply(Employee e) {
+ return e.name.contains("e");
+ }
+ })
+ .into(result);
+ assertEquals(
+ "[Employee(name: Fred, deptno:10), Employee(name: Janet, deptno:10)]",
+ result.toString());
+ }
+
+ public void testList() {
+ final List<Employee> employees = Arrays.asList(
+ new Employee(100, "Fred", 10),
+ new Employee(110, "Bill", 30),
+ new Employee(120, "Eric", 10),
+ new Employee(130, "Janet", 10));
+ final Map<Employee, Department> empDepts =
+ new HashMap<Employee, Department>();
+ for (Employee employee : employees) {
+ empDepts.put(employee, depts[(employee.deptno - 10) / 10]);
+ }
+ final List<Grouping<Object, Map.Entry<Employee, Department>>> result =
+ new ArrayList<Grouping<Object, Map.Entry<Employee, Department>>>();
+ Linq4j.asEnumerable(empDepts.entrySet())
+ .groupBy(
+ new Function1<Map.Entry<Employee, Department>, Object>() {
+ public Object apply(Map.Entry<Employee, Department> entry) {
+ return entry.getValue();
+ }
+ })
+ .into(result);
+ assertNotNull(result.toString());
+ }
+
public static class Employee {
public final int empno;
public final String name;
diff --git a/src/test/java/net/hydromatic/linq4j/test/PrimitiveTest.java b/src/test/java/net/hydromatic/linq4j/test/PrimitiveTest.java
index 509f1c5..d0b67b8 100644
--- a/src/test/java/net/hydromatic/linq4j/test/PrimitiveTest.java
+++ b/src/test/java/net/hydromatic/linq4j/test/PrimitiveTest.java
@@ -70,6 +70,20 @@
assertEquals(boolean[].class, Primitive.box(boolean[].class));
}
+ public void testOfBox() {
+ assertEquals(Primitive.INT, Primitive.ofBox(Integer.class));
+ assertNull(Primitive.ofBox(int.class));
+ assertNull(Primitive.ofBox(String.class));
+ assertNull(Primitive.ofBox(Integer[].class));
+ }
+
+ public void testOfBoxOr() {
+ assertEquals(Primitive.INT, Primitive.ofBox(Integer.class));
+ assertNull(Primitive.ofBox(int.class));
+ assertNull(Primitive.ofBox(String.class));
+ assertNull(Primitive.ofBox(Integer[].class));
+ }
+
/** Tests the {@link Primitive#number(Number)} method. */
public void testNumber() {
Number number = Primitive.SHORT.number(Integer.valueOf(2));