blob: 5bdc163fc5037e49e20b59bc558cab2e6306acde [file] [log] [blame]
/*******************************************************************************
* 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)
{
// 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");
}
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");
}
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());
}
}
}