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