package edu.washington.escience.myria.expression; import com.google.common.base.Preconditions; import edu.washington.escience.myria.Type; import edu.washington.escience.myria.expression.evaluate.ExpressionOperatorParameter; /** * Cast the output from an expression to another type. */ public class CastExpression extends BinaryExpression { /***/ private static final long serialVersionUID = 1L; /** * Type of casting. */ private enum CastType { /** * from long to int. */ LONG_TO_INT, /** * from float or double to int. */ FLOATS_TO_INT, /** * int or long to float. */ INT_OR_LONG_TO_FLOAT, /** * from double to float. */ DOUBLE_TO_FLOAT, /** * from number to double. */ NUM_TO_DOUBLE, /** * from int to long. */ INT_TO_LONG, /** * from float or double to long. */ FLOATS_TO_LONG, /** * from anything to string. */ TO_STR, /** * from string to int. */ STR_TO_INT, /** * from string to float. */ STR_TO_FLOAT, /** * from string to double. */ STR_TO_DOUBLE, /** * from string to long. */ STR_TO_LONG, /** * unsupported cast. */ UNSUPPORTED, } /** * This is not really unused, it's used automagically by Jackson deserialization. */ @SuppressWarnings("unused") private CastExpression() {} /** * @param left what to cast. * @param right the output type of this operand is used to determine what to cast to. */ public CastExpression(final ExpressionOperator left, final ExpressionOperator right) { super(left, right); Preconditions.checkArgument( right instanceof TypeExpression || right instanceof TypeOfExpression, "The right child of a cast operator must be a Type or a TypeOf, not a %s", right.getClass().getSimpleName()); } @Override public Type getOutputType(final ExpressionOperatorParameter parameters) { final Type castFrom = getLeft().getOutputType(parameters); final Type castTo = getRight().getOutputType(parameters); Preconditions.checkArgument( (castFrom == castTo) || getCastType(castFrom, castTo) != CastType.UNSUPPORTED, "Cast from %s to %s is not supported.", castFrom, castTo); return castTo; } /** * @param castTo the type that we cast to * @param castFrom the type that we cast from * @return true if the cast can be done using value of */ private CastType getCastType(final Type castFrom, final Type castTo) { switch (castFrom + "|" + castTo) { /* any type to string. */ case "INT_TYPE|STRING_TYPE": case "FLOAT_TYPE|STRING_TYPE": case "DOUBLE_TYPE|STRING_TYPE": case "LONG_TYPE|STRING_TYPE": case "DATETIME_TYPE|STRING_TYPE": return CastType.TO_STR; /* numeric type to int. */ case "LONG_TYPE|INT_TYPE": return CastType.LONG_TO_INT; case "FLOAT_TYPE|INT_TYPE": case "DOUBLE_TYPE|INT_TYPE": return CastType.FLOATS_TO_INT; /* numeric type to float. */ case "INT_TYPE|FLOAT_TYPE": case "LONG_TYPE|FLOAT_TYPE": return CastType.INT_OR_LONG_TO_FLOAT; case "DOUBLE_TYPE|FLOAT_TYPE": return CastType.DOUBLE_TO_FLOAT; /* numeric type to double. */ case "INT_TYPE|DOUBLE_TYPE": case "LONG_TYPE|DOUBLE_TYPE": case "FLOAT_TYPE|DOUBLE_TYPE": return CastType.NUM_TO_DOUBLE; /* numeric type to long. */ case "INT_TYPE|LONG_TYPE": return CastType.INT_TO_LONG; case "FLOAT_TYPE|LONG_TYPE": case "DOUBLE_TYPE|LONG_TYPE": return CastType.FLOATS_TO_LONG; /* String to numeric */ case "STRING_TYPE|INT_TYPE": return CastType.STR_TO_INT; case "STRING_TYPE|FLOAT_TYPE": return CastType.STR_TO_FLOAT; case "STRING_TYPE|DOUBLE_TYPE": return CastType.STR_TO_DOUBLE; case "STRING_TYPE|LONG_TYPE": return CastType.STR_TO_LONG; default: break; } return CastType.UNSUPPORTED; } /** * Returns the string for a primitive cast: '((' + targetType + ')' + left + ')'. * * @param targetType string of the type to be casted to. * @param parameters parameters that are needed to determine the output type. * @return string that used for numeric type cast. */ private String getPrimitiveTypeCastString( final String targetType, final ExpressionOperatorParameter parameters) { return new StringBuilder() .append("((") .append(targetType) .append(")(") .append(getLeft().getJavaString(parameters)) .append("))") .toString(); } @Override public String getJavaString(final ExpressionOperatorParameter parameters) { final Type castFrom = getLeft().getOutputType(parameters); final Type castTo = getRight().getOutputType(parameters); /* Trivial casts are, of course, allowed. See also #626. */ if (castFrom == castTo) { return getLeft().getJavaString(parameters); } switch (getCastType(castFrom, castTo)) { case LONG_TO_INT: return getLeftFunctionCallString( "com.google.common.primitives.Ints.checkedCast", parameters); case FLOATS_TO_INT: return getLeftFunctionCallWithParameterString( "com.google.common.math.DoubleMath.roundToInt", parameters, "java.math.RoundingMode.DOWN"); case INT_OR_LONG_TO_FLOAT: return getPrimitiveTypeCastString("float", parameters); case DOUBLE_TO_FLOAT: return getLeftFunctionCallString( "edu.washington.escience.myria.util.MathUtils.castDoubleToFloat", parameters); case NUM_TO_DOUBLE: return getPrimitiveTypeCastString("double", parameters); case INT_TO_LONG: return getPrimitiveTypeCastString("long", parameters); case FLOATS_TO_LONG: return getLeftFunctionCallWithParameterString( "com.google.common.math.DoubleMath.roundToLong", parameters, "java.math.RoundingMode.DOWN"); case TO_STR: return getLeftFunctionCallString("String.valueOf", parameters); case STR_TO_INT: return getLeftFunctionCallString("Integer.parseInt", parameters); case STR_TO_FLOAT: return getLeftFunctionCallString("Float.parseFloat", parameters); case STR_TO_DOUBLE: return getLeftFunctionCallString("Double.parseDouble", parameters); case STR_TO_LONG: return getLeftFunctionCallString("Long.parseLong", parameters); default: throw new IllegalStateException("should not reach here."); } } }