| /******************************************************************************* |
| * 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.IType; |
| import org.eclipse.jst.jsf.common.internal.types.BooleanLiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.IAssignable; |
| import org.eclipse.jst.jsf.common.internal.types.LiteralType; |
| import org.eclipse.jst.jsf.common.internal.types.StringLiteralType; |
| 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.common.util.TypeUtil; |
| import org.eclipse.jst.jsf.context.symbol.internal.util.IObjectSymbolBasedValueType; |
| import org.eclipse.jst.jsf.validation.internal.el.diagnostics.DiagnosticFactory; |
| |
| /** |
| * A relational binary operator for equality: "==" or "!=" |
| * |
| * @author cbateman |
| * |
| */ |
| /*package*/ abstract class EqualityRelationalBinaryOperator extends RelationalBinaryOperator |
| { |
| EqualityRelationalBinaryOperator(final DiagnosticFactory diagnosticFactory, String jsfVersion) |
| { |
| super(diagnosticFactory, jsfVersion); |
| } |
| |
| /** |
| * @param firstArg |
| * @param secondArg |
| * @return the result of the operation |
| */ |
| protected abstract boolean doRealOperation(Boolean firstArg, Boolean secondArg); |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jst.jsf.validation.internal.el.operators.BinaryOperator#performOperation(org.eclipse.jst.jsf.core.internal.types.ValueType, org.eclipse.jst.jsf.core.internal.types.ValueType) |
| */ |
| public ValueType performOperation(ValueType firstArg, ValueType secondArg) |
| { |
| // JSP.2.3.5.7 step 1 if operands are equal, then true for ==, false for != |
| if (TypeCoercer.typeIsNull(firstArg.getSignature()) |
| && TypeCoercer.typeIsNull(secondArg.getSignature())) |
| { |
| // perform the operation on two arguments that are equal. |
| return BooleanLiteralType.valueOf(doRealOperation(Integer.valueOf(4), Integer.valueOf(4))); |
| } |
| |
| String boxedFirstType = TypeTransformer.transformBoxPrimitives(firstArg.getSignature()); |
| String boxedSecondType = TypeTransformer.transformBoxPrimitives(secondArg.getSignature()); |
| |
| // JSP.2.3.5.7 step 3, if either is BigDecimal, promote both and compare |
| if (TypeConstants.TYPE_BIG_DOUBLE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BIG_DOUBLE.equals(boxedSecondType)) |
| { |
| return handleNumericComparison(firstArg, secondArg, BigDecimal.class); |
| } |
| |
| // JSP.2.3.5.7, step 4 if either is a float or double, promote both to |
| // double and compare |
| if (TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_FLOAT.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_FLOAT.equals(boxedSecondType)) |
| { |
| return handleNumericComparison(firstArg, secondArg, Double.class); |
| } |
| |
| // JSP.2.3.5.7, step 5 if either is a big integer, promote and compare |
| if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstType) |
| || TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondType)) |
| { |
| return handleNumericComparison(firstArg, secondArg, BigInteger.class); |
| } |
| |
| // JSP.2.3.5.7, step 6 if either is Long or smaller, coerce both to Long |
| if (TypeConstants.TYPE_BOXED_LONG.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_LONG.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_INTEGER.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_INTEGER.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_SHORT.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_SHORT.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_BYTE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_BYTE.equals(boxedSecondType) |
| || TypeConstants.SIGNATURE_BOXED_CHARACTER.equals(boxedFirstType) |
| || TypeConstants.SIGNATURE_BOXED_CHARACTER.equals(boxedSecondType)) |
| { |
| return handleNumericComparison(firstArg, secondArg, Long.class); |
| } |
| |
| // JSP.2.3.5.7, step 7 if either is a boolean, coerce to boolean |
| if (TypeConstants.TYPE_BOXED_BOOLEAN.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_BOOLEAN.equals(boxedSecondType)) |
| { |
| return handleBooleanComparison(firstArg, secondArg); |
| } |
| |
| // Unified EL 1.8.2, step 8 if either is a enum, then coerce both to enum |
| // NOTE: we handle the JSF 1.1 case also where enums are treated as non-coercable |
| // Object's |
| if (firstArg.isEnumType() || secondArg.isEnumType()) |
| { |
| return handleEnumComparison(firstArg, secondArg); |
| } |
| |
| // JSP.2.3.5.7, step 8 if either is a string, coerce to string and |
| // compare lexically |
| if (TypeConstants.TYPE_STRING.equals(boxedFirstType) |
| || TypeConstants.TYPE_STRING.equals(boxedSecondType)) |
| { |
| return handleStringComparison(firstArg, secondArg); |
| } |
| |
| // otherwise, an equal compare will be done A.equals(B). Since |
| return new ValueType(TypeConstants.TYPE_BOOLEAN, IAssignable.ASSIGNMENT_TYPE_RHS); |
| } |
| |
| private ValueType handleEnumComparison(ValueType firstArg, |
| ValueType secondArg) |
| { |
| assert firstArg.isEnumType() || secondArg.isEnumType(); |
| |
| // if the first is not an enum, then we have non-Enum == Enum case |
| if (!firstArg.isEnumType()) |
| { |
| return handleComparsionOfEnumAndNonEnum(secondArg, firstArg); |
| } |
| |
| // if the second is not an enum, then we have Enum == non-Enum case |
| if (!secondArg.isEnumType()) |
| { |
| return handleComparsionOfEnumAndNonEnum(firstArg, secondArg); |
| } |
| |
| // only other case is they are both enums. Check if they are directly |
| // comparable. |
| if (TypeUtil.canNeverBeEqual(firstArg.getSignature(), secondArg.getSignature())) |
| { |
| boolean result = doRealOperation("foo", "notFoo"); // just simulate the operation where the operands are not equal //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| return BooleanLiteralType.valueOf(result); |
| } |
| |
| // otherwise, all we know is that it's a boolean |
| return new ValueType(TypeConstants.TYPE_BOOLEAN, IAssignable.ASSIGNMENT_TYPE_RHS); |
| } |
| |
| private ValueType handleComparsionOfEnumAndNonEnum(ValueType enumType, |
| ValueType nonEnumType) |
| { |
| // the only literal value that could have got us here is a |
| // StringLiteralValue since the others a filtered out before this is |
| // called |
| if (nonEnumType instanceof LiteralType) |
| { |
| assert nonEnumType instanceof StringLiteralType; |
| |
| Diagnostic result = validateIfEnumToStringComparison(((StringLiteralType)nonEnumType).getLiteralValue(), enumType); |
| |
| if (result != null) |
| { |
| // compare two things that aren't equal |
| return BooleanLiteralType.valueOf(doRealOperation("foo", "foo_")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| return new ValueType(TypeConstants.TYPE_BOOLEAN, IAssignable.ASSIGNMENT_TYPE_RHS); |
| } |
| |
| // if the arg is a String, then we can't prove anything before runtime |
| if (nonEnumType.isInstanceOf(TypeConstants.TYPE_STRING)) |
| { |
| return new ValueType(TypeConstants.TYPE_BOOLEAN, IAssignable.ASSIGNMENT_TYPE_RHS); |
| } |
| // otherwise, we know it will result in a problem since one is an enum |
| // and the other isn't so simply do a comparison on two things that aren't equals |
| return BooleanLiteralType.valueOf(doRealOperation("foo", "foo_")); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| 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.7 step 2 if either operand is null, then not equal |
| if (TypeCoercer.typeIsNull(firstArg.getSignature()) |
| && TypeCoercer.typeIsNull(secondArg.getSignature())) |
| { |
| // perform the operation on two arguments that are equal. |
| final boolean result = doRealOperation(Integer.valueOf(4), Integer.valueOf(4)); |
| return _diagnosticFactory.create_BINARY_OP_EQUALITY_COMP_WITH_NULL_ALWAYS_EVAL_SAME(Boolean.toString(result)); |
| } |
| |
| final String boxedFirstType = |
| TypeTransformer.transformBoxPrimitives(firstArg.getSignature()); |
| final String boxedSecondType = |
| TypeTransformer.transformBoxPrimitives(secondArg.getSignature()); |
| |
| // JSP.2.3.5.7 step 3, if either is BigDecimal, promote both and compare |
| if (TypeConstants.TYPE_BIG_DOUBLE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BIG_DOUBLE.equals(boxedSecondType)) |
| { |
| return validateNumericComparison(firstArg, secondArg, BigDecimal.class); |
| } |
| |
| // JSP.2.3.5.7, step 4 if either is a float or double, promote both to |
| // double and compare |
| if (TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_FLOAT.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_DOUBLE.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_FLOAT.equals(boxedSecondType)) |
| { |
| return validateNumericComparison(firstArg, secondArg, Double.class); |
| } |
| |
| // JSP.2.3.5.7, step 5 if either is a big integer, promote and compare |
| if (TypeConstants.TYPE_BIG_INTEGER.equals(boxedFirstType) |
| || TypeConstants.TYPE_BIG_INTEGER.equals(boxedSecondType)) |
| { |
| return validateNumericComparison(firstArg, secondArg, BigInteger.class); |
| } |
| |
| // JSP.2.3.5.7, step 6 if either is Long or smaller, coerce both to Long |
| if (TypeConstants.TYPE_BOXED_LONG.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_LONG.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_INTEGER.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_INTEGER.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_SHORT.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_SHORT.equals(boxedSecondType) |
| || TypeConstants.TYPE_BOXED_BYTE.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_BYTE.equals(boxedSecondType) |
| || TypeConstants.SIGNATURE_BOXED_CHARACTER.equals(boxedFirstType) |
| || TypeConstants.SIGNATURE_BOXED_CHARACTER.equals(boxedSecondType)) |
| { |
| return validateNumericComparison(firstArg, secondArg, Long.class); |
| } |
| |
| // JSP.2.3.5.7, step 7 if either is a boolean, coerce to boolean |
| if (TypeConstants.TYPE_BOXED_BOOLEAN.equals(boxedFirstType) |
| || TypeConstants.TYPE_BOXED_BOOLEAN.equals(boxedSecondType)) |
| { |
| return validateBooleanComparison(firstArg, secondArg); |
| } |
| |
| // Unified EL 1.8.2, step 8 if either is a enum, then coerce both to enum |
| // NOTE: we handle the JSF 1.1 case also where enums are treated as non-coercable |
| // Object's |
| if (firstArg.isEnumType() || secondArg.isEnumType()) |
| { |
| return validateEnumComparison(firstArg, secondArg); |
| } |
| |
| // JSP.2.3.5.7, step 8 if either is a string, coerce to string and |
| // compare lexically |
| if (TypeConstants.TYPE_STRING.equals(boxedFirstType) |
| || TypeConstants.TYPE_STRING.equals(boxedSecondType)) |
| { |
| return validateStringComparison(firstArg, secondArg); |
| } |
| |
| // otherwise, an equal compare will be done A.equals(B). Since |
| return Diagnostic.OK_INSTANCE; |
| } |
| |
| |
| /** |
| * Both types are coerced to boolean before comparison |
| * |
| * @param firstArg |
| * @param secondArg |
| * @return the result of the comparison |
| */ |
| private ValueType handleBooleanComparison(ValueType firstArg, ValueType secondArg) |
| { |
| boolean canCoerceFirstArg = |
| TypeCoercer.canCoerceToBoolean(TypeTransformer.transformBoxPrimitives(firstArg.getSignature())); |
| boolean canCoerceSecondArg = TypeCoercer.canCoerceToBoolean(TypeTransformer.transformBoxPrimitives(secondArg.getSignature())); |
| |
| if (! (canCoerceFirstArg && canCoerceSecondArg)) |
| { |
| return null; |
| } |
| |
| if (firstArg instanceof LiteralType && secondArg instanceof LiteralType) |
| { |
| try |
| { |
| Boolean firstValue = ((LiteralType)firstArg).coerceToBoolean(); |
| Boolean secondValue = ((LiteralType)secondArg).coerceToBoolean(); |
| |
| if (firstValue != null && secondValue != null) |
| { |
| boolean result = doRealOperation(firstValue, secondValue); |
| return result ? |
| BooleanLiteralType.TRUE : |
| BooleanLiteralType.FALSE; |
| } |
| } |
| catch (TypeCoercionException tce) |
| { |
| throw new AssertionError("should never get here; have already checked coercability above"); //$NON-NLS-1$ |
| } |
| } |
| |
| // otherwise, we have a valid comparison that results in boolean |
| return new ValueType(TypeConstants.TYPE_BOOLEAN, IAssignable.ASSIGNMENT_TYPE_RHS); |
| } |
| |
| private Diagnostic validateBooleanComparison(ValueType firstType, ValueType secondType) |
| { |
| boolean canCoerceFirstArg = |
| TypeCoercer.canCoerceToBoolean(TypeTransformer.transformBoxPrimitives(firstType.getSignature())); |
| boolean canCoerceSecondArg = TypeCoercer.canCoerceToBoolean(TypeTransformer.transformBoxPrimitives(secondType.getSignature())); |
| |
| if (!canCoerceFirstArg) |
| { |
| return _diagnosticFactory.create_BINARY_OP_CANNOT_COERCE_ARGUMENT_TO_BOOLEAN(Messages.getString("EqualityRelationalBinaryOperator.FirstArgument")); //$NON-NLS-1$ |
| } |
| |
| if (!canCoerceSecondArg) |
| { |
| return _diagnosticFactory.create_BINARY_OP_CANNOT_COERCE_ARGUMENT_TO_BOOLEAN(Messages.getString("EqualityRelationalBinaryOperator.SecondArgument")); //$NON-NLS-1$ |
| } |
| |
| if (firstType instanceof LiteralType && secondType instanceof LiteralType) |
| { |
| try |
| { |
| Boolean firstValue = ((LiteralType)firstType).coerceToBoolean(); |
| Boolean secondValue = ((LiteralType)secondType).coerceToBoolean(); |
| |
| if (firstValue != null && secondValue != null) |
| { |
| final boolean result = |
| doRealOperation(firstValue, secondValue); |
| return _diagnosticFactory. |
| create_BINARY_OP_CONSTANT_EXPRESSION_ALWAYS_EVAL_SAME(getOperationName(), Boolean.toString(result)); |
| } |
| } |
| catch (TypeCoercionException tce) |
| { |
| throw new AssertionError("should never get here; have already checked coercability above"); //$NON-NLS-1$ |
| } |
| } |
| |
| // otherwise, we have a valid comparison |
| return Diagnostic.OK_INSTANCE; |
| } |
| |
| @Override |
| protected Diagnostic validateStringComparison(ValueType firstType, |
| ValueType secondType) |
| { |
| String firstValue = null; |
| |
| if (firstType instanceof LiteralType) |
| { |
| firstValue = ((LiteralType)firstType).getLiteralValue(); |
| } |
| |
| String secondValue = null; |
| if (secondType instanceof LiteralType) |
| { |
| secondValue = ((LiteralType)secondType).getLiteralValue(); |
| } |
| |
| if (firstValue != null) |
| { |
| Diagnostic result = validateIfEnumToStringComparison(firstValue, secondType); |
| |
| if (result != null) |
| { |
| return result; |
| } |
| } |
| |
| if (secondValue != null) |
| { |
| Diagnostic result = validateIfEnumToStringComparison(secondValue, firstType); |
| |
| if (result != null) |
| { |
| return result; |
| } |
| } |
| |
| // if it's a string to enum compare, do the default parent thing |
| return super.validateStringComparison(firstType, secondType); |
| } |
| |
| |
| @Override |
| protected ValueType handleStringComparison(ValueType firstType, |
| ValueType secondType) |
| { |
| String firstValue = null; |
| |
| if (firstType instanceof LiteralType) |
| { |
| firstValue = ((LiteralType)firstType).getLiteralValue(); |
| } |
| |
| String secondValue = null; |
| if (secondType instanceof LiteralType) |
| { |
| secondValue = ((LiteralType)secondType).getLiteralValue(); |
| } |
| |
| if (firstValue != null) |
| { |
| Diagnostic result = validateIfEnumToStringComparison(firstValue, secondType); |
| |
| if (result != null) |
| { |
| return handleIfEnumToNonMemberStringComparison(firstValue, secondType); |
| } |
| } |
| |
| if (secondValue != null) |
| { |
| Diagnostic result = validateIfEnumToStringComparison(secondValue, firstType); |
| |
| if (result != null) |
| { |
| return handleIfEnumToNonMemberStringComparison(secondValue, firstType); |
| } |
| } |
| |
| // otherwise, do the super thing |
| return super.handleStringComparison(firstType, secondType); |
| } |
| |
| private Diagnostic validateEnumComparison(final ValueType firstArg, final ValueType secondArg) |
| { |
| assert firstArg.isEnumType() || secondArg.isEnumType(); |
| |
| // if the first is not an enum, then we have non-Enum == Enum case |
| if (!firstArg.isEnumType()) |
| { |
| return validateComparsionOfEnumAndNonEnum(firstArg, secondArg); |
| } |
| |
| // if the second is not an enum, then we have Enum == non-Enum case |
| if (!secondArg.isEnumType()) |
| { |
| return validateComparsionOfEnumAndNonEnum(secondArg, firstArg); |
| } |
| |
| // only other case is they are both enums. Check if they are directly |
| // comparable. |
| if (TypeUtil.canNeverBeEqual(firstArg.getSignature(), secondArg.getSignature())) |
| { |
| return _diagnosticFactory. |
| create_BINARY_COMPARISON_WITH_TWO_ENUMS_ALWAYS_SAME |
| (getOperationName() |
| , doRealOperation("foo", "notFoo") // just simulate the operation where the operands are not equal //$NON-NLS-1$ //$NON-NLS-2$ |
| , TypeUtil.getFullyQualifiedName(firstArg.getSignature()) |
| , TypeUtil.getFullyQualifiedName(secondArg.getSignature())); |
| } |
| |
| // otherwise, it's all good |
| return Diagnostic.OK_INSTANCE; |
| } |
| |
| private Diagnostic validateComparsionOfEnumAndNonEnum(final ValueType nonEnumType, final ValueType enumType) |
| { |
| // the only literal value that could have got us here is a |
| // StringLiteralValue since the others a filtered out before this is |
| // called |
| if (nonEnumType instanceof LiteralType) |
| { |
| assert nonEnumType instanceof StringLiteralType; |
| |
| Diagnostic result = validateIfEnumToStringComparison(((StringLiteralType)nonEnumType).getLiteralValue(), enumType); |
| |
| if (result != null) |
| { |
| return result; |
| } |
| return Diagnostic.OK_INSTANCE; |
| } |
| |
| // if the arg is a String, then we can't prove anything before runtime |
| if (nonEnumType.isInstanceOf(TypeConstants.TYPE_STRING)) |
| { |
| return Diagnostic.OK_INSTANCE; |
| } |
| // otherwise, we know it will result in a problem since one is an enum |
| // and the other isn't |
| return _diagnosticFactory. |
| create_BINARY_COMPARISON_WITH_ENUM_AND_UNCOERCABLE_NONCONST_ALWAYS_SAME |
| (getOperationName() |
| , doRealOperation("foo", "notFoo") // just simulate the operation where the operands are not equal //$NON-NLS-1$ //$NON-NLS-2$ |
| , TypeUtil.getFullyQualifiedName(enumType.getSignature()) |
| , TypeUtil.getFullyQualifiedName(nonEnumType.getSignature())); |
| } |
| |
| private Diagnostic validateIfEnumToStringComparison(final String literalValue, final ValueType validateIfEnum) |
| { |
| if (validateIfEnum.isEnumType() |
| && validateIfEnum instanceof IObjectSymbolBasedValueType) |
| { |
| final IObjectSymbolBasedValueType symbolValueType = |
| (IObjectSymbolBasedValueType) validateIfEnum; |
| |
| IType type = symbolValueType.getSymbol().getTypeDescriptor().resolveType(symbolValueType.getSymbol().getTypeDescriptor().getTypeSignature()); |
| |
| if (type != null && !TypeUtil.isEnumMember(type, literalValue)) |
| { |
| return _diagnosticFactory. |
| create_BINARY_COMPARISON_WITH_ENUM_AND_CONST_ALWAYS_SAME |
| (getOperationName() |
| , doRealOperation(literalValue, literalValue+"_") // just simulate the operation where the operands are not equal //$NON-NLS-1$ |
| , TypeUtil.getFullyQualifiedName(validateIfEnum.getSignature()) |
| , literalValue); |
| } |
| } |
| |
| return null; |
| } |
| |
| private ValueType handleIfEnumToNonMemberStringComparison(final String literalValue, final ValueType enumValueType) |
| { |
| // we need to apply the real operation to literalValue and any string that !equals(literalValue) |
| // since it's not a member of the enum |
| return BooleanLiteralType.valueOf(doRealOperation(literalValue, literalValue+"_")); //$NON-NLS-1$ |
| } |
| } |