/******************************************************************************* | |
* Copyright (c) 2006, 2013 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 v1.0 and Eclipse Distribution License v. 1.0 | |
* which accompanies this distribution. | |
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html | |
* and the Eclipse Distribution License is available at | |
* http://www.eclipse.org/org/documents/edl-v10.php. | |
* | |
* Contributors: | |
* Oracle - initial API and implementation | |
* | |
******************************************************************************/ | |
package org.eclipse.persistence.jpa.jpql; | |
import java.util.Collection; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; | |
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor; | |
import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor; | |
import org.eclipse.persistence.jpa.jpql.parser.BadExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause; | |
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement; | |
import org.eclipse.persistence.jpa.jpql.parser.Expression; | |
import org.eclipse.persistence.jpa.jpql.parser.ExpressionRegistry; | |
import org.eclipse.persistence.jpa.jpql.parser.ExpressionVisitor; | |
import org.eclipse.persistence.jpa.jpql.parser.FromClause; | |
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause; | |
import org.eclipse.persistence.jpa.jpql.parser.HavingClause; | |
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar; | |
import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF; | |
import org.eclipse.persistence.jpa.jpql.parser.NullExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause; | |
import org.eclipse.persistence.jpa.jpql.parser.SelectClause; | |
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement; | |
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause; | |
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause; | |
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement; | |
import org.eclipse.persistence.jpa.jpql.parser.SubExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression; | |
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; | |
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement; | |
import org.eclipse.persistence.jpa.jpql.parser.WhereClause; | |
import org.eclipse.persistence.jpa.jpql.utility.CollectionTools; | |
/** | |
* The abstract definition of a validator, which provides helper methods and visitors. | |
* <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 AbstractGrammarValidator | |
* @see AbstractSemanticValidator | |
* | |
* @version 2.5 | |
* @since 2.4 | |
* @author Pascal Filion | |
*/ | |
@SuppressWarnings("nls") | |
public abstract class AbstractValidator extends AnonymousExpressionVisitor { | |
/** | |
* This visitor is responsible to traverse the children of a {@link CollectionExpression} in | |
* order to properly validate the {@link Expression}. | |
*/ | |
private BypassChildCollectionExpressionVisitor bypassChildCollectionExpressionVisitor; | |
/** | |
* This visitor is responsible to traverse the parent hierarchy and to skip {@link SubExpression} | |
* if it's a parent. | |
*/ | |
private BypassParentSubExpressionVisitor bypassParentSubExpressionVisitor; | |
/** | |
* This visitor gathers the children of a {@link CollectionExpression} or a single visited | |
* {@link Expression}. | |
*/ | |
private ChildrenCollectorVisitor childrenCollectorVisitor; | |
/** | |
* This visitor is used to retrieve a variable name from various type of | |
* {@link org.eclipse.persistence.jpa.jpql.parser.Expression JPQL Expression}. | |
*/ | |
private LiteralVisitor literalVisitor; | |
/** | |
* | |
* | |
* @since 2.5 | |
*/ | |
private NestedArrayVisitor nestedArrayVisitor; | |
/** | |
* This visitor is responsible to traverse the parent hierarchy and to retrieve the owning clause | |
* of the {@link Expression} being visited. | |
*/ | |
private OwningClauseVisitor owningClauseVisitor; | |
/** | |
* This visitor is responsible to traverse the parent hierarchy and to retrieve the owning | |
* statement of the {@link Expression} being visited. | |
* | |
* @since 2.4 | |
*/ | |
private OwningStatementVisitor owningStatementVisitor; | |
/** | |
* The list of {@link JPQLQueryProblem} describing grammatical and semantic issues found in the query. | |
*/ | |
private Collection<JPQLQueryProblem> problems; | |
/** | |
* This visitor determines whether the visited {@link Expression} is a subquery or not. | |
* | |
* @since 2.5 | |
*/ | |
private SubqueryVisitor subqueryVisitor; | |
/** | |
* The {@link JPQLQueryBNFValidator} mapped by the BNF IDs. | |
*/ | |
private Map<String, JPQLQueryBNFValidator> validators; | |
/** | |
* Creates a new <code>AbstractValidator</code>. | |
*/ | |
protected AbstractValidator() { | |
super(); | |
initialize(); | |
} | |
/** | |
* Adds a new validation problem that was found in the given {@link Expression}. | |
* | |
* @param expression The {@link Expression} that is either not following the BNF grammar or that | |
* has semantic problems | |
* @param startPosition The position where the problem was encountered | |
* @param endPosition The position where the problem ends, inclusively | |
* @param messageKey The key used to retrieve the localized message describing the problem | |
* @param messageArguments The list of arguments that can be used to format the localized | |
* description of the problem | |
*/ | |
protected void addProblem(Expression expression, | |
int startPosition, | |
int endPosition, | |
String messageKey, | |
String... messageArguments) { | |
problems.add(buildProblem(expression, startPosition, endPosition, messageKey, messageArguments)); | |
} | |
/** | |
* Adds a new validation problem that was found in the given {@link Expression}. | |
* | |
* @param expression The {@link Expression} that is either not following the BNF grammar or that | |
* has semantic problems | |
* @param startPosition The position where the problem was encountered | |
* @param messageKey The key used to retrieve the localized message describing the problem | |
* @param messageArguments The list of arguments that can be used to format the localized | |
* description of the problem | |
*/ | |
protected void addProblem(Expression expression, | |
int startPosition, | |
String messageKey, | |
String... messageArguments) { | |
addProblem(expression, startPosition, startPosition, messageKey, messageArguments); | |
} | |
/** | |
* Adds a new validation problem that was found in the given {@link Expression}. The start index | |
* is the position of the given {@link Expression} within the JPQL query and the end index is | |
* the end position of the {@link Expression} within the JPQL query. | |
* | |
* @param expression The {@link Expression} that is either not following the BNF grammar or that | |
* has semantic problems | |
* @param messageKey The key used to retrieve the localized message describing the problem | |
*/ | |
protected void addProblem(Expression expression, String messageKey) { | |
addProblem(expression, messageKey, ExpressionTools.EMPTY_STRING_ARRAY); | |
} | |
/** | |
* Adds a new validation problem that was found in the given {@link Expression}. The start index | |
* is the position of the given {@link Expression} within the JPQL query and the end index is | |
* the end position of the {@link Expression} within the JPQL query. | |
* | |
* @param expression The {@link Expression} that is either not following the BNF grammar or that | |
* has semantic problems | |
* @param messageKey The key used to retrieve the localized message describing the problem | |
* @param arguments The list of arguments that can be used to format the localized description of | |
* the problem | |
*/ | |
protected void addProblem(Expression expression, String messageKey, String... arguments) { | |
int startPosition = expression.getOffset(); | |
int endPosition = startPosition + length(expression); | |
addProblem(expression, startPosition, endPosition, messageKey, arguments); | |
} | |
protected ChildrenCollectorVisitor buildChildrenCollector() { | |
return new ChildrenCollectorVisitor(); | |
} | |
/** | |
* Creates the visitor that can retrieve some information about various literal. | |
* | |
* @return A new {@link LiteralVisitor} | |
*/ | |
protected abstract LiteralVisitor buildLiteralVisitor(); | |
/** | |
* Creates the visitor that traverses an {@link Expression} and determines if it's a nested array | |
* or not. | |
* | |
* @return A new {@link NestedArrayVisitor} | |
* @since 2.5 | |
*/ | |
protected NestedArrayVisitor buildNestedArrayVisitor() { | |
return new NestedArrayVisitor(); | |
} | |
/** | |
* Creates the visitor that traverses the parent hierarchy of any {@link Expression} and stops at | |
* the first {@link Expression} that is a clause. | |
* | |
* @return A new {@link OwningClauseVisitor} | |
*/ | |
protected abstract OwningClauseVisitor buildOwningClauseVisitor(); | |
/** | |
* Creates the visitor that traverses the parent hierarchy of any {@link Expression} and stops at | |
* the first {@link Expression} that is a statement. | |
* | |
* @return A new {@link OwningStatementVisitor} | |
* @since 2.4 | |
*/ | |
protected OwningStatementVisitor buildOwningStatementVisitor() { | |
return new OwningStatementVisitor(); | |
} | |
/** | |
* Creates a new validation problem that was found in the given {@link Expression}. | |
* | |
* @param expression The {@link Expression} that is either not following the BNF grammar or that | |
* has semantic problems | |
* @param startPosition The position where the problem was encountered | |
* @param endPosition The position where the problem ends, inclusively | |
* @param messageKey The key used to retrieve the localized message describing the problem | |
* @param messageArguments The list of arguments that can be used to format the localized | |
* description of the problem | |
* @return The {@link JPQLQueryProblem} describing a problem | |
*/ | |
protected JPQLQueryProblem buildProblem(Expression expression, | |
int startPosition, | |
int endPosition, | |
String messageKey, | |
String... messageArguments) { | |
return new DefaultJPQLQueryProblem( | |
expression, | |
startPosition, | |
endPosition, | |
messageKey, | |
messageArguments | |
); | |
} | |
/** | |
* Creates the visitor that checks if the visited expression is a subquery or not.. | |
* | |
* @return A new {@link SubqueryVisitor} | |
* @since 2.5 | |
*/ | |
protected SubqueryVisitor buildSubqueryVisitor() { | |
return new SubqueryVisitor(); | |
} | |
/** | |
* Disposes this visitor. | |
*/ | |
public void dispose() { | |
problems = null; | |
} | |
protected BypassChildCollectionExpressionVisitor getBypassChildCollectionExpressionVisitor() { | |
if (bypassChildCollectionExpressionVisitor == null) { | |
bypassChildCollectionExpressionVisitor = new BypassChildCollectionExpressionVisitor(); | |
} | |
return bypassChildCollectionExpressionVisitor; | |
} | |
protected BypassParentSubExpressionVisitor getBypassParentSubExpressionVisitor() { | |
if (bypassParentSubExpressionVisitor == null) { | |
bypassParentSubExpressionVisitor = new BypassParentSubExpressionVisitor(); | |
} | |
return bypassParentSubExpressionVisitor; | |
} | |
/** | |
* Returns a list containing either the given {@link Expression} if it's not a {@link | |
* CollectionExpression} or the children of the given {@link CollectionExpression}. | |
* | |
* @param expression The {@link Expression} to visit | |
* @return A list containing either the given {@link Expression} or the children of {@link | |
* CollectionExpression} | |
*/ | |
protected List<Expression> getChildren(Expression expression) { | |
ChildrenCollectorVisitor visitor = getChildrenCollectorVisitor(); | |
try { | |
visitor.expressions = new LinkedList<Expression>(); | |
expression.accept(visitor); | |
return visitor.expressions; | |
} | |
finally { | |
visitor.expressions = null; | |
} | |
} | |
protected ChildrenCollectorVisitor getChildrenCollectorVisitor() { | |
if (childrenCollectorVisitor == null) { | |
childrenCollectorVisitor = buildChildrenCollector(); | |
} | |
return childrenCollectorVisitor; | |
} | |
/** | |
* Returns the registry containing the {@link JPQLQueryBNF JPQLQueryBNFs} and the {@link | |
* org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory ExpressionFactories} that are used | |
* to properly parse a JPQL query. | |
* | |
* @return The registry containing the information related to the JPQL grammar | |
*/ | |
protected ExpressionRegistry getExpressionRegistry() { | |
return getGrammar().getExpressionRegistry(); | |
} | |
protected JPQLQueryBNFValidator getExpressionValidator(String queryBNF) { | |
JPQLQueryBNFValidator validator = validators.get(queryBNF); | |
if (validator == null) { | |
validator = new JPQLQueryBNFValidator(getExpressionRegistry().getQueryBNF(queryBNF)); | |
validators.put(queryBNF, validator); | |
} | |
return validator; | |
} | |
/** | |
* Returns the {@link JPQLGrammar} that defines how the JPQL query was parsed. | |
* | |
* @return The {@link JPQLGrammar} that was used to parse the JPQL query | |
*/ | |
protected abstract JPQLGrammar getGrammar(); | |
/** | |
* Returns the version of the Java Persistence this entity for which it was defined. | |
* | |
* @return The version of the Java Persistence being used | |
*/ | |
protected JPAVersion getJPAVersion() { | |
return getGrammar().getJPAVersion(); | |
} | |
/** | |
* Returns the {@link JPQLQueryBNFValidator} that can be used to validate an {@link Expression} | |
* by making sure its BNF is part of the given BNF. | |
* | |
* @param queryBNF The BNF used to determine the validity of an {@link Expression} | |
* @return A {@link JPQLQueryBNFValidator} that can determine if an {@link Expression} follows | |
* the given BNF | |
*/ | |
protected JPQLQueryBNFValidator getJPQLQueryBNFValidator(JPQLQueryBNF queryBNF) { | |
JPQLQueryBNFValidator validator = validators.get(queryBNF); | |
if (validator == null) { | |
validator = new JPQLQueryBNFValidator(queryBNF); | |
validators.put(queryBNF.getId(), validator); | |
} | |
return validator; | |
} | |
/** | |
* Returns the {@link JPQLQueryBNFValidator} that can be used to validate an {@link Expression} | |
* by making sure its BNF is part of the given BNF. | |
* | |
* @param queryBNF The BNF used to determine the validity of an {@link Expression} | |
* @return A {@link JPQLQueryBNFValidator} that can determine if an {@link Expression} follows | |
* the given BNF | |
*/ | |
protected JPQLQueryBNFValidator getJPQLQueryBNFValidator(String queryBNF) { | |
return getJPQLQueryBNFValidator(getQueryBNF(queryBNF)); | |
} | |
/** | |
* Returns the visitor that can retrieve some information about various literal. | |
* | |
* @return A {@link LiteralVisitor} | |
*/ | |
protected LiteralVisitor getLiteralVisitor() { | |
if (literalVisitor == null) { | |
literalVisitor = buildLiteralVisitor(); | |
} | |
return literalVisitor; | |
} | |
/** | |
* Returns the visitor that can determine if an {@link Expression} represents a nested array. | |
* | |
* @return A {@link NestedArrayVisitor} | |
* @since 2.5 | |
*/ | |
protected NestedArrayVisitor getNestedArrayVisitor() { | |
if (nestedArrayVisitor == null) { | |
nestedArrayVisitor = buildNestedArrayVisitor(); | |
} | |
return nestedArrayVisitor; | |
} | |
/** | |
* Returns the visitor that traverses the parent hierarchy of any {@link Expression} and stops at | |
* the first {@link Expression} that is a clause. | |
* | |
* @return {@link OwningClauseVisitor} | |
*/ | |
protected OwningClauseVisitor getOwningClauseVisitor() { | |
if (owningClauseVisitor == null) { | |
owningClauseVisitor = buildOwningClauseVisitor(); | |
} | |
return owningClauseVisitor; | |
} | |
/** | |
* Returns the visitor that traverses the parent hierarchy of any {@link Expression} and stops at | |
* the first {@link Expression} that is a statement. | |
* | |
* @return {@link OwningStatementVisitor} | |
* @since 2.4 | |
*/ | |
protected OwningStatementVisitor getOwningStatementVisitor() { | |
if (owningStatementVisitor == null) { | |
owningStatementVisitor = buildOwningStatementVisitor(); | |
} | |
return owningStatementVisitor; | |
} | |
/** | |
* Returns the persistence provider name. | |
* | |
* @return The name of the persistence provider, <code>null</code> should never be returned | |
* @since 2.5 | |
*/ | |
protected String getProvider() { | |
return getGrammar().getProvider(); | |
} | |
/** | |
* Returns the version of the persistence provider. | |
* | |
* @return The version of the persistence provider, if one is extending the default JPQL grammar | |
* defined in the Java Persistence specification, otherwise returns an empty string | |
* @since 2.4 | |
*/ | |
protected String getProviderVersion() { | |
return getGrammar().getProviderVersion(); | |
} | |
/** | |
* Retrieves the BNF object that was registered for the given unique identifier. | |
* | |
* @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} to retrieve | |
* @return The {@link JPQLQueryBNF} representing a section of the grammar | |
*/ | |
protected JPQLQueryBNF getQueryBNF(String queryBNFId) { | |
return getGrammar().getExpressionRegistry().getQueryBNF(queryBNFId); | |
} | |
/** | |
* Returns the visitor that checks if the visited expression is a subquery or not. | |
* | |
* @return {@link SubqueryVisitor} | |
* @since 2.5 | |
*/ | |
protected SubqueryVisitor getSubqueryVisitor() { | |
if (subqueryVisitor == null) { | |
subqueryVisitor = buildSubqueryVisitor(); | |
} | |
return subqueryVisitor; | |
} | |
/** | |
* Initializes this validator. | |
*/ | |
protected void initialize() { | |
validators = new HashMap<String, JPQLQueryBNFValidator>(); | |
} | |
/** | |
* Determines whether the given {@link Expression} represents a nested array or not. To be a | |
* nested array, the given {@link Expression} is a {@link SubExpression} and its child is a | |
* {@link CollectionExpression}. | |
* | |
* @return <code>true</code> if the given {@link Expression} is a nested array; <code>false</code> otherwise | |
* @since 2.5 | |
*/ | |
protected boolean isNestedArray(Expression expression) { | |
return nestedArraySize(expression) > -1; | |
} | |
/** | |
* Determines whether the given {@link Expression} is a subquery. | |
* | |
* @param expression The {@link Expression} to check its type | |
* @return <code>true</code> if the given {@link Expression} is a subquery; <code>false</code> otherwise | |
* @since 2.5 | |
*/ | |
protected boolean isSubquery(Expression expression) { | |
SubqueryVisitor visitor = getSubqueryVisitor(); | |
try { | |
expression.accept(visitor); | |
return visitor.expression != null; | |
} | |
finally { | |
visitor.expression = null; | |
} | |
} | |
/** | |
* Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF} | |
* with the given {@link JPQLQueryBNF}. | |
* | |
* @param expression The {@link Expression} to validate based on the query BNF | |
* @param queryBNF The {@link JPQLQueryBNF} that determines if the given {@link Expression} is valid | |
* @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the | |
* {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise | |
*/ | |
protected boolean isValid(Expression expression, JPQLQueryBNF queryBNF) { | |
JPQLQueryBNFValidator validator = getJPQLQueryBNFValidator(queryBNF); | |
try { | |
expression.accept(validator); | |
return validator.valid; | |
} | |
finally { | |
validator.valid = false; | |
} | |
} | |
/** | |
* Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF} | |
* with the {@link JPQLQueryBNF} associated with the given unique identifier. | |
* | |
* @param expression The {@link Expression} to validate based on the query BNF | |
* @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that determines if the | |
* given {@link Expression} is valid | |
* @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the | |
* {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise | |
*/ | |
protected boolean isValid(Expression expression, String queryBNFId) { | |
return isValid(expression, getQueryBNF(queryBNFId)); | |
} | |
/** | |
* Determines whether the given {@link Expression} is valid by checking its {@link JPQLQueryBNF} | |
* with the list of {@link JPQLQueryBNF} associated with the given unique identifiers. | |
* | |
* @param expression The {@link Expression} to validate based on the query BNF | |
* @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that determines if the | |
* given {@link Expression} is valid | |
* @return <code>true</code> if the {@link Expression}'s {@link JPQLQueryBNF} is either the | |
* {@link JPQLQueryBNF} or a child of it; <code>false</code> otherwise | |
*/ | |
protected boolean isValid(Expression expression, String... queryBNFIds) { | |
for (String queryBNFId : queryBNFIds) { | |
if (isValid(expression, queryBNFId)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Determines whether the given {@link Expression} part is an expression of the given query BNF. | |
* The {@link CollectionExpression} that may be the direct child of the given {@link Expression} | |
* will be bypassed. | |
* | |
* @param expression The {@link Expression} to validate based on the query BNF | |
* @return <code>true</code> if the {@link Expression} part is a child of the given query BNF; | |
* <code>false</code> otherwise | |
*/ | |
protected boolean isValidWithChildCollectionBypass(Expression expression, String queryBNF) { | |
JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF); | |
BypassChildCollectionExpressionVisitor bypassValidator = getBypassChildCollectionExpressionVisitor(); | |
try { | |
bypassValidator.visitor = validator; | |
expression.accept(bypassValidator); | |
return validator.valid; | |
} | |
finally { | |
bypassValidator.visitor = null; | |
validator.valid = false; | |
} | |
} | |
/** | |
* Determines whether the given {@link Expression} is part of a subquery. | |
* | |
* @param expression The {@link Expression} to start scanning its location | |
* @return <code>true</code> if the given {@link Expression} is part of a subquery; <code>false</code> | |
* if it's part of the top-level query | |
* @since 2.4 | |
*/ | |
protected boolean isWithinSubquery(Expression expression) { | |
OwningStatementVisitor visitor = getOwningStatementVisitor(); | |
try { | |
expression.accept(visitor); | |
return visitor.simpleSelectStatement != null; | |
} | |
finally { | |
visitor.dispose(); | |
} | |
} | |
/** | |
* Determines whether the given {@link Expression} is part of the top-level query. | |
* | |
* @param expression The {@link Expression} to start scanning its location | |
* @return <code>true</code> if the given {@link Expression} is part of the top-level query; | |
* <code>false</code> if it's part of a subquery | |
* @since 2.4 | |
*/ | |
protected boolean isWithinTopLevelQuery(Expression expression) { | |
OwningStatementVisitor visitor = getOwningStatementVisitor(); | |
try { | |
expression.accept(visitor); | |
return visitor.deleteStatement != null || | |
visitor.selectStatement != null || | |
visitor.updateStatement != null; | |
} | |
finally { | |
visitor.dispose(); | |
} | |
} | |
/** | |
* Returns the length of the string representation of the given {@link Expression}. | |
* | |
* @param expression The {@link Expression} to retrieve the length of its string | |
* @return The length of the string representation of the given {@link Expression} | |
*/ | |
protected int length(Expression expression) { | |
return expression.getLength(); | |
} | |
/** | |
* Retrieves the "literal" from the given {@link Expression}. The literal to retrieve depends on | |
* the given {@link LiteralType type}. The literal is basically a string value like an | |
* identification variable name, an input parameter, a path expression, an abstract schema name, | |
* etc. | |
* | |
* @param expression The {@link Expression} to visit | |
* @param type The {@link LiteralType} helps to determine what to retrieve from the visited | |
* {@link Expression} | |
* @return A value from the given {@link Expression} or an empty string if the given {@link | |
* Expression} and the {@link LiteralType} do not match | |
*/ | |
protected String literal(Expression expression, LiteralType type) { | |
LiteralVisitor visitor = getLiteralVisitor(); | |
try { | |
visitor.setType(type); | |
expression.accept(visitor); | |
return visitor.literal; | |
} | |
finally { | |
visitor.literal = ExpressionTools.EMPTY_STRING; | |
} | |
} | |
/** | |
* Returns the number of items in the nested array if the given {@link Expression} represents one. | |
* To be a nested array, the given {@link Expression} is a {@link SubExpression} and its child is | |
* a {@link CollectionExpression}. | |
* | |
* @return The number of items in the array or -1 if the {@link Expression} is not a nested array | |
* @since 2.5 | |
*/ | |
protected int nestedArraySize(Expression expression) { | |
NestedArrayVisitor visitor = getNestedArrayVisitor(); | |
try { | |
visitor.nestedArraySize = -1; | |
expression.accept(visitor); | |
return visitor.nestedArraySize; | |
} | |
finally { | |
visitor.nestedArraySize = -1; | |
} | |
} | |
/** | |
* Calculates the position of the given expression by calculating the length of what is before. | |
* | |
* @param expression The expression to determine its position within the parsed tree | |
* @return The length of the string representation of what comes before the given expression | |
*/ | |
protected int position(Expression expression) { | |
return expression.getOffset(); | |
} | |
/** | |
* Returns the current number of problems that were registered during validation. | |
* | |
* @return The current number of problems | |
* @since 2.4 | |
*/ | |
public final int problemsSize() { | |
return problems.size(); | |
} | |
/** | |
* Sets the collection that will be used to store {@link JPQLQueryProblem problems} this | |
* validator will find in the JPQL query. | |
* | |
* @param problems A non-<code>null</code> collection that will be used to store the {@link | |
* JPQLQueryProblem problems} if any was found | |
* @exception NullPointerException The Collection cannot be <code>null</code> | |
*/ | |
public void setProblems(Collection<JPQLQueryProblem> problems) { | |
Assert.isNotNull(problems, "The Collection cannot be null"); | |
this.problems = problems; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void visit(Expression expression) { | |
expression.acceptChildren(this); | |
} | |
/** | |
* This visitor is responsible to traverse the children of a {@link CollectionExpression} in | |
* order to properly validate the {@link Expression}. | |
*/ | |
public static class BypassChildCollectionExpressionVisitor extends AnonymousExpressionVisitor { | |
/** | |
* The visitor that will visit the {@link Expression}. | |
*/ | |
public JPQLQueryBNFValidator visitor; | |
/** | |
* Creates a new <code>BypassChildCollectionExpressionVisitor</code>. | |
*/ | |
public BypassChildCollectionExpressionVisitor() { | |
super(); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(CollectionExpression expression) { | |
for (Expression child : expression.children()) { | |
child.accept(this); | |
if (!visitor.valid) { | |
break; | |
} | |
} | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void visit(Expression expression) { | |
expression.accept(visitor); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(NullExpression expression) { | |
// Ignore this, it should be validated by another validator | |
} | |
} | |
/** | |
* This visitor is responsible to traverse the parent hierarchy and to skip {@link SubExpression} | |
* if it's a parent. | |
*/ | |
public static class BypassParentSubExpressionVisitor extends AnonymousExpressionVisitor { | |
/** | |
* The {@link ExpressionVisitor} that will visit the {@link Expression}. | |
*/ | |
public ExpressionVisitor visitor; | |
/** | |
* Creates a new <code>BypassParentSubExpressionVisitor</code>. | |
*/ | |
public BypassParentSubExpressionVisitor() { | |
super(); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void visit(Expression expression) { | |
expression.accept(visitor); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SubExpression expression) { | |
expression.getParent().accept(this); | |
} | |
} | |
/** | |
* This visitor gathers the children of a {@link CollectionExpression} or a single visited | |
* {@link Expression}. | |
*/ | |
public static class ChildrenCollectorVisitor extends AnonymousExpressionVisitor { | |
/** | |
* The unique {@link Expression} that was visited or the children of {@link CollectionExpression}. | |
*/ | |
protected List<Expression> expressions; | |
/** | |
* Creates a new <code>ChildrenCollectorVisitor</code>. | |
*/ | |
public ChildrenCollectorVisitor() { | |
super(); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(CollectionExpression expression) { | |
CollectionTools.addAll(expressions, expression.children()); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void visit(Expression expression) { | |
expressions.add(expression); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(NullExpression expression) { | |
// Don't add it | |
} | |
} | |
/** | |
* This visitor validates any {@link Expression} by checking its BNF against some BNFs. | |
*/ | |
public static class JPQLQueryBNFValidator extends AnonymousExpressionVisitor { | |
/** | |
* | |
*/ | |
protected boolean bypassCompound; | |
/** | |
* The {@link JPQLQueryBNF} used to determine if the expression's BNF is valid. | |
*/ | |
private JPQLQueryBNF queryBNF; | |
/** | |
* Determines whether the visited {@link Expression}'s BNF is valid based on the BNF that was | |
* used for validation. | |
*/ | |
protected boolean valid; | |
/** | |
* Creates a new <code>JPQLQueryBNFValidator</code>. | |
* | |
* @param queryBNF The {@link JPQLQueryBNF} used to determine if the expression's BNF is valid | |
*/ | |
public JPQLQueryBNFValidator(JPQLQueryBNF queryBNF) { | |
super(); | |
this.queryBNF = queryBNF; | |
} | |
private void allJPQLQueryBNFs(Set<String> queryBNFIds, JPQLQueryBNF queryBNF) { | |
if (queryBNFIds.add(queryBNF.getId()) && (bypassCompound || !queryBNF.isCompound())) { | |
for (JPQLQueryBNF childQueryBNF : queryBNF.nonCompoundChildren()) { | |
allJPQLQueryBNFs(queryBNFIds, childQueryBNF); | |
} | |
} | |
} | |
/** | |
* Disposes of the internal data. | |
*/ | |
public void dispose() { | |
valid = false; | |
bypassCompound = false; | |
} | |
/** | |
* Determines whether the visited {@link Expression} is valid or not based on the {@link | |
* JPQLQueryBNF} that was specified. | |
* | |
* @return <code>true</code> if the {@link Expression} is valid; <code>false</code> otherwise | |
*/ | |
public boolean isValid() { | |
return valid; | |
} | |
/** | |
* Sets | |
* | |
* @param bypassCompound | |
*/ | |
public void setBypassCompound(boolean bypassCompound) { | |
this.bypassCompound = bypassCompound; | |
} | |
/** | |
* Validates the given {@link JPQLQueryBNF} by making sure it is the one expected or one of | |
* the children from the "root" BNF passed to this validator's constructor. | |
* | |
* @param queryBNF The {@link JPQLQueryBNF} to validate | |
*/ | |
public void validate(JPQLQueryBNF queryBNF) { | |
// By setting the flag to false will assure that if this validator is used for | |
// more than one item, it will reflect the global validity state. If all are | |
// valid, then the last expression will set the flag to true | |
valid = false; | |
// Quick check | |
if (queryBNF.getId() == this.queryBNF.getId()) { | |
valid = true; | |
} | |
// Retrieve all the children from the "root" JPQLQueryBNF and | |
// check if the BNF to validate is one of those children | |
else { | |
Set<String> allQueryBNFIds = new HashSet<String>(); | |
allJPQLQueryBNFs(allQueryBNFIds, this.queryBNF); | |
valid = allQueryBNFIds.contains(queryBNF.getId()); | |
} | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(BadExpression expression) { | |
// This is not a valid expression | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(CollectionExpression expression) { | |
// A collection expression is never valid | |
valid = false; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
protected void visit(Expression expression) { | |
validate(((AbstractExpression) expression).getQueryBNF()); | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(NullExpression expression) { | |
// The missing expression is validated by GrammarValidator | |
valid = true; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SubExpression expression) { | |
if (expression.hasExpression()) { | |
expression.getExpression().accept(this); | |
} | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(UnknownExpression expression) { | |
// This is not a valid expression | |
} | |
} | |
protected static class NestedArrayVisitor extends AbstractExpressionVisitor { | |
/** | |
* The number of items contained in the nested array or -1 if the {@link Expression} does not | |
* represent a nested array. | |
*/ | |
public int nestedArraySize; | |
/** | |
* Internal flag used to determine if a sub-expression is traversed, which is required when | |
* representing a nested array. | |
*/ | |
protected boolean subExpression; | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(CollectionExpression expression) { | |
nestedArraySize = subExpression ? expression.childrenSize() : -1; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SubExpression expression) { | |
subExpression = true; | |
expression.getExpression().accept(this); | |
subExpression = false; | |
} | |
} | |
/** | |
* This visitor retrieves the clause owning the visited {@link Expression}. | |
*/ | |
public static class OwningClauseVisitor extends AbstractTraverseParentVisitor { | |
public DeleteClause deleteClause; | |
public FromClause fromClause; | |
public GroupByClause groupByClause; | |
public HavingClause havingClause; | |
public OrderByClause orderByClause; | |
public SelectClause selectClause; | |
public SimpleFromClause simpleFromClause; | |
public SimpleSelectClause simpleSelectClause; | |
public UpdateClause updateClause; | |
public WhereClause whereClause; | |
/** | |
* Creates a new <code>OwningClauseVisitor</code>. | |
*/ | |
public OwningClauseVisitor() { | |
super(); | |
} | |
/** | |
* Disposes the internal data. | |
*/ | |
public void dispose() { | |
deleteClause = null; | |
fromClause = null; | |
groupByClause = null; | |
havingClause = null; | |
orderByClause = null; | |
selectClause = null; | |
simpleFromClause = null; | |
simpleSelectClause = null; | |
updateClause = null; | |
whereClause = null; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(DeleteClause expression) { | |
deleteClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(FromClause expression) { | |
fromClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(GroupByClause expression) { | |
groupByClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(HavingClause expression) { | |
havingClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(OrderByClause expression) { | |
orderByClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SelectClause expression) { | |
selectClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SimpleFromClause expression) { | |
simpleFromClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SimpleSelectClause expression) { | |
simpleSelectClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(UpdateClause expression) { | |
updateClause = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(WhereClause expression) { | |
whereClause = expression; | |
} | |
} | |
/** | |
* This visitor retrieves the statement owning the visited {@link Expression}. | |
*/ | |
protected static class OwningStatementVisitor extends AbstractTraverseParentVisitor { | |
public DeleteStatement deleteStatement; | |
public SelectStatement selectStatement; | |
public SimpleSelectStatement simpleSelectStatement; | |
public UpdateStatement updateStatement; | |
/** | |
* Disposes the internal data. | |
*/ | |
protected void dispose() { | |
deleteStatement = null; | |
selectStatement = null; | |
simpleSelectStatement = null; | |
updateStatement = null; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(DeleteStatement expression) { | |
deleteStatement = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SelectStatement expression) { | |
selectStatement = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SimpleSelectStatement expression) { | |
simpleSelectStatement = expression; | |
} | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(UpdateStatement expression) { | |
updateStatement = expression; | |
} | |
} | |
/** | |
* This visitor retrieves the statement owning the visited {@link Expression}. | |
*/ | |
protected static class SubqueryVisitor extends AbstractExpressionVisitor { | |
/** | |
* The subquery is the visited {@link Expression} is a subquery. | |
*/ | |
private SimpleSelectStatement expression; | |
/** | |
* {@inheritDoc} | |
*/ | |
@Override | |
public void visit(SimpleSelectStatement expression) { | |
this.expression = expression; | |
} | |
} | |
} |