blob: 29a86e96b6a74f3a03ed3752f67da6ae508cce0a [file] [log] [blame]
/*
* 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);
}
}
}