| /* |
| * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation |
| // |
| package org.eclipse.persistence.jpa.jpql.tools; |
| |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AbsExpression_InvalidNumericExpression; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.AvgFunction_InvalidNumericExpression; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.BetweenExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CollectionMemberExpression_Embeddable; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ComparisonExpression_WrongComparisonType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConcatExpression_Expression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UndefinedConstructor; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ConstructorExpression_UnknownType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.CountFunction_DistinctEmbeddable; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.EncapsulatedIdentificationVariableExpression_NotMapValued; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.IdentificationVariable_EntityName; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LengthExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_FirstExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_SecondExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LocateExpression_ThirdExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.LowerExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_FirstExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.ModExpression_SecondExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NotExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.NullComparisonExpression_InvalidType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SqrtExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_FirstExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_SecondExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SubstringExpression_ThirdExpression_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.SumFunction_WrongType; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotAssignable; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NotResolvable; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpdateItem_NullNotAssignableToPrimitive; |
| import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.UpperExpression_WrongType; |
| |
| import java.lang.reflect.Constructor; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.jpa.jpql.AbstractSemanticValidator; |
| import org.eclipse.persistence.jpa.jpql.ExpressionTools; |
| import org.eclipse.persistence.jpa.jpql.ITypeHelper; |
| import org.eclipse.persistence.jpa.jpql.JPAVersion; |
| import org.eclipse.persistence.jpa.jpql.LiteralType; |
| import org.eclipse.persistence.jpa.jpql.LiteralVisitor; |
| import org.eclipse.persistence.jpa.jpql.SemanticValidatorHelper; |
| import org.eclipse.persistence.jpa.jpql.parser.AbsExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; |
| import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName; |
| import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.AndExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor; |
| import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.BooleanPrimaryBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.CaseExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.CountFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.EncapsulatedIdentificationVariableExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.EntryExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.Expression; |
| import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; |
| import org.eclipse.persistence.jpa.jpql.parser.IndexExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.InputParameter; |
| import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.KeyExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LengthExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LikeExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LiteralBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.LocateExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LowerExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.MaxFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.MinFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.ModExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.NotExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.NullExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral; |
| import org.eclipse.persistence.jpa.jpql.parser.OrExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.OrderByClause; |
| import org.eclipse.persistence.jpa.jpql.parser.OrderByItem; |
| import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration; |
| import org.eclipse.persistence.jpa.jpql.parser.SimpleArithmeticExpressionBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.SizeExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.StringLiteral; |
| import org.eclipse.persistence.jpa.jpql.parser.StringPrimaryBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.SubExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.SumFunction; |
| import org.eclipse.persistence.jpa.jpql.parser.TrimExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; |
| import org.eclipse.persistence.jpa.jpql.parser.UpdateItem; |
| import org.eclipse.persistence.jpa.jpql.parser.UpperExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.ValueExpression; |
| |
| /** |
| * This validator is responsible to gather the problems found in a JPQL query by validating the |
| * content to make sure it is semantically valid. The grammar is not validated by this visitor. |
| * <p> |
| * For instance, the function <b>AVG</b> accepts a state field path. The property it represents has |
| * to be of numeric type. <b>AVG(e.name)</b> is parsable but is not semantically valid because the |
| * type of name is a string (the property signature is: "<code>private String name</code>"). |
| * <p> |
| * Provisional API: This interface is part of an interim API that is still under development and |
| * expected to change significantly before reaching stability. It is available at this early stage |
| * to solicit feedback from pioneering adopters on the understanding that any code that uses this |
| * API will almost certainly be broken (repeatedly) as the API evolves. |
| * |
| * @see DefaultGrammarValidator |
| * |
| * @version 2.5 |
| * @since 2.3 |
| * @author Pascal Filion |
| */ |
| @SuppressWarnings("nls") |
| public class DefaultSemanticValidator extends AbstractSemanticValidator { |
| |
| /** |
| * This validator determines whether the {@link Expression} visited represents |
| * {@link Expression#NULL}. |
| */ |
| protected NullValueVisitor nullValueVisitor; |
| |
| /** |
| * This finder is responsible to retrieve the abstract schema name from the <b>UPDATE</b> range |
| * declaration expression. |
| */ |
| protected UpdateClauseAbstractSchemaNameFinder updateClauseAbstractSchemaNameFinder; |
| |
| /** |
| * The {@link TypeValidator TypeVlidators} mapped to their Java class. Those validators validate |
| * any {@link Expression} by making sure its type matches the desired type. |
| */ |
| protected Map<Class<? extends TypeValidator>, TypeValidator> validators; |
| |
| /** |
| * Creates a new <code>DefaultSemanticValidator</code>. |
| * |
| * @param queryContext The context used to query information about the JPQL query |
| * @exception NullPointerException The given {@link JPQLQueryContext} cannot be <code>null</code> |
| */ |
| public DefaultSemanticValidator(JPQLQueryContext queryContext) { |
| super(new GenericSemanticValidatorHelper(queryContext)); |
| } |
| |
| /** |
| * Creates a new <code>DefaultSemanticValidator</code>. |
| * |
| * @param helper The given helper allows the validator to access the JPA artifacts without using |
| * Hermes SPI directly |
| * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code> |
| * @since 2.4 |
| */ |
| public DefaultSemanticValidator(SemanticValidatorHelper helper) { |
| super(helper); |
| } |
| |
| protected boolean areTypesEquivalent(Object[] typeDeclarations1, Object[] typeDeclarations2) { |
| |
| // Empty array |
| if ((typeDeclarations1.length == 0) && (typeDeclarations2.length == 0)) { |
| return true; |
| } |
| |
| // Different array length, always not equivalent |
| if (typeDeclarations1.length != typeDeclarations2.length) { |
| return false; |
| } |
| |
| // Compare each element of the array together |
| for (int index = typeDeclarations1.length; --index >= 0; ) { |
| if (!helper.isTypeDeclarationAssignableTo(typeDeclarations1[index], typeDeclarations2[index])) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected LiteralVisitor buildLiteralVisitor() { |
| return new DefaultLiteralVisitor(); |
| } |
| |
| @Override |
| protected OwningClauseVisitor buildOwningClauseVisitor() { |
| return new OwningClauseVisitor(); |
| } |
| |
| protected ResultVariableInOrderByVisitor buildResultVariableInOrderByVisitor() { |
| return new ResultVariableInOrderByVisitor(); |
| } |
| |
| protected AbstractSchemaName findAbstractSchemaName(UpdateItem expression) { |
| UpdateClauseAbstractSchemaNameFinder visitor = getUpdateClauseAbstractSchemaNameFinder(); |
| try { |
| expression.accept(visitor); |
| return visitor.expression; |
| } |
| finally { |
| visitor.expression = null; |
| } |
| } |
| |
| protected NullValueVisitor getNullValueVisitor() { |
| if (nullValueVisitor == null) { |
| nullValueVisitor = new NullValueVisitor(); |
| } |
| return nullValueVisitor; |
| } |
| |
| protected Object getType(Expression expression) { |
| return helper.getType(expression); |
| } |
| |
| protected ITypeHelper getTypeHelper() { |
| return helper.getTypeHelper(); |
| } |
| |
| protected UpdateClauseAbstractSchemaNameFinder getUpdateClauseAbstractSchemaNameFinder() { |
| if (updateClauseAbstractSchemaNameFinder == null) { |
| updateClauseAbstractSchemaNameFinder = new UpdateClauseAbstractSchemaNameFinder(); |
| } |
| return updateClauseAbstractSchemaNameFinder; |
| } |
| |
| protected TypeValidator getValidator(Class<? extends TypeValidator> validatorClass) { |
| TypeValidator validator = validators.get(validatorClass); |
| if (validator == null) { |
| try { |
| Constructor<? extends TypeValidator> constructor = validatorClass.getDeclaredConstructor(DefaultSemanticValidator.class); |
| constructor.setAccessible(true); |
| validator = constructor.newInstance(this); |
| validators.put(validatorClass, validator); |
| } |
| catch (ReflectiveOperationException e) { /* Never happens */ } |
| } |
| return validator; |
| } |
| |
| @Override |
| protected void initialize() { |
| super.initialize(); |
| validators = new HashMap<Class<? extends TypeValidator>, TypeValidator>(); |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a boolean value;</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> |
| * otherwise |
| */ |
| protected boolean isBooleanType(Expression expression) { |
| return isValid(expression, BooleanTypeValidator.class); |
| } |
| |
| protected boolean isComparisonEquivalentType(Expression expression1, Expression expression2) { |
| |
| Object type1 = helper.getType(expression1); |
| Object type2 = helper.getType(expression2); |
| |
| // 1. The types are the same |
| // 2. The type cannot be determined, pretend they are equivalent, |
| // another rule will validate it |
| // 3. One is assignable to the other one |
| ITypeHelper typeHelper = getTypeHelper(); |
| |
| return (type1 == type2) || |
| !helper.isTypeResolvable(type1) || |
| !helper.isTypeResolvable(type2) || |
| typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) || |
| typeHelper.isDateType(type1) && typeHelper.isDateType(type2) || |
| helper.isAssignableTo(type1, type2) || |
| helper.isAssignableTo(type2, type1); |
| } |
| |
| protected boolean isEquivalentBetweenType(Expression expression1, Expression expression2) { |
| |
| Object type1 = helper.getType(expression1); |
| Object type2 = helper.getType(expression2); |
| |
| // The type cannot be determined, pretend they are equivalent, |
| // another rule will validate it |
| if (!helper.isTypeResolvable(type1) || |
| !helper.isTypeResolvable(type2)) { |
| |
| return true; |
| } |
| |
| ITypeHelper typeHelper = getTypeHelper(); |
| |
| if (type1 == type2) { |
| return typeHelper.isNumericType(type1) || |
| typeHelper.isStringType(type1) || |
| typeHelper.isDateType(type1); |
| } |
| else { |
| return typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) || |
| typeHelper.isStringType(type1) && typeHelper.isStringType(type2) || |
| typeHelper.isDateType(type1) && typeHelper.isDateType(type2); |
| } |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression}'s type is an integral type (long or integer).</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> |
| * otherwise |
| */ |
| protected boolean isIntegralType(Expression expression) { |
| |
| if (isNumericType(expression)) { |
| |
| ITypeHelper typeHelper = getTypeHelper(); |
| Object type = helper.getType(expression); |
| |
| return type == typeHelper.unknownType() || |
| typeHelper.isIntegralType(type); |
| } |
| |
| return false; |
| } |
| |
| protected boolean isNullValue(Expression expression) { |
| NullValueVisitor visitor = getNullValueVisitor(); |
| try { |
| expression.accept(visitor); |
| return visitor.valid; |
| } |
| finally { |
| visitor.valid = false; |
| } |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a numeric value;</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> |
| * otherwise |
| */ |
| protected boolean isNumericType(Expression expression) { |
| return isValid(expression, NumericTypeValidator.class); |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression}'s type is a string type.</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> |
| * otherwise |
| */ |
| protected boolean isStringType(Expression expression) { |
| return isValid(expression, StringTypeValidator.class); |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type by using the |
| * {@link TypeValidator}. |
| * |
| * @param expression The {@link Expression} to validate |
| * @param validatorClass The Java class of the {@link TypeValidator} that will determine if the |
| * given {@link Expression} has the right type |
| * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code> |
| * otherwise |
| */ |
| protected boolean isValid(Expression expression, Class<? extends TypeValidator> validatorClass) { |
| TypeValidator validator = getValidator(validatorClass); |
| try { |
| expression.accept(validator); |
| return validator.valid; |
| } |
| finally { |
| validator.valid = false; |
| } |
| } |
| |
| protected boolean isValidWithFindQueryBNF(AbstractExpression expression, String queryBNF) { |
| JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF); |
| try { |
| JPQLQueryBNF childQueryBNF = expression.getParent().findQueryBNF(expression); |
| validator.validate(childQueryBNF); |
| return validator.isValid(); |
| } |
| finally { |
| validator.dispose(); |
| } |
| } |
| |
| @Override |
| protected PathType selectClausePathExpressionPathType() { |
| return PathType.ANY_FIELD; |
| } |
| |
| @Override |
| protected boolean validateAbsExpression(AbsExpression expression) { |
| |
| boolean valid = super.validateAbsExpression(expression); |
| |
| if (valid) { |
| // The ABS function takes a numeric argument |
| valid = validateNumericType(expression.getExpression(), AbsExpression_InvalidNumericExpression); |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| protected int validateArithmeticExpression(ArithmeticExpression expression, |
| String leftExpressionWrongTypeMessageKey, |
| String rightExpressionWrongTypeMessageKey) { |
| |
| int result = super.validateArithmeticExpression( |
| expression, |
| leftExpressionWrongTypeMessageKey, |
| rightExpressionWrongTypeMessageKey |
| ); |
| |
| // Only validate the left expression if it's still valid |
| if (isValid(result, 0)) { |
| boolean valid = validateNumericType(expression.getLeftExpression(), leftExpressionWrongTypeMessageKey); |
| updateStatus(result, 0, valid); |
| } |
| |
| // Validate the right expression |
| boolean valid = validateNumericType(expression.getRightExpression(), rightExpressionWrongTypeMessageKey); |
| updateStatus(result, 1, valid); |
| |
| return result; |
| } |
| |
| @Override |
| protected boolean validateAvgFunction(AvgFunction expression) { |
| |
| // Arguments to the functions AVG must be numeric |
| boolean valid = super.validateAvgFunction(expression); |
| |
| if (valid) { |
| valid = validateNumericType(expression.getExpression(), AvgFunction_InvalidNumericExpression); |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| protected int validateBetweenExpression(BetweenExpression expression) { |
| |
| int result = super.validateBetweenExpression(expression); |
| |
| // Only add extra validation if the lower and upper expressions are still valid |
| if (isValid(result, 1) && |
| isValid(result, 2) && |
| !isEquivalentBetweenType(expression.getExpression(), expression.getLowerBoundExpression()) || |
| !isEquivalentBetweenType(expression.getExpression(), expression.getUpperBoundExpression())) { |
| |
| addProblem(expression, BetweenExpression_WrongType); |
| updateStatus(result, 1, false); |
| updateStatus(result, 2, false); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a boolean value;</li> |
| * <li>The {@link Expression}'s type is a boolean type.</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @param messageKey The key used to retrieve the localized message describing the problem |
| */ |
| protected boolean validateBooleanType(Expression expression, String messageKey) { |
| |
| if (isValid(expression, BooleanPrimaryBNF.ID) && !isBooleanType(expression)) { |
| addProblem(expression, messageKey); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected int validateCollectionMemberExpression(CollectionMemberExpression expression) { |
| |
| int result = super.validateCollectionMemberExpression(expression); |
| |
| if (isValid(result, 0) && expression.hasEntityExpression()) { |
| Expression entityExpression = expression.getEntityExpression(); |
| |
| // Check for embeddable type |
| Object type = helper.getType(entityExpression); |
| |
| if (helper.getEmbeddable(type) != null) { |
| addProblem(entityExpression, CollectionMemberExpression_Embeddable); |
| updateStatus(result, 0, false); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected boolean validateComparisonExpression(ComparisonExpression expression) { |
| |
| boolean valid = super.validateComparisonExpression(expression); |
| |
| // Only validate the left and right if they are still valid |
| if (valid && expression.hasLeftExpression() && expression.hasRightExpression()) { |
| |
| // The result of the two expressions must be like that of the other argument |
| // Comparisons over instances of embeddable class or map entry types are not supported |
| if (!isComparisonEquivalentType(expression.getLeftExpression(), |
| expression.getRightExpression())) { |
| |
| addProblem(expression, ComparisonExpression_WrongComparisonType); |
| valid = false; |
| } |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| protected boolean validateConcatExpression(ConcatExpression expression) { |
| |
| boolean result = super.validateConcatExpression(expression); |
| |
| // The CONCAT function returns a string that is a concatenation of its arguments |
| if (expression.hasExpression()) { |
| int index = 0; |
| for (Expression child : getChildren(expression.getExpression())) { |
| // Don't validate the first expression if it is not valid |
| if (index != 0 || result) { |
| result &= validateStringType(child, ConcatExpression_Expression_WrongType); |
| } |
| index++; |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected void validateConstructorExpression(ConstructorExpression expression) { |
| |
| super.validateConstructorExpression(expression); |
| String className = expression.getClassName(); |
| |
| // Only test the constructor if it has been specified |
| if (ExpressionTools.stringIsNotEmpty(className)) { |
| Object type = helper.getType(className); |
| |
| // Unknown type |
| if (!helper.isTypeResolvable(type)) { |
| int startPosition = position(expression) + 4 /* NEW + whitespace */; |
| int endPosition = startPosition + className.length(); |
| addProblem(expression, startPosition, endPosition, ConstructorExpression_UnknownType, className); |
| } |
| // Test the arguments' types with the constructors' types |
| else if (expression.hasLeftParenthesis() && |
| expression.hasConstructorItems()) { |
| |
| Expression constructorItems = expression.getConstructorItems(); |
| |
| // Retrieve the constructor's arguments so their type can be calculated |
| List<Expression> children = getChildren(constructorItems); |
| Object[] typeDeclarations = null; |
| boolean constructorFound = false; |
| |
| for (Object constructor : helper.getConstructors(type)) { |
| Object[] parameterTypeDeclarations = helper.getMethodParameterTypeDeclarations(constructor); |
| |
| // The number of items match, check their types are equivalent |
| if (children.size() == parameterTypeDeclarations.length) { |
| |
| // The constructor is the default constructor and both have no parameters |
| if (parameterTypeDeclarations.length == 0) { |
| constructor = true; |
| } |
| else { |
| |
| // Populate the type declaration array |
| if (typeDeclarations == null) { |
| typeDeclarations = new Object[children.size()]; |
| |
| for (int index = children.size(); --index >= 0; ) { |
| typeDeclarations[index] = helper.getTypeDeclaration(children.get(index)); |
| } |
| } |
| |
| constructorFound = areTypesEquivalent(parameterTypeDeclarations, typeDeclarations); |
| |
| if (constructorFound) { |
| break; |
| } |
| } |
| } |
| } |
| |
| // No constructor was found matching the argument list |
| if (!constructorFound) { |
| int startPosition = position(expression) + 4 /* NEW + whitespace */; |
| int endPosition = startPosition + className.length(); |
| addProblem(expression, startPosition, endPosition, ConstructorExpression_UndefinedConstructor); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void validateCountFunction(CountFunction expression) { |
| |
| super.validateCountFunction(expression); |
| |
| // The use of DISTINCT with COUNT is not supported for arguments of |
| // embeddable types or map entry types (weird because map entry is not |
| // allowed in a COUNT expression) |
| if (expression.hasExpression() && |
| expression.hasDistinct()) { |
| |
| Expression childExpression = expression.getExpression(); |
| |
| // Check for embeddable type |
| Object type = helper.getType(childExpression); |
| |
| if (helper.getEmbeddable(type) != null) { |
| int distinctLength = Expression.DISTINCT.length() + 1; // +1 = space |
| int startIndex = position(childExpression) - distinctLength; |
| int endIndex = startIndex + length(childExpression) + distinctLength; |
| addProblem(expression, startIndex, endIndex, CountFunction_DistinctEmbeddable); |
| } |
| } |
| } |
| |
| @Override |
| protected void validateEntryExpression(EntryExpression expression) { |
| validateMapIdentificationVariable(expression); |
| super.validateEntryExpression(expression); |
| } |
| |
| @Override |
| protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) { |
| |
| boolean valid = super.validateIdentificationVariable(expression, variable); |
| |
| for (String entityName : helper.entityNames()) { |
| |
| // An identification variable must not have the same name as any entity in the same |
| // persistence unit, unless it's representing an entity literal |
| if (variable.equalsIgnoreCase(entityName)) { |
| |
| // An identification variable could represent an entity type literal, |
| // validate the parent to make sure it allows it |
| if (!isValidWithFindQueryBNF(expression, LiteralBNF.ID)) { |
| int startIndex = position(expression); |
| int endIndex = startIndex + variable.length(); |
| addProblem(expression, startIndex, endIndex, IdentificationVariable_EntityName); |
| valid = false; |
| break; |
| } |
| } |
| } |
| |
| return valid; |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a integral value;</li> |
| * <li>The {@link Expression}'s type is an integral type (long or integer).</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @param queryBNF The unique identifier of the query BNF used to validate the type |
| * @param messageKey The key used to retrieve the localized message describing the problem |
| * @return <code>false</code> if the given expression was validated and is invalid; |
| * <code>true</code> otherwise |
| */ |
| protected boolean validateIntegralType(Expression expression, String queryBNF, String messageKey) { |
| |
| if (isValid(expression, queryBNF) && !isIntegralType(expression)) { |
| addProblem(expression, messageKey); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected void validateKeyExpression(KeyExpression expression) { |
| validateMapIdentificationVariable(expression); |
| super.validateKeyExpression(expression); |
| } |
| |
| @Override |
| protected boolean validateLengthExpression(LengthExpression expression) { |
| |
| boolean valid = super.validateLengthExpression(expression); |
| |
| if (valid) { |
| valid = validateStringType(expression.getExpression(), LengthExpression_WrongType); |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| protected int validateLocateExpression(LocateExpression expression) { |
| |
| int result = super.validateLocateExpression(expression); |
| |
| // The first argument is the string to be located; the second argument is the string to be |
| // searched; the optional third argument is an integer that represents the string position at |
| // which the search is started (by default, the beginning of the string to be searched) |
| if (isValid(result, 0)) { |
| boolean valid = validateStringType(expression.getFirstExpression(), LocateExpression_FirstExpression_WrongType); |
| updateStatus(result, 0, valid); |
| } |
| |
| if (isValid(result, 1)) { |
| boolean valid = validateStringType (expression.getSecondExpression(), LocateExpression_SecondExpression_WrongType); |
| updateStatus(result, 1, valid); |
| } |
| |
| if (isValid(result, 2)) { |
| boolean valid = validateNumericType(expression.getThirdExpression(), LocateExpression_ThirdExpression_WrongType); |
| updateStatus(result, 2, valid); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected boolean validateLowerExpression(LowerExpression expression) { |
| |
| boolean valid = super.validateLowerExpression(expression); |
| |
| if (valid) { |
| valid = validateStringType(expression.getExpression(), LowerExpression_WrongType); |
| } |
| |
| return valid; |
| } |
| |
| protected void validateMapIdentificationVariable(EncapsulatedIdentificationVariableExpression expression) { |
| |
| // The KEY, VALUE, and ENTRY operators may only be applied to |
| // identification variables that correspond to map-valued associations |
| // or map-valued element collections |
| if (expression.hasExpression()) { |
| |
| Expression childExpression = expression.getExpression(); |
| String variableName = literal(childExpression, LiteralType.IDENTIFICATION_VARIABLE); |
| |
| // Retrieve the identification variable's type without traversing the type parameters |
| if (ExpressionTools.stringIsNotEmpty(variableName)) { |
| Object typeDeclaration = helper.getTypeDeclaration(childExpression); |
| Object type = helper.getType(typeDeclaration); |
| |
| if (!getTypeHelper().isMapType(type)) { |
| addProblem( |
| childExpression, |
| EncapsulatedIdentificationVariableExpression_NotMapValued, |
| expression.getIdentifier() |
| ); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected int validateModExpression(ModExpression expression) { |
| |
| int result = super.validateModExpression(expression); |
| |
| // Don't validate the first expression if it's not valid |
| if (isValid(result, 0)) { |
| |
| boolean valid = validateIntegralType( |
| expression.getFirstExpression(), |
| expression.parameterExpressionBNF(0), |
| ModExpression_FirstExpression_WrongType |
| ); |
| |
| updateStatus(result, 0, valid); |
| } |
| |
| // Don't validate the second expression if it's not valid |
| if (isValid(result, 1)) { |
| |
| boolean valid = validateIntegralType( |
| expression.getSecondExpression(), |
| expression.parameterExpressionBNF(1), |
| ModExpression_SecondExpression_WrongType |
| ); |
| |
| updateStatus(result, 1, valid); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected void validateNotExpression(NotExpression expression) { |
| super.validateNotExpression(expression); |
| validateBooleanType(expression.getExpression(), NotExpression_WrongType); |
| } |
| |
| @Override |
| protected void validateNullComparisonExpression(NullComparisonExpression expression) { |
| |
| super.validateNullComparisonExpression(expression); |
| |
| // Null comparisons over instances of embeddable class types are not supported |
| StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getExpression()); |
| |
| if (pathExpression != null) { |
| Object type = helper.getType(pathExpression); |
| |
| if (helper.getEmbeddable(type) != null) { |
| addProblem(pathExpression, NullComparisonExpression_InvalidType, pathExpression.toParsedText()); |
| } |
| } |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a numeric value;</li> |
| * <li>The {@link Expression}'s type is an numeric type.</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @param messageKey The key used to retrieve the localized message describing the problem |
| * @return <code>false</code> if the given expression was validated and is invalid; |
| * <code>true</code> otherwise |
| */ |
| protected boolean validateNumericType(Expression expression, String messageKey) { |
| |
| if (isValid(expression, SimpleArithmeticExpressionBNF.ID) && !isNumericType(expression)) { |
| addProblem(expression, messageKey); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected boolean validateSqrtExpression(SqrtExpression expression) { |
| |
| boolean valid = super.validateSqrtExpression(expression); |
| |
| if (valid) { |
| // The SQRT function takes a numeric argument |
| valid = validateNumericType(expression.getExpression(), SqrtExpression_WrongType); |
| } |
| |
| return valid; |
| } |
| |
| /** |
| * Determines whether the given {@link Expression} is of the correct type based on these rules: |
| * <ul> |
| * <li>The {@link Expression} returns a String value;</li> |
| * <li>The {@link Expression}'s type is a String type.</li> |
| * </ul> |
| * |
| * @param expression The {@link Expression} to validate |
| * @param messageKey The key used to retrieve the localized message describing the problem |
| * @return <code>false</code> if the given expression was validated and is invalid; |
| * <code>true</code> otherwise |
| */ |
| protected boolean validateStringType(Expression expression, String messageKey) { |
| |
| if (isValid(expression, StringPrimaryBNF.ID) && !isStringType(expression)) { |
| addProblem(expression, messageKey, expression.toParsedText()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected int validateSubstringExpression(SubstringExpression expression) { |
| |
| int result = super.validateSubstringExpression(expression); |
| |
| if (isValid(result, 0)) { |
| boolean valid = validateStringType( |
| expression.getFirstExpression(), |
| SubstringExpression_FirstExpression_WrongType |
| ); |
| updateStatus(result, 0, valid); |
| } |
| |
| // The second and third arguments of the SUBSTRING function denote the starting position and |
| // length of the substring to be returned. These arguments are integers |
| if (isValid(result, 1)) { |
| |
| boolean valid = validateIntegralType( |
| expression.getSecondExpression(), |
| expression.getParameterQueryBNFId(1), |
| SubstringExpression_SecondExpression_WrongType |
| ); |
| |
| updateStatus(result, 1, valid); |
| } |
| |
| // The third argument is optional for JPA 2.0 |
| if (isValid(result, 2) && getJPAVersion().isNewerThanOrEqual(JPAVersion.VERSION_2_0)) { |
| |
| boolean valid = validateIntegralType( |
| expression.getThirdExpression(), |
| expression.getParameterQueryBNFId(2), |
| SubstringExpression_ThirdExpression_WrongType |
| ); |
| |
| updateStatus(result, 2, valid); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected boolean validateSumFunction(SumFunction expression) { |
| |
| boolean valid = super.validateSumFunction(expression); |
| |
| if (valid) { |
| // Arguments to the functions SUM must be numeric |
| valid = validateNumericType(expression.getExpression(), SumFunction_WrongType); |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| @SuppressWarnings("null") |
| protected boolean validateUpdateItem(UpdateItem expression) { |
| |
| // First validate the path expression |
| boolean valid = super.validateUpdateItem(expression); |
| |
| if (valid) { |
| |
| // Retrieve the entity to make sure the state field is part of it |
| AbstractSchemaName abstractSchemaName = findAbstractSchemaName(expression); |
| String entityName = (abstractSchemaName != null) ? abstractSchemaName.getText() : null; |
| |
| if (ExpressionTools.stringIsNotEmpty(entityName)) { |
| Object entity = helper.getEntityNamed(entityName); |
| |
| // Check the existence of the state field on the entity |
| if ((entity != null) && expression.hasSpaceAfterStateFieldPathExpression()) { |
| StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression()); |
| String stateFieldValue = (pathExpression != null) ? pathExpression.toParsedText() : null; |
| |
| if (ExpressionTools.stringIsNotEmpty(stateFieldValue)) { |
| |
| // State field without a dot |
| if (stateFieldValue.indexOf('.') == -1) { |
| Object mapping = helper.getMappingNamed(entity, stateFieldValue); |
| |
| if (mapping == null) { |
| addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue); |
| } |
| else { |
| validateUpdateItemTypes(expression, helper.getMappingType(mapping)); |
| } |
| } |
| else { |
| Object type = helper.getType(pathExpression); |
| |
| if (!helper.isTypeResolvable(type)) { |
| addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue); |
| } |
| else { |
| validateUpdateItemTypes(expression, type); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return valid; |
| } |
| |
| protected void validateUpdateItemTypes(UpdateItem expression, Object type) { |
| |
| if (expression.hasNewValue()) { |
| |
| Expression newValue = expression.getNewValue(); |
| ITypeHelper typeHelper = getTypeHelper(); |
| boolean nullValue = isNullValue(newValue); |
| |
| // A NULL value is ignored, except if the type is a primitive, null cannot be |
| // assigned to a mapping of primitive type |
| if (nullValue) { |
| if (typeHelper.isPrimitiveType(type)) { |
| addProblem(expression, UpdateItem_NullNotAssignableToPrimitive); |
| } |
| return; |
| } |
| |
| Object newValueType = getType(newValue); |
| |
| // Do a quick check for known JDK types: |
| // 1) Date/Time/Timestamp |
| // 2) Any classes related to a number, eg long/Long etc |
| if (!helper.isTypeResolvable(newValueType) || |
| typeHelper.isDateType(type) && typeHelper.isDateType(newValueType) || |
| (typeHelper.isNumericType(type) || typeHelper.isPrimitiveType(type)) && |
| (typeHelper.isNumericType(newValueType) || typeHelper.isPrimitiveType(newValueType))) { |
| |
| return; |
| } |
| |
| // The new value's type can't be assigned to the item's type |
| if (!helper.isAssignableTo(newValueType, type)) { |
| addProblem( |
| expression, |
| UpdateItem_NotAssignable, |
| helper.getTypeName(newValueType), |
| helper.getTypeName(type) |
| ); |
| } |
| } |
| } |
| |
| @Override |
| protected boolean validateUpperExpression(UpperExpression expression) { |
| |
| boolean valid = super.validateUpperExpression(expression); |
| |
| if (valid) { |
| // The UPPER function convert a string to upper case, |
| // with regard to the locale of the database |
| valid = validateStringType(expression.getExpression(), UpperExpression_WrongType); |
| } |
| |
| return valid; |
| } |
| |
| @Override |
| protected void validateValueExpression(ValueExpression expression) { |
| validateMapIdentificationVariable(expression); |
| super.validateValueExpression(expression); |
| } |
| |
| /** |
| * This visitor validates expression that is a boolean literal to make sure the type is a |
| * <b>Boolean</b>. |
| */ |
| protected class BooleanTypeValidator extends TypeValidator { |
| |
| @Override |
| protected boolean isRightType(Object type) { |
| return getTypeHelper().isBooleanType(type); |
| } |
| |
| @Override |
| public void visit(AllOrAnyExpression expression) { |
| // ALL|ANY|SOME always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(AndExpression expression) { |
| // AND always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(BetweenExpression expression) { |
| // BETWEEN always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(ComparisonExpression expression) { |
| // A comparison always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(EmptyCollectionComparisonExpression expression) { |
| // IS EMPTY always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(ExistsExpression expression) { |
| // EXISTS always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(KeywordExpression expression) { |
| valid = true; |
| } |
| |
| @Override |
| public void visit(LikeExpression expression) { |
| // LIKE always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(NotExpression expression) { |
| // NOT always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(NullComparisonExpression expression) { |
| // IS NULL always returns a boolean value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(OrExpression expression) { |
| // OR always returns a boolean value |
| valid = true; |
| } |
| } |
| |
| protected static class NullValueVisitor extends AbstractExpressionVisitor { |
| |
| /** |
| * Determines whether the {@link Expression} visited represents {@link Expression#NULL}. |
| */ |
| protected boolean valid; |
| |
| @Override |
| public void visit(KeywordExpression expression) { |
| valid = expression.getText() == Expression.NULL; |
| } |
| } |
| |
| /** |
| * This visitor validates expression that is a numeric literal to make sure the type is an |
| * instance of <b>Number</b>. |
| */ |
| protected class NumericTypeValidator extends TypeValidator { |
| |
| @Override |
| protected boolean isRightType(Object type) { |
| return getTypeHelper().isNumericType(type); |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| // ABS always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(AdditionExpression expression) { |
| // An addition expression always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(ArithmeticFactor expression) { |
| // +/- is always numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| // AVG always returns a double |
| valid = true; |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| // COUNT always returns a long |
| valid = true; |
| } |
| |
| @Override |
| public void visit(DivisionExpression expression) { |
| // A division expression always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(IndexExpression expression) { |
| // INDEX always returns an integer |
| valid = true; |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| // LENGTH always returns an integer |
| valid = true; |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| // LOCATE always returns an integer |
| valid = true; |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| // SUM always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| // SUM always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(ModExpression expression) { |
| // MOD always returns an integer |
| valid = true; |
| } |
| |
| @Override |
| public void visit(MultiplicationExpression expression) { |
| // A multiplication expression always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(NumericLiteral expression) { |
| // A numeric literal is by definition valid |
| valid = true; |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| // SIZE always returns an integer |
| valid = true; |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| // SQRT always returns a double |
| valid = true; |
| } |
| |
| @Override |
| public void visit(SubtractionExpression expression) { |
| // A subtraction expression always returns a numeric value |
| valid = true; |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| // SUM always returns a long |
| valid = true; |
| } |
| } |
| |
| protected static class ResultVariableInOrderByVisitor extends AbstractExpressionVisitor { |
| |
| public boolean result; |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @Override |
| public void visit(OrderByClause expression) { |
| result = true; |
| } |
| |
| @Override |
| public void visit(OrderByItem expression) { |
| expression.getParent().accept(this); |
| } |
| } |
| |
| /** |
| * This visitor validates that the {@link Expression} is a string primary and to make sure the |
| * type is String. |
| */ |
| protected class StringTypeValidator extends TypeValidator { |
| |
| @Override |
| protected boolean isRightType(Object type) { |
| return getTypeHelper().isStringType(type); |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| // CONCAT always returns a string |
| valid = true; |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| // LOWER always returns a string |
| valid = true; |
| } |
| |
| @Override |
| public void visit(StringLiteral expression) { |
| // A string literal is by definition valid |
| valid = true; |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| // SUBSTRING always returns a string |
| valid = true; |
| } |
| |
| @Override |
| public void visit(TrimExpression expression) { |
| // TRIM always returns a string |
| valid = true; |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| // UPPER always returns a string |
| valid = true; |
| } |
| } |
| |
| /** |
| * The basic validator for validating the type of an {@link Expression}. |
| */ |
| protected abstract class TypeValidator extends AbstractExpressionVisitor { |
| |
| /** |
| * Determines whether the expression that was visited returns a number. |
| */ |
| protected boolean valid; |
| |
| /** |
| * Determines whether the given type is the expected type. |
| * |
| * @param type The type to validate |
| * @return <code>true</code> if the given type is of the expected type; <code>false</code> if |
| * it's not the right type |
| */ |
| protected abstract boolean isRightType(Object type); |
| |
| @Override |
| public final void visit(CaseExpression expression) { |
| Object type = getType(expression); |
| valid = isRightType(type); |
| } |
| |
| @Override |
| public final void visit(CoalesceExpression expression) { |
| Object type = getType(expression); |
| valid = isRightType(type); |
| } |
| |
| @Override |
| public final void visit(InputParameter expression) { |
| // An input parameter can't be validated until the query |
| // is executed so it is assumed to be valid |
| valid = true; |
| } |
| |
| @Override |
| public void visit(NullExpression expression) { |
| // The missing expression is validated by GrammarValidator |
| valid = true; |
| } |
| |
| @Override |
| public final void visit(NullIfExpression expression) { |
| expression.getFirstExpression().accept(this); |
| } |
| |
| @Override |
| public final void visit(StateFieldPathExpression expression) { |
| Object type = getType(expression); |
| valid = isRightType(type); |
| } |
| |
| @Override |
| public final void visit(SubExpression expression) { |
| expression.getExpression().accept(this); |
| } |
| } |
| |
| protected static class UpdateClauseAbstractSchemaNameFinder extends AbstractExpressionVisitor { |
| |
| protected AbstractSchemaName expression; |
| |
| @Override |
| public void visit(AbstractSchemaName expression) { |
| this.expression = expression; |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @Override |
| public void visit(RangeVariableDeclaration expression) { |
| expression.getRootObject().accept(this); |
| } |
| |
| @Override |
| public void visit(UpdateClause expression) { |
| expression.getRangeVariableDeclaration().accept(this); |
| } |
| |
| @Override |
| public void visit(UpdateItem expression) { |
| expression.getParent().accept(this); |
| } |
| } |
| } |