/******************************************************************************* * Copyright (c) 2006 Oracle Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cameron Bateman/Oracle - initial API and implementation * ********************************************************************************/ package org.eclipse.jst.jsf.validation.internal.el.operators; import java.math.BigDecimal; import java.math.BigInteger; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.jdt.core.Signature; import org.eclipse.jst.jsf.common.internal.types.FloatLiteralType; import org.eclipse.jst.jsf.common.internal.types.IAssignable; import org.eclipse.jst.jsf.common.internal.types.IntegerLiteralType; import org.eclipse.jst.jsf.common.internal.types.LiteralType; import org.eclipse.jst.jsf.common.internal.types.TypeCoercer; import org.eclipse.jst.jsf.common.internal.types.TypeCoercionException; import org.eclipse.jst.jsf.common.internal.types.TypeConstants; import org.eclipse.jst.jsf.common.internal.types.TypeTransformer; import org.eclipse.jst.jsf.common.internal.types.ValueType; import org.eclipse.jst.jsf.validation.internal.el.diagnostics.DiagnosticFactory; /** * Represents non-dividing arithmetic EL operators: +,-,* * Based on JSP.2.3.5.1 * * @author cbateman * */ /*package*/ abstract class NoDivArithmeticBinaryOperator extends ArithmeticBinaryOperator { NoDivArithmeticBinaryOperator(DiagnosticFactory diagnosticFactory) { super(diagnosticFactory); } protected abstract Long doRealOperation(Long firstArg, Long secondArg); protected abstract Double doRealOperation(Double firstArg, Double secondArg); public ValueType performOperation(ValueType firstArg, ValueType secondArg) { // JSP.2.3.5.1, step 1, if either arg is null, return (Long) 0 if (TypeCoercer.typeIsNull(firstArg.getSignature()) && TypeCoercer.typeIsNull(secondArg.getSignature())) { return new IntegerLiteralType(0); } final String boxedFirstArg = TypeTransformer.transformBoxPrimitives(firstArg.getSignature()); final String boxedSecondArg = TypeTransformer.transformBoxPrimitives(secondArg.getSignature()); // JSP.2.3.5.1, step 2, if either arg is a BigDecimal, coerce to BigDecimal // and apply if (TypeConstants.TYPE_BIG_DOUBLE.equals(boxedFirstArg) || TypeConstants.TYPE_BIG_DOUBLE.equals(boxedSecondArg)) { return handleNumericArithmetic(firstArg, secondArg, BigDecimal.class); } // JSP.2.3.5.1, step 3, if either arg is float or double or // a String containing "., e or E", then coerce if the other is // a big int, coerce up to BigDecimal, else to Double // Note: we are ignoring strings we can't resolve to figure out // if the contain "., e or E". Assume they always do if (TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedFirstArg) ||TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedSecondArg) ||TypeConstants.TYPE_BOXED_FLOAT.equals(boxedFirstArg) ||TypeConstants.TYPE_BOXED_FLOAT.equals(boxedSecondArg)) { if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstArg) ||TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondArg)) { // if the other operand is BigInteger, treat as BigDecimal return handleNumericArithmetic(firstArg, secondArg, BigDecimal.class); } // otherwise as double return handleNumericArithmetic(firstArg, secondArg, Double.class); } // JSP.2.3.5.1, step 4, if one is a big integer, coerce to big integer if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstArg) || TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondArg)) { return handleNumericArithmetic(firstArg, secondArg, BigInteger.class); } // JSP.2.3.5.1, step 5, otherwise, try to coerce to Long return handleNumericArithmetic(firstArg, secondArg, Long.class); } public Diagnostic validate(ValueType firstArg, ValueType secondArg) { if (TypeConstants.TYPE_JAVAOBJECT.equals(firstArg.getSignature()) || TypeConstants.TYPE_JAVAOBJECT.equals(secondArg.getSignature())) { return Diagnostic.OK_INSTANCE; } // JSP.2.3.5.1, step 1, if either arg is null, return (Long) 0 if (TypeCoercer.typeIsNull(firstArg.getSignature()) && TypeCoercer.typeIsNull(secondArg.getSignature())) { return _diagnosticFactory.create_BINARY_OP_BOTH_OPERANDS_NULL(getOperatorName()); } final String boxedFirstArg = TypeTransformer.transformBoxPrimitives(firstArg.getSignature()); final String boxedSecondArg = TypeTransformer.transformBoxPrimitives(secondArg.getSignature()); // JSP.2.3.5.1, step 2, if either arg is a BigDecimal, coerce to BigDecimal // and apply if (TypeConstants.TYPE_BIG_DOUBLE.equals(boxedFirstArg) || TypeConstants.TYPE_BIG_DOUBLE.equals(boxedSecondArg)) { return validateNumericArithmetic(firstArg, secondArg, BigDecimal.class); } // JSP.2.3.5.1, step 3, if either arg is float or double or // a String containing "., e or E", then coerce if the other is // a big int, coerce up to BigDecimal, else to Double // Note: we are ignoring strings we can't resolve to figure out // if the contain "., e or E". Assume they always do if (TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedFirstArg) ||TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedSecondArg) ||TypeConstants.TYPE_BOXED_FLOAT.equals(boxedFirstArg) ||TypeConstants.TYPE_BOXED_FLOAT.equals(boxedSecondArg)) { if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstArg) ||TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondArg)) { // if the other operand is BigInteger, treat as BigDecimal return validateNumericArithmetic(firstArg, secondArg, BigDecimal.class); } // otherwise as double return validateNumericArithmetic(firstArg, secondArg, Double.class); } // JSP.2.3.5.1, step 4, if one is a big integer, coerce to big integer if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstArg) || TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondArg)) { return validateNumericArithmetic(firstArg, secondArg, BigInteger.class); } // JSP.2.3.5.1, step 5, otherwise, try to coerce to Long return validateNumericArithmetic(firstArg, secondArg, Long.class); } /** * @param firstArg * @param secondArg * @param numberType * @return a value type based on the result of the arithmetic operation */ protected ValueType handleNumericArithmetic(ValueType firstArg, ValueType secondArg, Class numberType) { try { // final String coercedFirstArg = TypeCoercer.coerceToNumber(TypeTransformer.transformBoxPrimitives(firstArg.getSignature())); // final String coercedSecondArg = TypeCoercer.coerceToNumber(TypeTransformer.transformBoxPrimitives(secondArg.getSignature())); if (firstArg instanceof LiteralType && secondArg instanceof LiteralType) { try { Number firstValue = ((LiteralType)firstArg).coerceToNumber(numberType); Number secondValue = ((LiteralType)secondArg).coerceToNumber(numberType); LiteralType result = null; if (numberType == Double.class) { Double resultValue = doRealOperation((Double)firstValue, (Double) secondValue); result = new FloatLiteralType(resultValue.doubleValue()); } else if (numberType == Long.class) { Long resultValue = doRealOperation((Long) firstValue, (Long) secondValue); result = new IntegerLiteralType(resultValue.longValue()); } else { throw new AssertionError("unsupport arithmetic upcast type"); //$NON-NLS-1$ } return result; } catch (TypeCoercionException tce) { // could happen if two string literals passed return null; } } // if we get to here, then we have two valid numeric arith // types, but at least one is not a literal, so the best we can // say is that the return will be the same asthe type of numeric // coercion if (numberType == BigDecimal.class) { return new ValueType(TypeConstants.TYPE_BIG_DOUBLE, IAssignable.ASSIGNMENT_TYPE_RHS); } else if (numberType == Double.class) { return new ValueType(Signature.SIG_DOUBLE, IAssignable.ASSIGNMENT_TYPE_RHS); } else if (numberType == BigInteger.class) { return new ValueType(TypeConstants.TYPE_BIG_INTEGER, IAssignable.ASSIGNMENT_TYPE_RHS); } else { return new ValueType(Signature.SIG_LONG, IAssignable.ASSIGNMENT_TYPE_RHS); } } catch (TypeCoercionException tce) { // coercion to number failed, so no go return null; } } /** * @param firstArg * @param secondArg * @param numberType * @return a diagnostic validating the arithmetic expr firstArg op secondArg */ protected Diagnostic validateNumericArithmetic(ValueType firstArg, ValueType secondArg, Class numberType) { try { // final String coercedFirstArg = TypeCoercer.coerceToNumber(TypeTransformer.transformBoxPrimitives(firstArg.getSignature())); // final String coercedSecondArg = TypeCoercer.coerceToNumber(TypeTransformer.transformBoxPrimitives(secondArg.getSignature())); if (firstArg instanceof LiteralType && secondArg instanceof LiteralType) { try { Number firstValue = ((LiteralType)firstArg).coerceToNumber(numberType); Number secondValue = ((LiteralType)secondArg).coerceToNumber(numberType); Number result = null; if (numberType == Double.class) { result = doRealOperation((Double)firstValue, (Double) secondValue); } else if (numberType == Long.class) { result = doRealOperation((Long) firstValue, (Long) secondValue); } else { throw new AssertionError("unsupport arithmetic upcast type"); //$NON-NLS-1$ } return _diagnosticFactory. create_BINARY_OP_CONSTANT_EXPRESSION_ALWAYS_EVAL_SAME (getOperatorName(), result.toString()); } catch (TypeCoercionException tce) { // could happen when two strings are passed return _diagnosticFactory. create_BINARY_OP_COULD_NOT_COERCE_LITERALS_TO_NUMBERS(); } } // if we get to here, then we have two valid numeric arith // types, but at least one is not a literal // everything should be ok return Diagnostic.OK_INSTANCE; } catch (TypeCoercionException tce) { // coercion to number failed, so no go return _diagnosticFactory. create_BINARY_OP_COULD_NOT_MAKE_NUMERIC_COERCION(getOperatorName()); } } }