/******************************************************************************* * Copyright (c) 2006, 2015 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.getId()); 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 queryBNFIds 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; } } }