blob: 7fc0657275042209f0b4ab592b72bae0b30fa0d4 [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 java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
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.ParameterTypeVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseChildrenVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.ExpressionRegistry;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Declaration;
import org.eclipse.persistence.jpa.jpql.tools.resolver.DeclarationResolver;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Resolver;
import org.eclipse.persistence.jpa.jpql.tools.resolver.ResolverBuilder;
import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedTypeProvider;
import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping;
import org.eclipse.persistence.jpa.jpql.tools.spi.IQuery;
import org.eclipse.persistence.jpa.jpql.tools.spi.IType;
import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration;
import org.eclipse.persistence.jpa.jpql.tools.spi.ITypeRepository;
/**
* This context is used to store information related to the JPQL query.
*
* <pre><code> {@link IQuery} externalQuery = ...;
*
* JPQLQueryContext context = new JPQLQueryContext(DefaultJPQLGrammar.instance());
* context.setQuery(query);</code></pre>
*
* If the JPQL query is already parsed, then the context can use it and it needs to be set before
* setting the {@link IQuery}:
* <pre><code> {@link JPQLExpression} jpqlExpression = ...;
*
* JPQLQueryContext context = new JPQLQueryContext(DefaultJPQLGrammar.instance());
* context.setJPQLExpression(jpqlExpression);
* context.setQuery(query);</code></pre>
* <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.
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public abstract class JPQLQueryContext {
/**
* This map caches the {@link JPQLQueryContext contexts} in order to keep them in memory and for
* fast access to the information of any query (top-level query and subqueries).
*/
private Map<Expression, JPQLQueryContext> contexts;
/**
* The current {@link JPQLQueryContext} is the context used for the current query or subquery.
* If the current context is not the global context, then its parent is non <code>null</code>.
*/
protected JPQLQueryContext currentContext;
/**
* The parsed {@link Expression JPQL Expression} currently visited.
*/
private Expression currentQuery;
/**
* The resolver of the current query's declaration. For a <b>SELECT</b> query, it contains the
* information defined in the <b>FROM</b> clause. For <b>DELETE</b> and <b>UPDATE</b> queries,
* it contains a single range declaration variable.
*/
private DeclarationResolver declarationResolver;
/**
* This visitor is responsible to find the {@link InputParameter} with a specific named parameter
* or positional parameter.
*/
private InputParameterVisitor inputParameterVisitor;
/**
* The parsed tree representation of the JPQL query.
*/
private JPQLExpression jpqlExpression;
/**
* The grammar that defines how to parse a JPQL query.
*/
private JPQLGrammar jpqlGrammar;
/**
* 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;
/**
* This visitor is responsible to calculate the closest type of any input parameter.
*/
private ParameterTypeVisitor parameterTypeVisitor;
/**
* When this context is a sub-context used for a subquery, then this is the context for the
* parent query.
*/
protected JPQLQueryContext parent;
/**
* The external form of the JPQL query being manipulated.
*/
private IQuery query;
/**
* This visitor is responsible to retrieve the {@link Expression} that is the beginning of a
* query. For a subquery, it will retrieve {@link SimpleSelectStatement} and for a top-level
* query, it will retrieve {@link JPQLExpression}. The search goes through the parent hierarchy.
*/
private QueryExpressionVisitor queryExpressionVisitor;
/**
* This visitor creates a {@link Resolver} that gives information about the visited {@link
* Expression}. The actual {@link Resolver} will calculate the proper {@link IType} as well.
*/
private ResolverBuilder resolverBuilder;
/**
* Determines if the parsing system should be tolerant, meaning if it should try to parse invalid
* or incomplete queries.
*/
private boolean tolerant;
/**
* Internal flag used to determine if the declaration portion of the query was visited.
*/
private boolean traversed;
/**
* Creates a new <code>JPQLQueryContext</code>.
*
* @param jpqlGrammar The {@link JPQLGrammar} defines how to parse a JPQL query
*/
public JPQLQueryContext(JPQLGrammar jpqlGrammar) {
super();
initialize(jpqlGrammar);
}
/**
* Creates a new sub-<code>JPQLQueryContext</code>.
*
* @param parent The parent context
* @param currentQuery The parsed tree representation of the subquery
*/
protected JPQLQueryContext(JPQLQueryContext parent, Expression currentQuery) {
this(parent.jpqlGrammar);
store(parent, currentQuery);
}
protected DeclarationResolver buildDeclarationResolver() {
DeclarationResolver parentResolver = (parent != null) ? parent.getDeclarationResolverImp() : null;
return buildDeclarationResolver(parentResolver);
}
protected DeclarationResolver buildDeclarationResolver(DeclarationResolver parent) {
return new DeclarationResolver(parent, this);
}
protected InputParameterVisitor buildInputParameter() {
return new InputParameterVisitor();
}
protected abstract JPQLQueryContext buildJPQLQueryContext(JPQLQueryContext currentContext,
Expression currentQuery);
protected abstract LiteralVisitor buildLiteralVisitor();
protected abstract ParameterTypeVisitor buildParameterTypeVisitor();
protected QueryExpressionVisitor buildQueryExpressionVisitor() {
return new QueryExpressionVisitor();
}
protected abstract ResolverBuilder buildResolverBuilder();
/**
* Converts the given {@link Declaration} from being set as a range variable declaration to
* a path expression declaration.
* <p>
* In this query "<code>UPDATE Employee SET firstName = 'MODIFIED' WHERE (SELECT COUNT(m) FROM
* managedEmployees m) {@literal >} 0</code>" <em>managedEmployees</em> is an unqualified
* collection-valued path expression (<code>employee.managedEmployees</code>).
*
* @param declaration The {@link Declaration} that was parsed to range over an abstract schema
* name but is actually ranging over a path expression
*/
public void convertUnqualifiedDeclaration(Declaration declaration) {
if (parent != null) {
// Retrieve the range identification variable from the parent declaration
Declaration parentDeclaration = parent.getDeclarationResolverImp().getDeclarations().get(0);
String outerVariableName = parentDeclaration.getVariableName();
// Qualify the range expression to be fully qualified
getDeclarationResolverImp().convertUnqualifiedDeclaration(declaration, outerVariableName);
}
}
/**
* Disposes the internal data.
*/
public void dispose() {
query = null;
traversed = false;
currentQuery = null;
currentContext = this;
jpqlExpression = null;
contexts.clear();
if (declarationResolver != null) {
declarationResolver.dispose();
}
}
/**
* Disposes this context, which is the current context being used by a subquery. Once it is
* disposed, any information retrieved will be for the subquery's parent query.
*/
public void disposeSubqueryContext() {
currentContext = currentContext.parent;
}
/**
* Retrieves all the {@link InputParameter InputParameters} with the given parameter name.
*
* @param parameterName The parameter used to find the {@link InputParameter InputParameters}
* with the same value
* @return Either the {@link InputParameter InputParameters} that has the given parameter or an
* empty collection
*/
public Collection<InputParameter> findInputParameters(String parameterName) {
InputParameterVisitor visitor = getInputParameterVisitor();
try {
visitor.parameterName = parameterName;
visitor.inputParameters = new ArrayList<InputParameter>();
jpqlExpression.accept(visitor);
return visitor.inputParameters;
}
finally {
visitor.parameterName = null;
visitor.inputParameters = null;
}
}
/**
* Returns the current {@link Expression} being manipulated, which is either the top-level query
* or a subquery.
*
* @return Either the top-level query or a subquery
*/
public Expression getActualCurrentQuery() {
return currentQuery;
}
/**
* Returns the {@link DeclarationResolver} of this context and not from the current query's
* declaration.
*
* @return The {@link DeclarationResolver} for this context
*/
public DeclarationResolver getActualDeclarationResolver() {
return getDeclarationResolverImp();
}
/**
* Returns the current {@link JPQLQueryContext}, i.e. the context of the query being manipulated,
* which can either be the top-level query or a subquery.
*
* @return The active context
*/
public JPQLQueryContext getCurrentContext() {
return currentContext;
}
/**
* Returns the current {@link Expression} being manipulated, which is either the top-level query
* or a subquery.
*
* @return Either the top-level query or a subquery
*/
public Expression getCurrentQuery() {
return currentContext.currentQuery;
}
/**
* Retrieves the {@link Declaration} for which the given variable name is used to navigate to the
* "root" object. This does not go up the hierarchy when looking for the {@link Declaration}.
*
* @param variableName The name of the identification variable that is used to navigate a "root" object
* @return The {@link Declaration} containing the information about the identification variable declaration
* @since 2.5
*/
public Declaration getDeclaration(String variableName) {
return getDeclarationResolver().getDeclaration(variableName);
}
/**
* Returns the {@link DeclarationResolver} of the current query's declaration. For a
* <b>SELECT</b> query, it contains the information defined in the <b>FROM</b> clause. For
* <b>DELETE</b> and <b>UPDATE</b> queries, it contains a single range declaration variable. If
* the current query is a subquery, then it contains the information defined in the
* <code>FROM</code> clause.
*
* @return The {@link DeclarationResolver} for the current query being visited
*/
public DeclarationResolver getDeclarationResolver() {
return currentContext.getDeclarationResolverImp();
}
/**
* Returns the {@link DeclarationResolver} of the current query's declaration. For a
* <b>SELECT</b> query, it contains the information defined in the <b>FROM</b> clause. For
* <b>DELETE</b> and <b>UPDATE</b> queries, it contains a single range variable declaration. If
* the current query is a subquery, then it contains the information defined in the subquery
* <code>FROM</code> clause.
*
* @param expression The {@link Expression} that will be used to retrieve its query expression,
* i.e. either {@link JPQLExpression} or {@link SimpleSelectStatement}
* @return The {@link DeclarationResolver} for the current query being visited
*/
public DeclarationResolver getDeclarationResolver(Expression expression) {
// Retrieve the expression for the query (either the top-level query or subquery)
// owning the given Expression
expression = getQueryExpression(expression);
// Retrieve the cached JPQLQueryContext
JPQLQueryContext context = contexts.get(expression);
if (context != null) {
return context.getDeclarationResolverImp();
}
// The JPQLQueryContext has not been created yet,
// create the parent JPQLQueryContexts first
getDeclarationResolver(expression.getParent());
// Create the JPQLQueryContext and DeclarationResolver for the subquery
newSubqueryContext(expression);
return currentContext.getDeclarationResolverImp();
}
/**
* Returns the {@link DeclarationResolver} of the current query's declaration.
*
* @return The {@link DeclarationResolver} for the current query being visited
*/
protected DeclarationResolver getDeclarationResolverImp() {
if (declarationResolver == null) {
declarationResolver = buildDeclarationResolver();
}
// Only traverse the declaration once
if (!traversed) {
traversed = true;
declarationResolver.populate(currentQuery);
}
return declarationResolver;
}
/**
* Returns the ordered list of {@link Declaration Declarations}.
*
* @return The {@link Declaration Declarations} of the current query that was parsed
*/
public List<Declaration> getDeclarations() {
return getDeclarationResolver().getDeclarations();
}
/**
* Returns the {@link IType} representing the possible given enum type. If the type name
*
* @param enumTypeName The fully qualified enum type with the constant
* @return The external form for the given Enum type
*/
public IType getEnumType(String enumTypeName) {
return getTypeRepository().getEnumType(enumTypeName);
}
/**
* Returns the registry containing the {@link org.eclipse.persistence.jpa.jpql.parser.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
*/
public ExpressionRegistry getExpressionRegistry() {
return jpqlGrammar.getExpressionRegistry();
}
/**
* Returns the JPQL grammar that will be used to define how to parse a JPQL query.
*
* @return The grammar that was used to parse this {@link Expression}
*/
public JPQLGrammar getGrammar() {
return jpqlGrammar;
}
protected InputParameterVisitor getInputParameterVisitor() {
if (inputParameterVisitor == null) {
inputParameterVisitor = buildInputParameter();
}
return inputParameterVisitor;
}
/**
* Returns the version of the Java Persistence to support, which dictates which version of the
* JPQL grammar to support.
*
* @return The version of the supported Java Persistence functional specification
*/
public JPAVersion getJPAVersion() {
return jpqlGrammar.getJPAVersion();
}
/**
* Returns the parsed tree representation of the JPQL query.
*
* @return The parsed tree representation of the JPQL query
*/
public JPQLExpression getJPQLExpression() {
return jpqlExpression;
}
/**
* Returns the string representation of the JPQL query.
*
* @return The string representation of the JPQL query
*/
public String getJPQLQuery() {
return query.getExpression();
}
protected LiteralVisitor getLiteralVisitor() {
if (literalVisitor == null) {
literalVisitor = buildLiteralVisitor();
}
return literalVisitor;
}
/**
* Returns the {@link IMapping} for the field represented by the given {@link Expression}.
*
* @param expression The {@link Expression} representing a state field path expression or a
* collection-valued path expression
* @return Either the {@link IMapping} or <code>null</code> if none exists
*/
public IMapping getMapping(Expression expression) {
return getResolver(expression).getMapping();
}
/**
* Retrieves, if it can be determined, the type of the given {@link InputParameter}. The type
* will be guessed based on its location within expression.
* <p>
* Note: Both named and positional input parameter can be used.
*
* @param inputParameter The {@link InputParameter} to retrieve its type
* @return Either the closest type of the input parameter or <code>null</code> if the type could
* not be determined
*/
public IType getParameterType(InputParameter inputParameter) {
ParameterTypeVisitor visitor = getParameterTypeVisitor();
try {
inputParameter.accept(visitor);
return (IType) visitor.getType();
}
finally {
visitor.dispose();
}
}
protected ParameterTypeVisitor getParameterTypeVisitor() {
if (parameterTypeVisitor == null) {
parameterTypeVisitor = buildParameterTypeVisitor();
}
return parameterTypeVisitor;
}
/**
* Returns the parent context if the current context is not the root context.
*
* @return The parent context or <code>null</code> if the current context is the root
*/
public JPQLQueryContext getParent() {
return parent;
}
/**
* Retrieves the provider of managed types.
*
* @return The object that has access to the application's managed types.
*/
public IManagedTypeProvider getProvider() {
return query.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.5
*/
public String getProviderVersion() {
return jpqlGrammar.getProviderVersion();
}
/**
* Returns the external form of the JPQL query.
*
* @return The external form of the JPQL query
*/
public IQuery getQuery() {
return query;
}
/**
* Retrieves the {@link Expression} representing the query statement (either the top-level query
* {@link JPQLExpression} or the subquery {@link SimpleSelectStatement}) owning the given {@link
* Expression}.
*
* @param expression A child of the "root" {@link Expression} to retrieve
* @return The query statement that is the "root" parent of the given {@link Expression}
*/
public Expression getQueryExpression(Expression expression) {
QueryExpressionVisitor visitor = getQueryExpressionVisitor();
try {
expression.accept(visitor);
return visitor.expression;
}
finally {
visitor.expression = null;
}
}
protected QueryExpressionVisitor getQueryExpressionVisitor() {
if (queryExpressionVisitor == null) {
queryExpressionVisitor = buildQueryExpressionVisitor();
}
return queryExpressionVisitor;
}
/**
* Creates or retrieved the cached {@link Resolver} for the given {@link Expression}. The
* {@link Resolver} can return the {@link IType} and {@link ITypeDeclaration} of the {@link
* Expression} and either the {@link org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType}
* or the {@link IMapping}.
*
* @param expression The {@link Expression} for which its {@link Resolver} will be retrieved
* @return {@link Resolver} for the given {@link Expression}
*/
public Resolver getResolver(Expression expression) {
ResolverBuilder visitor = getResolverBuilder();
try {
expression.accept(visitor);
return visitor.getResolver();
}
finally {
visitor.dispose();
}
}
/**
* Retrieves the {@link Resolver} mapped with the given identification variable. If the
* identification is not defined in the declaration traversed by this resolver, than the search
* will traverse the parent hierarchy.
*
* @param variableName The identification variable that maps a {@link Resolver}
* @return The {@link Resolver} mapped with the given identification variable
*/
public Resolver getResolver(String variableName) {
return getDeclarationResolver().getResolver(variableName);
}
protected ResolverBuilder getResolverBuilder() {
if (resolverBuilder == null) {
resolverBuilder = buildResolverBuilder();
}
return resolverBuilder;
}
/**
* Returns the variables that got defined in the select expression. This only applies to JPQL
* queries built for JPA 2.0.
*
* @return The variables identifying the select expressions, if any was defined or an empty set
* if none were defined
*/
public Set<String> getResultVariables() {
return getDeclarationResolver().getResultVariables();
}
/**
* Retrieves the external type for the given Java type.
*
* @param type The Java type to wrap with an external form
* @return The external form of the given type
*/
public IType getType(Class<?> type) {
return getTypeRepository().getType(type);
}
/**
* Returns the {@link IType} of the given {@link Expression}.
*
* @param expression The {@link Expression} for which its type will be calculated
* @return Either the {@link IType} that was resolved by this {@link Resolver} or the
* {@link IType} for {@link IType#UNRESOLVABLE_TYPE} if it could not be resolved
*/
public IType getType(Expression expression) {
return getResolver(expression).getType();
}
/**
* Retrieves the external class with the given fully qualified class name.
*
* @param typeName The fully qualified class name of the class to retrieve
* @return The external form of the class to retrieve
*/
public IType getType(String typeName) {
return getTypeRepository().getType(typeName);
}
/**
* Returns the {@link ITypeDeclaration} of the field handled by this {@link Resolver}.
*
* @param expression The {@link Expression} for which its type declaration will be calculated
* @return Either the {@link ITypeDeclaration} that was resolved by this {@link Resolver} or the
* {@link ITypeDeclaration} for {@link IType#UNRESOLVABLE_TYPE} if it could not be resolved
*/
public ITypeDeclaration getTypeDeclaration(Expression expression) {
return getResolver(expression).getTypeDeclaration();
}
/**
* Returns a helper that gives access to the most common {@link IType types}.
*
* @return A helper containing a collection of methods related to {@link IType}
*/
public TypeHelper getTypeHelper() {
return getTypeRepository().getTypeHelper();
}
/**
* Returns the type repository for the application.
*
* @return The repository of {@link IType ITypes}
*/
public ITypeRepository getTypeRepository() {
return getProvider().getTypeRepository();
}
/**
* Determines whether the JPQL expression has <b>JOIN</b> expressions.
*
* @return <code>true</code> if the query or subquery being traversed contains <b>JOIN</b>
* expressions; <code>false</code> otherwise
*/
public boolean hasJoins() {
return getDeclarationResolver().hasJoins();
}
/**
* Initializes this {@link JPQLQueryContext}.
*
* @param jpqlGrammar The grammar that defines how to parse a JPQL query
*/
protected void initialize(JPQLGrammar jpqlGrammar) {
this.tolerant = true;
this.currentContext = this;
this.jpqlGrammar = jpqlGrammar;
this.contexts = new HashMap<Expression, JPQLQueryContext>();
}
/**
* Initializes the parsed tree representation of the JPQL query if it has not been set before
* setting the {@link IQuery}.
*/
protected void initializeRoot() {
if (jpqlExpression == null) {
jpqlExpression = new JPQLExpression(query.getExpression(), jpqlGrammar, tolerant);
}
currentQuery = jpqlExpression;
contexts.put(currentQuery, this);
}
/**
* Determines whether the given identification variable is defining a join or a collection member
* declaration expressions.
*
* @param variableName The identification variable to check for what it maps
* @return <code>true</code> if the given identification variable maps a collection-valued field
* defined in a <code>JOIN</code> or <code>IN</code> expression; <code>false</code> if it's not
* defined or it's mapping an abstract schema name
*/
public boolean isCollectionIdentificationVariable(String variableName) {
return getDeclarationResolver().isCollectionIdentificationVariable(variableName);
}
/**
* Determines whether the given variable name is an identification variable name used to define
* an abstract schema name.
*
* @param variableName The name of the variable to verify if it's defined in a range variable
* declaration in the current query or any parent query
* @return <code>true</code> if the variable name is mapping an abstract schema name; <code>false</code>
* if it's defined in a collection member declaration
*/
public boolean isRangeIdentificationVariable(String variableName) {
return getDeclarationResolver().isRangeIdentificationVariable(variableName);
}
/**
* Determines if the given variable is a result variable.
*
* @param variable The variable to check if it's a result variable
* @return <code>true</code> if the given variable is defined as a result variable; <code>false</code>
* otherwise
*/
public boolean isResultVariable(String variable) {
return getDeclarationResolverImp().isResultVariable(variable);
}
/**
* Determines whether this {@link JPQLQueryContext} currently holds the information of a subquery
* or for the top-level query.
*
* @return <code>true</code> if the current context is for a subquery; <code>false</code> for the
* top-level query
* @since 2.5
*/
public boolean isSubquery() {
return currentContext.parent != null;
}
/**
* Determines if the parser is in tolerant mode or is in fast mode. When the tolerant is turned
* on, it means the parser will attempt to parse incomplete or invalid queries.
*
* @return <code>true</code> if the parsing system should parse invalid or incomplete queries;
* <code>false</code> when the query is well-formed and valid
* @since 2.5
*/
public boolean isTolerant() {
return tolerant;
}
/**
* 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
*/
public 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;
}
}
/**
* Changes the state of this context to use the given subquery.
*
* @param currentQuery The parsed tree representation of the subquery that will become the
* current query
* @see #disposeSubqueryContext()
*/
public void newSubqueryContext(Expression currentQuery) {
JPQLQueryContext context = contexts.get(currentQuery);
if (context != null) {
currentContext = context;
}
else {
currentContext = buildJPQLQueryContext(currentContext, currentQuery);
}
}
/**
* Sets the parsed tree representation of the JPQL query. If the expression was parsed outside of
* the scope of this context, then this method has to be invoked before {@link #setQuery(IQuery)}
* because the JPQL query is automatically parsed by that method.
*
* @param jpqlExpression The parsed representation of the JPQL query to manipulate
* @see #setQuery(IQuery)
*/
public void setJPQLExpression(JPQLExpression jpqlExpression) {
this.jpqlExpression = jpqlExpression;
}
/**
* Sets the external form of the JPQL query, which will be parsed and information will be
* extracted for later access.
*
* @param query The external form of the JPQL query
* @see #setJPQLExpression(JPQLExpression)
*/
public void setQuery(IQuery query) {
this.query = query;
initializeRoot();
}
/**
* Sets whether the parser is in tolerant mode or is in fast mode. When the tolerant is turned
* on, it means the parser will attempt to parse incomplete or invalid queries.
* <p>
* Note: This needs to be set before {@link #setQuery(IQuery)} or {@link #setJPQLExpression(JPQLExpression)}
* is invoked.
*
* @param tolerant Determines if the parsing system should be tolerant, meaning if it should try
* to parse invalid or incomplete queries
* @since 2.5
*/
public void setTolerant(boolean tolerant) {
this.tolerant = tolerant;
}
/**
* Stores the information contained in the given parent into this one.
*
* @param parent The parent context, which is the context of the parent query
* @param currentQuery The subquery becoming the current query
*/
protected void store(JPQLQueryContext parent, Expression currentQuery) {
this.parent = parent;
// Copy the information from the parent to this one, only a single instance is required
this.currentQuery = currentQuery;
this.query = parent.query;
this.contexts = parent.contexts;
this.jpqlExpression = parent.jpqlExpression;
this.literalVisitor = parent.literalVisitor;
this.resolverBuilder = parent.resolverBuilder;
this.parameterTypeVisitor = parent.parameterTypeVisitor;
this.queryExpressionVisitor = parent.queryExpressionVisitor;
// Cache the context
this.contexts.put(currentQuery, this);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (parent != null) {
sb.append("Subquery=");
}
else {
sb.append("Top-Level Query=");
}
sb.append(currentQuery.toParsedText());
return sb.toString();
}
/**
* This visitor is responsible to find the {@link InputParameter InputParameters} with a certain
* parameter name.
*/
protected class InputParameterVisitor extends AbstractTraverseChildrenVisitor {
/**
* The collection of {@link InputParameter InputParameters} that was retrieved by traversing the
* parsed tree.
*/
protected Collection<InputParameter> inputParameters;
/**
* The name of the input parameter to search.
*/
protected String parameterName;
@Override
public void visit(InputParameter expression) {
if (parameterName.equals(expression.getParameter())) {
inputParameters.add(expression);
}
}
@Override
public void visit(SimpleSelectStatement expression) {
newSubqueryContext(expression);
try {
super.visit(expression);
}
finally {
disposeSubqueryContext();
}
}
}
/**
* This visitor is responsible to retrieve the {@link Expression} that is the beginning of a
* query. For a subquery, it will retrieve {@link SimpleSelectStatement} and for a top-level
* query, it will retrieve {@link JPQLExpression}. The search goes through the parent hierarchy.
*/
protected static class QueryExpressionVisitor extends AbstractTraverseParentVisitor {
/**
* The {@link Expression} that is the beginning of a query.
*/
protected Expression expression;
@Override
public void visit(JPQLExpression expression) {
this.expression = expression;
}
@Override
public void visit(SimpleSelectStatement expression) {
this.expression = expression;
}
}
}