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