/******************************************************************************* * 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.tools; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import org.eclipse.persistence.jpa.jpql.AbstractValidator.JPQLQueryBNFValidator; import org.eclipse.persistence.jpa.jpql.Assert; import org.eclipse.persistence.jpa.jpql.ExpressionTools; import org.eclipse.persistence.jpa.jpql.JPAVersion; import org.eclipse.persistence.jpa.jpql.JPQLQueryDeclaration.Type; import org.eclipse.persistence.jpa.jpql.LiteralType; import org.eclipse.persistence.jpa.jpql.WordParser; import org.eclipse.persistence.jpa.jpql.parser.AbsExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractConditionalClause; import org.eclipse.persistence.jpa.jpql.parser.AbstractDoubleEncapsulatedExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause; import org.eclipse.persistence.jpa.jpql.parser.AbstractOrderByClause; import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName; import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectClause; import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement; import org.eclipse.persistence.jpa.jpql.parser.AbstractSingleEncapsulatedExpression; import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor; import org.eclipse.persistence.jpa.jpql.parser.AbstractTripleEncapsulatedExpression; import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression; import org.eclipse.persistence.jpa.jpql.parser.AggregateFunction; import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression; import org.eclipse.persistence.jpa.jpql.parser.AndExpression; import org.eclipse.persistence.jpa.jpql.parser.AnonymousExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpressionFactory; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor; import org.eclipse.persistence.jpa.jpql.parser.ArithmeticPrimaryBNF; import org.eclipse.persistence.jpa.jpql.parser.AvgFunction; import org.eclipse.persistence.jpa.jpql.parser.BadExpression; import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression; import org.eclipse.persistence.jpa.jpql.parser.CaseExpression; import org.eclipse.persistence.jpa.jpql.parser.CaseOperandBNF; import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration; import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression; import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpressionBNF; import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpressionFactory; import org.eclipse.persistence.jpa.jpql.parser.CompoundExpression; import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression; import org.eclipse.persistence.jpa.jpql.parser.ConditionalExpressionBNF; import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression; import org.eclipse.persistence.jpa.jpql.parser.ConstructorItemBNF; import org.eclipse.persistence.jpa.jpql.parser.CountFunction; import org.eclipse.persistence.jpa.jpql.parser.DateTime; import org.eclipse.persistence.jpa.jpql.parser.DeleteClause; import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement; import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression; import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral; import org.eclipse.persistence.jpa.jpql.parser.EntryExpression; import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression; import org.eclipse.persistence.jpa.jpql.parser.Expression; import org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory; import org.eclipse.persistence.jpa.jpql.parser.ExpressionVisitor; import org.eclipse.persistence.jpa.jpql.parser.FromClause; import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression; import org.eclipse.persistence.jpa.jpql.parser.GroupByClause; import org.eclipse.persistence.jpa.jpql.parser.GroupByItemBNF; import org.eclipse.persistence.jpa.jpql.parser.HavingClause; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable; import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration; import org.eclipse.persistence.jpa.jpql.parser.IdentifierRole; import org.eclipse.persistence.jpa.jpql.parser.InExpression; import org.eclipse.persistence.jpa.jpql.parser.InExpressionItemBNF; import org.eclipse.persistence.jpa.jpql.parser.IndexExpression; import org.eclipse.persistence.jpa.jpql.parser.InputParameter; import org.eclipse.persistence.jpa.jpql.parser.InternalBetweenExpressionBNF; import org.eclipse.persistence.jpa.jpql.parser.InternalJoinBNF; import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression; import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar; import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF; import org.eclipse.persistence.jpa.jpql.parser.Join; import org.eclipse.persistence.jpa.jpql.parser.KeyExpression; import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression; import org.eclipse.persistence.jpa.jpql.parser.LengthExpression; import org.eclipse.persistence.jpa.jpql.parser.LikeExpression; import org.eclipse.persistence.jpa.jpql.parser.LocateExpression; import org.eclipse.persistence.jpa.jpql.parser.LogicalExpression; import org.eclipse.persistence.jpa.jpql.parser.LowerExpression; import org.eclipse.persistence.jpa.jpql.parser.MaxFunction; import org.eclipse.persistence.jpa.jpql.parser.MinFunction; import org.eclipse.persistence.jpa.jpql.parser.ModExpression; import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression; import org.eclipse.persistence.jpa.jpql.parser.NewValueBNF; import org.eclipse.persistence.jpa.jpql.parser.NotExpression; import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression; import org.eclipse.persistence.jpa.jpql.parser.NullExpression; import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression; import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral; import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression; import org.eclipse.persistence.jpa.jpql.parser.OnClause; import org.eclipse.persistence.jpa.jpql.parser.OrExpression; import org.eclipse.persistence.jpa.jpql.parser.OrderByClause; import org.eclipse.persistence.jpa.jpql.parser.OrderByItem; import org.eclipse.persistence.jpa.jpql.parser.OrderByItem.Ordering; import org.eclipse.persistence.jpa.jpql.parser.OrderByItemBNF; import org.eclipse.persistence.jpa.jpql.parser.QueryPosition; import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration; import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclarationBNF; import org.eclipse.persistence.jpa.jpql.parser.ResultVariable; import org.eclipse.persistence.jpa.jpql.parser.ScalarExpressionBNF; 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.SizeExpression; import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression; import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression; import org.eclipse.persistence.jpa.jpql.parser.StringLiteral; import org.eclipse.persistence.jpa.jpql.parser.StringPrimaryBNF; import org.eclipse.persistence.jpa.jpql.parser.SubExpression; import org.eclipse.persistence.jpa.jpql.parser.SubqueryBNF; import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression; import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression; import org.eclipse.persistence.jpa.jpql.parser.SumFunction; import org.eclipse.persistence.jpa.jpql.parser.TreatExpression; import org.eclipse.persistence.jpa.jpql.parser.TrimExpression; import org.eclipse.persistence.jpa.jpql.parser.TypeExpression; import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression; import org.eclipse.persistence.jpa.jpql.parser.UpdateClause; import org.eclipse.persistence.jpa.jpql.parser.UpdateItem; import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement; import org.eclipse.persistence.jpa.jpql.parser.UpperExpression; import org.eclipse.persistence.jpa.jpql.parser.ValueExpression; import org.eclipse.persistence.jpa.jpql.parser.WhenClause; import org.eclipse.persistence.jpa.jpql.parser.WhereClause; import org.eclipse.persistence.jpa.jpql.tools.ContentAssistProposals.ClassType; import org.eclipse.persistence.jpa.jpql.tools.resolver.Declaration; import org.eclipse.persistence.jpa.jpql.tools.resolver.Resolver; import org.eclipse.persistence.jpa.jpql.tools.resolver.StateFieldResolver; import org.eclipse.persistence.jpa.jpql.tools.spi.IEntity; import org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType; import org.eclipse.persistence.jpa.jpql.tools.spi.IMapping; import org.eclipse.persistence.jpa.jpql.tools.spi.IType; import org.eclipse.persistence.jpa.jpql.tools.utility.filter.AndFilter; import org.eclipse.persistence.jpa.jpql.utility.CollectionTools; import org.eclipse.persistence.jpa.jpql.utility.filter.Filter; import org.eclipse.persistence.jpa.jpql.utility.filter.NullFilter; import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*; import static org.eclipse.persistence.jpa.jpql.parser.Expression.*; /** * The visitor provides support for finding the possible proposals within a JPQL query at a certain * position. * <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.4 * @author Pascal Filion */ @SuppressWarnings({"nls", "unused"}) // unused used for the import statement: see bug 330740 public abstract class AbstractContentAssistVisitor extends AnonymousExpressionVisitor { /** * This is used to change the position of the cursor in order to add possible proposals */ protected Stack<Integer> corrections; /** * The cached helpers that are used by this visitor to add valid content assist proposals. */ protected Map<Class<?>, Object> helpers; /** * This map contains the {@link Filter} that is used to determine when an identifier is a valid * proposal, some of them depends on the expression's type. */ protected Map<String, Filter<Expression>> identifierFilters; /** * Used to prevent and infinite recursion when one of the visit method is virtually asking a * child expression to be visited. */ protected Stack<Expression> lockedExpressions; /** * The set of possible proposals gathered based on the position in the query. */ protected DefaultContentAssistProposals proposals; /** * The context used to query information about the JPQL query. */ protected final JPQLQueryContext queryContext; /** * Contains the position of the cursor within the parsed {@link Expression} from the root node * up to the deepest leaf node. */ protected QueryPosition queryPosition; /** * A virtual space is used to move the position of the cursor by adding an extra space in order * to find some proposals within an expression. This is usually used when the trailing whitespace * is not owned by the child expression but by one of its parents. */ protected Stack<Integer> virtualSpaces; /** * The current word, which was retrieved from the JPQL query based on the position of the cursor. * The word is the partial string found to the left of the cursor and up to the cursor. */ protected String word; /** * This is used to retrieve words from the actual JPQL query. */ protected WordParser wordParser; /** * This {@link Filter} is used to say the {@link Expression} is invalid without doing anything. */ protected static final Filter<Expression> INVALID_IDENTIFIER_FILTER = new Filter<Expression>() { public boolean accept(Expression expression) { return false; } }; /** * A constant for the length of a whitespace, which is 1. */ protected static final int SPACE_LENGTH = 1; /** * This {@link Filter} is used to say the {@link Expression} is valid without doing anything. */ protected static final Filter<Expression> VALID_IDENTIFIER_FILTER = new Filter<Expression>() { public boolean accept(Expression expression) { return true; } }; /** * Creates a new <code>AbstractContentAssistVisitor</code>. * * @param queryContext The context used to query information about the JPQL query * @exception NullPointerException The {@link JPQLQueryContext} cannot be <code>null</code> */ protected AbstractContentAssistVisitor(JPQLQueryContext queryContext) { super(); Assert.isNotNull(queryContext, "The JPQLQueryContext cannot be null"); this.queryContext = queryContext; initialize(); } /** * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#AGGREGATE} * and the beginning starts with the current word. * * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks */ protected void addAggregateIdentifier(String identifier) { if (isAggregate(identifier)) { addIdentifier(identifier); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#AGGREGATE} and the beginning starts with the * current word. * * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be * added as proposals if they pass the checks */ protected void addAggregateIdentifiers(JPQLQueryBNF queryBNF) { for (String identifier : queryBNF.getIdentifiers()) { addAggregateIdentifier(identifier); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#AGGREGATE} and the beginning starts with the * current word. * * @param queryBNFId The unique of the {@link JPQLQueryBNF} for which the registered JPQL * identifiers will be added as proposals if they pass the checks */ protected void addAggregateIdentifiers(String queryBNFId) { addAggregateIdentifiers(getQueryBNF(queryBNFId)); } /** * Adds the JPQL identifiers which correspond to the arithmetic operators as valid proposals. The * word has to be an empty string. */ protected void addArithmeticIdentifiers() { if (word.length() == 0) { addExpressionFactoryIdentifiers(ArithmeticExpressionFactory.ID); } } /** * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#CLAUSE} * and the beginning starts with the current word. * * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks */ protected void addClauseIdentifier(String identifier) { if (isClause(identifier)) { addIdentifier(identifier); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#CLAUSE} and the beginning starts with the * current word. * * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be * added as proposals if they pass the checks */ protected void addClauseIdentifiers(JPQLQueryBNF queryBNF) { for (String identifier : queryBNF.getIdentifiers()) { addClauseIdentifier(identifier); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#CLAUSE} and the beginning starts with the * current word. * * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered * JPQL identifiers will be * added as proposals if they pass the checks */ protected void addClauseIdentifiers(String queryBNFId) { addClauseIdentifiers(getQueryBNF(queryBNFId)); } /** * Adds the JPQL identifiers which correspond to the comparison operators as valid proposals. The * word has to be an empty string. * * @param expression The {@link Expression} is preceding the position of the cursor and will help * to determine which of the comparison operators are valid */ protected void addComparisonIdentifiers(Expression expression) { if (word.length() == 0) { for (String identifier : getExpressionFactory(ComparisonExpressionFactory.ID).identifiers()) { Filter<Expression> filter = getFilter(identifier); if (filter.accept(expression)) { addIdentifier(identifier); } } } } /** * Adds the composite JPQL identifier by following the given rules: * * <ul> * <li>If the word is empty and the offset is -1, then use the rule used by {@link * #addIdentifier(String)};</li> * <li>Otherwise checks the ending of the JPQL query with a portion of the identifier from the * entire length of the identifier to the given offset by cutting off the trailing characters.</li> * </ul> * * @param identifier The composite JPQL identifier to add as valid proposal. The composite part * means it is an identifier composed of more than one word, example: <code><b>GROUP BY</b></code> * @param offset The smallest length of the given identifier to test against the ending of the * JPQL query based on the position of the cursor. The offset is exclusive */ protected void addCompositeIdentifier(String identifier, int offset) { // No need to check by fragmenting the composite identifier if ((word.length() == 0) && (offset == -1)) { addIdentifier(identifier); } else if (isValidVersion(identifier)) { // Make sure the offset is not -1 offset = Math.max(offset, 0); // Cut the composite identifier by removing the ending portion one character at a time // and see if the JPQL query is ending with that same text // Example: "SELECT e FROM Employee e ORDER B" and the JPQL identifier is "ORDER BY", the // test will do "ORDER B", "ORDER ", "ORDER", "ORD", "OR", "O" and if it does // match then we have to make sure there is a whitespace before to make sure it's // a complete word int position = queryPosition.getPosition(); for (int index = identifier.length(); --index > offset; ) { String fragment = identifier.substring(0, index); // The text ends with the beginning portion of the JPQL identifier, // make sure it has a whitespace before it. There is no case a non-alphanumeric // character should also be checked, like '(' or '+' if (wordParser.endsWithIgnoreCase(position, fragment)) { char character = wordParser.character(position - fragment.length() - 1); if (Character.isWhitespace(character)) { proposals.addIdentifier(identifier); break; } } } } } /** * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#COMPOUND_FUNCTION} * and the beginning starts with the current word. * * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks * @param expression The {@link Expression} represents the fragment that is before the current * word and it helps to filter out some of the compound identifiers * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word, * which would also be before the <code><b>NOT</b></code> identifier if it was also found * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word */ protected void addCompoundIdentifier(String identifier, Expression expression, boolean hasIs, boolean hasNot) { if (isCompoundFunction(identifier)) { Filter<Expression> filter = getFilter(identifier); // "IS <identifier>" (examples of possible identifiers: IS NULL, IS NOT NULL) if (hasIs && !hasNot) { if (identifier.startsWith("IS ") && filter.accept(expression)) { addCompositeIdentifier(identifier, 0); } } // "NOT <identifier>" (examples of possible identifiers: NOT EMPTY, NOT BETWEEN) else if (!hasIs && hasNot) { if (identifier.startsWith("NOT ") && filter.accept(expression)) { addCompositeIdentifier(identifier, 0); } } // "IS NOT <identifier>" (examples of possible identifiers: IS NOT NULL, IS NOT EMPTY) else if (hasIs && hasNot) { if (identifier.startsWith("IS NOT ") && filter.accept(expression)) { addCompositeIdentifier(identifier, 0); } } // "<identifier>" (examples of possible identifiers: BETWEEN, MEMBER, MEMBER OF) else if (filter.accept(expression)) { addIdentifier(identifier); } } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#COMPOUND_FUNCTION} and the beginning starts * with the current word. * * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be * added as proposals if they pass the checks * @param expression The {@link Expression} represents the fragment that is before the current * word and it helps to filter out some of the compound identifiers * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word, * which would also be before the <code><b>NOT</b></code> identifier if it was also found * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word */ protected void addCompoundIdentifiers(JPQLQueryBNF queryBNF, Expression expression, boolean hasIs, boolean hasNot) { for (String identifier : queryBNF.getIdentifiers()) { addCompoundIdentifier(identifier, expression, hasIs, hasNot); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#COMPOUND_FUNCTION} and the beginning starts * with the current word. * * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered * JPQL identifiers will be added as proposals if they pass the checks * @param expression The {@link Expression} represents the fragment that is before the current * word and it helps to filter out some of the compound identifiers * @param hasIs Flag indicating if the <code><b>IS</b></code> identifier was found before the word, * which would also be before the <code><b>NOT</b></code> identifier if it was also found * @param hasNot Flag indicating if the <code><b>NOT</b></code> identifier was found before the word */ protected void addCompoundIdentifiers(String queryBNFId, Expression expression, boolean hasIs, boolean hasNot) { addCompoundIdentifiers(getQueryBNF(queryBNFId), expression, hasIs, hasNot); } /** * Adds the entities as possible content assist proposals but will be filtered using the current word. */ protected void addEntities() { for (IEntity entity : queryContext.getProvider().entities()) { if (isValidProposal(entity.getName(), word)) { proposals.addEntity(entity); } } } /** * Adds the entities as possible content assist proposals but will be filtered using the current * word and the entity's type will have to be assignable from the given {@link IType}. * * @param type The {@link IType} used to filter the abstract schema types */ protected void addEntities(IType type) { for (IEntity entity : queryContext.getProvider().entities()) { if (isValidProposal(entity.getName(), word) && type.isAssignableTo(entity.getType())) { proposals.addEntity(entity); } } } /** * Adds the given enum constant as a valid proposal. * * @param enumType The {@link IType} of the {@link Enum} * @param enumConstant The enum constant to be added as a valid proposal */ protected void addEnumConstant(IType enumType, String enumConstant) { proposals.addEnumConstant(enumType, enumConstant); } /** * Adds the constants of the given enum type as valid proposals if the beginning starts with the * given word. * * @param enumType The {@link IType} of the enum type * @param word The word is used to filter the enum constants, which can be <code>null</code> or * an empty string */ protected void addEnumConstants(IType enumType, String word) { for (String enumConstant : enumType.getEnumConstants()) { if (ExpressionTools.startWithIgnoreCase(enumConstant, word)) { addEnumConstant(enumType, enumConstant); } } } /** * Adds the JPQL identifiers that were registered with the given {@link ExpressionFactory}. * * @param expressionFactory The factory for which its registered JPQL identifiers are added as * valid proposals, if they meet the checks */ protected void addExpressionFactoryIdentifiers(ExpressionFactory expressionFactory) { for (String identifier : expressionFactory.identifiers()) { addIdentifier(identifier); } } /** * Adds the JPQL identifiers that were registered with the {@link ExpressionFactory} with the * given unique identifier. * * @param expressionFactoryId The unique identifier of the factory for which its registered JPQL * identifiers are added as valid proposals, if they meet the checks */ protected void addExpressionFactoryIdentifiers(String expressionFactoryId) { addExpressionFactoryIdentifiers(getExpressionFactory(expressionFactoryId)); } /** * Adds the given JPQL identifier as a valid proposal if its role is {@link IdentifierRole#FUNCTION} * and the beginning starts with the current word. * * @param identifier The JPQL identifier to add as a valid proposal if it passes the checks */ protected void addFunctionIdentifier(String identifier) { if (isFunction(identifier)) { addIdentifier(identifier); } } /** * Adds the JPQL identifiers for which their role is {@link IdentifierRole#FUNCTION FUNCTION} by * determining the {@link JPQLQueryBNF} that represents the fragment for which the given {@link * Expression} was parsed. * <p> * For instance: "<code>SELECT e, AVG(e.name) FROM Employee e</code>" and the given expression is * "<code>AVG(e.name)</code>", then the BNF should be the select item BNF. * <p> * If the BNF allows for a subquery and the expression is encapsulated by parenthesis, then * <code><b>SELECT</b></code> will also be added. * <p> * The identification variables will also be added. * * @param expression The {@link Expression} is used to determine the {@link JPQLQueryBNF} to use * when retrieving the JPQL identifiers representing a function */ protected void addFunctionIdentifiers(Expression expression) { JPQLQueryBNF queryBNF = expression.getParent().findQueryBNF(expression); addIdentificationVariables(); addFunctionIdentifiers(queryBNF); if (isValid(queryBNF, SubqueryBNF.ID, true) && isEncapsulated(expression)) { addIdentifier(SELECT); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#FUNCTION} and the beginning starts with the * current word. * * @param queryBNF The {@link JPQLQueryBNF} for which the registered JPQL identifiers will be * added as proposals if they pass the checks */ protected void addFunctionIdentifiers(JPQLQueryBNF queryBNF) { for (String identifier : queryBNF.getIdentifiers()) { addFunctionIdentifier(identifier); } } /** * Adds the JPQL identifiers that are registered with the given {@link JPQLQueryBNF} as valid * proposals if their role is {@link IdentifierRole#FUNCTION} and the beginning starts with the * current word. * * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} for which the registered * JPQL identifiers will be added as proposals if they pass the checks */ protected void addFunctionIdentifiers(String queryBNFId) { addFunctionIdentifiers(getQueryBNF(queryBNFId)); } /** * Adds the given identification variable as a valid proposal. * * @param identificationVariable The identification variable to add as a proposal if it passes the checks */ protected void addIdentificationVariable(String identificationVariable) { if (ExpressionTools.stringIsNotEmpty(identificationVariable) && isValidProposal(identificationVariable, word)) { proposals.addIdentificationVariable(identificationVariable); } } /** * Adds all the identification variables defined in the current query's <b>FROM</b> clause. */ protected void addIdentificationVariables() { addIdentificationVariables(null, IdentificationVariableType.ALL); } /** * Adds the possible identification variables as valid proposals but filter them based on the * given type. * <p> * For instance, if the type is {@link IdentificationVariableType#LEFT}, then any identification * variables that have been defined before the given {@link Expression} are valid proposals, but * those defined after are not valid proposals. * * @param expression The {@link Expression} where the content assist was invoked, which helps to * determine how to stop adding identification variables * @param type Which type of identification variables to add as valid proposals */ protected void addIdentificationVariables(Expression expression, IdentificationVariableType type) { for (Declaration declaration : queryContext.getDeclarations()) { boolean stop = type.add(this, declaration, expression); if (stop) { break; } } } /** * Adds the given identifier as a proposal if it passes the checks. * * @param identifier The JPQL identifier to add as a proposal */ protected void addIdentifier(String identifier) { if (isValidVersion(identifier) && isValidProposal(identifier, word)) { proposals.addIdentifier(identifier); } } /** * Adds the join specification identifiers as proposals. */ protected void addJoinIdentifiers() { addIdentifier(INNER_JOIN); addIdentifier(INNER_JOIN_FETCH); addIdentifier(JOIN); addIdentifier(JOIN_FETCH); addIdentifier(LEFT_JOIN); addIdentifier(LEFT_JOIN_FETCH); addIdentifier(LEFT_OUTER_JOIN); addIdentifier(LEFT_OUTER_JOIN_FETCH); } /** * Adds the identification variables defined in the current query's <b>FROM</b> clause that are * declared before the given {@link Expression}. * * @param expression The {@link Expression} used to determine at which declaration to stop */ protected void addLeftIdentificationVariables(Expression expression) { addIdentificationVariables(expression, IdentificationVariableType.LEFT); } /** * Adds the logical identifiers, which are <code><b>AND</b></code> and <code><b>OR</b></code>. */ protected void addLogicalIdentifiers() { addIdentifier(AND); addIdentifier(OR); } /** * Adds the given identification variable as a proposal if it passes the checks. If an entity is * found, then it will be registered. * * @param identificationVariable An identification variable that is */ protected void addRangeIdentificationVariable(String identificationVariable) { if (ExpressionTools.stringIsNotEmpty(identificationVariable) && isValidProposal(identificationVariable, word)) { // Resolve the identification variable Resolver resolver = queryContext.getResolver(identificationVariable); IEntity entity = queryContext.getProvider().getEntity(resolver.getType()); // The identification variable identifies an entity, add the extra information if (entity != null) { proposals.addRangeIdentificationVariable(identificationVariable, entity); } else { proposals.addIdentificationVariable(identificationVariable); } } } /** * Adds the result variables defined in the <code>SELECT</code> clause as valid proposals. */ protected void addResultVariables() { for (String resultVariable : queryContext.getResultVariables()) { addIdentificationVariable(resultVariable); } } /** * Adds a virtual space on the stack. */ protected final void addVirtualSpace() { virtualSpaces.add(SPACE_LENGTH); } /** * Determines whether the given {@link Expression} can be followed by an arithmetic operator. * * @param expression The {@link Expression} that found left of the cursor, which determines if * the arithmetic operators are appendable or not * @return <code>true</code> if the operators are appendable; <code>false</code> otherwise */ protected boolean areArithmeticSymbolsAppendable(Expression expression) { return isAppendable(expression, AppendableType.ARITHMETIC); } /** * Determines whether the given {@link Expression} can be followed by a comparison operator. * * @param expression The {@link Expression} that found left of the cursor, which determines if * the comparison operators are appendable or not * @return <code>true</code> if the operators are appendable; <code>false</code> otherwise */ protected boolean areComparisonSymbolsAppendable(Expression expression) { return isAppendable(expression, AppendableType.COMPARISON); } /** * Determines whether the given {@link Expression} can be followed by a logical operator. * * @param expression The {@link Expression} that found left of the cursor, which determines if * the logical operators are appendable or not * @return <code>true</code> if the logical identifiers are appendable; <code>false</code> otherwise */ protected boolean areLogicalSymbolsAppendable(Expression expression) { return isAppendable(expression, AppendableType.LOGICAL); } protected abstract AcceptableTypeVisitor buildAcceptableTypeVisitor(); protected AppendableExpressionVisitor buildAppendableExpressionVisitor() { return new AppendableExpressionVisitor(this); } protected Filter<Expression> buildCollectionCompoundTypeFilter() { return new Filter<Expression>() { public boolean accept(Expression expression) { IType type = queryContext.getType(expression); TypeHelper typeHelper = queryContext.getTypeHelper(); return typeHelper.isCollectionType(type) || typeHelper.isMapType(type); } }; } protected CollectionExpressionVisitor buildCollectionExpressionVisitor() { return new CollectionExpressionVisitor(); } protected CollectionMappingFilter buildCollectionMappingFilter() { return new CollectionMappingFilter(); } protected ConcatExpressionCollectionHelper buildConcatExpressionCollectionHelper() { return new ConcatExpressionCollectionHelper(this); } protected ConditionalClauseCollectionHelper buildConditionalClauseCollectionHelper() { return new ConditionalClauseCollectionHelper(this); } protected ConstrutorCollectionHelper buildConstrutorCollectionHelper() { return new ConstrutorCollectionHelper(this); } protected DeclarationVisitor buildDeclarationVisitor() { return new DeclarationVisitor(); } protected DefaultMappingCollector buildDefaultMappingCollector() { return new DefaultMappingCollector(); } protected DeleteClauseCollectionHelper buildDeleteClauseCollectionHelper() { return new DeleteClauseCollectionHelper(this); } protected DeleteClauseStatementHelper buildDeleteClauseStatementHelper() { return new DeleteClauseStatementHelper(this); } protected Filter<Expression> buildDifferentComparisonFilter() { return new DifferentComparisonFilter(); } protected DoubleEncapsulatedCollectionHelper buildDoubleEncapsulatedCollectionHelper() { return new DoubleEncapsulatedCollectionHelper(this); } protected EncapsulatedExpressionVisitor buildEncapsulatedExpressionVisitor() { return new EncapsulatedExpressionVisitor(); } /** * Creates a new {@link QueryPosition} containing the corrected positions starting at the given * {@link Expression} and traversing the children at is always at the right side of the tree. * * @param invalidExpression The invalid {@link Expression} for which a new {@link QueryPosition} * will be calculated * @param startingPointExpression The {@link Expression} from which the calculation of the positions will start * @param virtualSpace Single element array that will be used to store the flag indicating if a * virtual space should be added or not * @return The new {@link QueryPosition} that contains the position of the cursor starting from * the given {@link Expression} down to the right leaf */ protected QueryPosition buildEndingPositionFromInvalidExpression(Expression invalidExpression, Expression startingPointExpression, boolean[] virtualSpace) { EndingQueryPositionBuilder visitor = getEndingQueryPositionBuilder(); try { visitor.prepare(invalidExpression); startingPointExpression.accept(visitor); virtualSpace[0] = visitor.hasVirtualSpace(); return visitor.getQueryPosition(); } finally { visitor.dispose(); } } protected EndingQueryPositionBuilder buildEndingQueryPositionBuilder() { return new EndingQueryPositionBuilder(this); } protected EnumVisitor buildEnumVisitor() { return new EnumVisitor(); } protected FilteringMappingCollector buildFilteringMappingCollector(AbstractPathExpression expression, Resolver resolver, Filter<IMapping> filter, String pattern) { return new FilteringMappingCollector( resolver, buildMappingFilter(expression, filter), pattern ); } protected FollowingClausesVisitor buildFollowingClausesVisitor() { return new FollowingClausesVisitor(); } protected FollowingInvalidExpressionVisitor buildFollowingInvalidExpressionVisitor() { return new FollowingInvalidExpressionVisitor(this); } protected FromClauseCollectionHelper buildFromClauseCollectionHelper() { return new FromClauseCollectionHelper(this); } protected FromClauseStatementHelper buildFromClauseStatementHelper() { return new FromClauseStatementHelper(this); } protected GroupByClauseCollectionHelper buildGroupByClauseCollectionHelper() { return new GroupByClauseCollectionHelper(this); } protected GroupByClauseStatementHelper buildGroupByClauseStatementHelper() { return new GroupByClauseStatementHelper(this); } protected HavingClauseStatementHelper buildHavingClauseStatementHelper() { return new HavingClauseStatementHelper(this); } protected IncompleteCollectionExpressionVisitor buildIncompleteCollectionExpressionVisitor() { return new IncompleteCollectionExpressionVisitor(); } protected InvalidExpressionVisitor buildInvalidExpressionVisitor() { return new InvalidExpressionVisitor(); } protected JoinCollectionHelper buildJoinCollectionHelper() { return new JoinCollectionHelper(this); } /** * 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 buildJPQLQueryBNFValidator(JPQLQueryBNF queryBNF) { return new JPQLQueryBNFValidator(queryBNF); } protected MappingCollector buildMappingCollector(AbstractPathExpression expression, Resolver resolver, Filter<IMapping> filter) { return buildFilteringMappingCollector( expression, resolver, filter, ExpressionTools.EMPTY_STRING ); } protected Filter<IMapping> buildMappingFilter(AbstractPathExpression expression, Filter<IMapping> filter) { // Wrap the filter with another Filter that will make sure only the // mappings with the right type will be accepted, for instance, AVG(e.| // can only accept state fields with a numeric type IType type = getAcceptableType(expression.getParent()); // No need to filter if (type == null) { return filter; } // This will filter the property mappings return new AndFilter<IMapping>(new MappingTypeFilter(type, this), filter); } protected Filter<IMapping> buildMappingFilter(Expression expression) { MappingFilterBuilder visitor = getMappingFilterBuilder(); try { expression.accept(visitor); return visitor.filter; } finally { visitor.dispose(); } } protected MappingFilterBuilder buildMappingFilterBuilder() { return new MappingFilterBuilder(this); } protected Filter<Expression> buildNonCollectionCompoundTypeFilter() { return new Filter<Expression>() { public boolean accept(Expression expression) { IType type = queryContext.getType(expression); TypeHelper typeHelper = queryContext.getTypeHelper(); return !typeHelper.isCollectionType(type) && !typeHelper.isMapType(type); } }; } protected NotExpressionVisitor buildNotExpressionVisitor() { return new NotExpressionVisitor(); } protected OrderByClauseCollectionHelper buildOrderByClauseCollectionHelper() { return new OrderByClauseCollectionHelper(this); } protected OrderByClauseStatementHelper buildOrderByClauseStatementHelper() { return new OrderByClauseStatementHelper(this); } protected PropertyMappingFilter buildPropertyMappingFilter() { return new PropertyMappingFilter(); } /** * Prepares this visitor by prepopulating it with the necessary data that is required to properly * gather the list of proposals based on the given caret position. * * @param position The position of the cursor within the JPQL query * @return The proposals that are valid choices for the given position */ public ContentAssistProposals buildProposals(int position) { return buildProposals(position, ContentAssistExtension.NULL_HELPER); } /** * Prepares this visitor by prepopulating it with the necessary data that is required to properly * gather the list of proposals based on the given caret position. * * @param position The position of the cursor within the JPQL query * @param extension This extension can be used to provide additional support to JPQL content assist * that is outside the scope of providing proposals related to JPA metadata. It adds support for * providing suggestions related to class names, enum constants, table names, column names * @return The proposals that are valid choices for the given position */ public ContentAssistProposals buildProposals(int position, ContentAssistExtension extension) { try { JPQLExpression jpqlExpression = queryContext.getJPQLExpression(); String jpqlQuery = queryContext.getJPQLQuery(); // Calculate the position of the cursor within the parsed tree queryPosition = jpqlExpression.buildPosition(jpqlQuery, position); // Retrieve the word from the JPQL query (which is the text before the position of the cursor) wordParser = new WordParser(jpqlQuery); wordParser.setPosition(position); word = wordParser.partialWord(); // Now visit the deepest leaf first and calculate the possible proposals proposals = new DefaultContentAssistProposals(queryContext.getGrammar(), extension); queryPosition.getExpression().accept(this); return proposals; } finally { dispose(); } } protected RangeVariableDeclarationVisitor buildRangeVariableDeclarationVisitor() { return new RangeVariableDeclarationVisitor(); } protected ResultVariableVisitor buildResultVariableVisitor() { return new ResultVariableVisitor(); } protected SelectClauseCollectionHelper buildSelectClauseCollectionHelper() { return new SelectClauseCollectionHelper(this); } protected SelectClauseStatementHelper buildSelectClauseStatementHelper() { return new SelectClauseStatementHelper(this); } protected SimpleFromClauseStatementHelper buildSimpleFromClauseStatementHelper() { return new SimpleFromClauseStatementHelper(this); } protected SimpleGroupByClauseStatementHelper buildSimpleGroupByClauseStatementHelper() { return new SimpleGroupByClauseStatementHelper(this); } protected SimpleHavingClauseStatementHelper buildSimpleHavingClauseStatementHelper() { return new SimpleHavingClauseStatementHelper(this); } protected SimpleSelectClauseCollectionHelper buildSimpleSelectClauseCollectionHelper() { return new SimpleSelectClauseCollectionHelper(this); } protected SimpleSelectClauseStatementHelper buildSimpleSelectClauseStatementHelper() { return new SimpleSelectClauseStatementHelper(this); } protected SimpleWhereClauseSelectStatementHelper buildSimpleWhereClauseSelectStatementHelper() { return new SimpleWhereClauseSelectStatementHelper(this); } protected SubqueryAppendableExpressionVisitor buildSubqueryAppendableExpressionVisitor() { return new SubqueryAppendableExpressionVisitor(); } protected SubqueryVisitor buildSubqueryVisitor() { return new SubqueryVisitor(); } protected TripleEncapsulatedCollectionHelper buildTripleEncapsulatedCollectionHelper() { return new TripleEncapsulatedCollectionHelper(this); } protected UpdateClauseStatementHelper buildUpdateClauseStatementHelper() { return new UpdateClauseStatementHelper(this); } protected UpdateItemCollectionHelper buildUpdateItemCollectionHelper() { return new UpdateItemCollectionHelper(this); } protected VisitParentVisitor buildVisitParentVisitor() { return new VisitParentVisitor(this); } protected WhenClauseConditionalClauseCollectionHelper buildWhenClauseConditionalClauseCollectionHelper() { return new WhenClauseConditionalClauseCollectionHelper(this); } protected WhereClauseDeleteStatementHelper buildWhereClauseDeleteStatementHelper() { return new WhereClauseDeleteStatementHelper(this); } protected WhereClauseSelectStatementHelper buildWhereClauseSelectStatementHelper() { return new WhereClauseSelectStatementHelper(this); } protected WhereClauseUpdateStatementHelper buildWhereClauseUpdateStatementHelper() { return new WhereClauseUpdateStatementHelper(this); } protected WithinInvalidExpressionVisitor buildWithinInvalidExpressionVisitor() { return new WithinInvalidExpressionVisitor(); } /** * Disposes of the internal data. */ public void dispose() { word = null; proposals = null; wordParser = null; queryPosition = null; // Not required but during debugging, this is important to be reset corrections.setSize(1); virtualSpaces.setSize(1); lockedExpressions.clear(); } /** * Retrieves the {@link RangeVariableDeclaration} that defines the entity name and identification * variable for the given {@link UpdateClause}. * * @return Either the {@link RangeVariableDeclaration} if the JPQL query defines it or <code>null</code> * if it was not defined */ protected RangeVariableDeclaration findRangeVariableDeclaration(UpdateClause expression) { RangeVariableDeclarationVisitor visitor = getRangeVariableDeclarationVisitor(); try { expression.getRangeVariableDeclaration().accept(visitor); return visitor.expression; } finally { visitor.dispose(); } } /** * Determines the root {@link IType} that any type should be assignable. If the {@link IType} is * {@link Number}, than any subclasses will be allowed. * * @param expression The {@link Expression} to visit, including its parent hierarchy until an * {@link Expression} requires a certain {@link IType} * @return The root {@link IType} allowed or <code>null</code> if anything is allowed */ protected IType getAcceptableType(Expression expression) { AcceptableTypeVisitor visitor = getExpressionTypeVisitor(); try { expression.accept(visitor); return visitor.type; } finally { visitor.dispose(); } } protected AppendableExpressionVisitor getAppendableExpressionVisitor() { AppendableExpressionVisitor helper = getHelper(AppendableExpressionVisitor.class); if (helper == null) { helper = buildAppendableExpressionVisitor(); registerHelper(AppendableExpressionVisitor.class, helper); } return helper; } /** * Casts the given {@link Expression} to a {@link CollectionExpression} if it is actually an * object of that type. * * @param expression The {@link Expression} to cast * @return The given {@link Expression} if it is a {@link CollectionExpression} or <code>null</code> * if it is any other object */ protected CollectionExpression getCollectionExpression(Expression expression) { CollectionExpressionVisitor visitor = getCollectionExpressionVisitor(); try { expression.accept(visitor); return visitor.expression; } finally { visitor.dispose(); } } /** * Returns the visitor that collects the {@link CollectionExpression} if it's been visited. * * @return The {@link CollectionExpressionVisitor} * @see #buildCollectionExpressionVisitor() */ protected CollectionExpressionVisitor getCollectionExpressionVisitor() { CollectionExpressionVisitor visitor = getHelper(CollectionExpressionVisitor.class); if (visitor == null) { visitor = buildCollectionExpressionVisitor(); registerHelper(CollectionExpressionVisitor.class, visitor); } return visitor; } protected ConcatExpressionCollectionHelper getConcatExpressionCollectionHelper() { ConcatExpressionCollectionHelper helper = getHelper(ConcatExpressionCollectionHelper.class); if (helper == null) { helper = buildConcatExpressionCollectionHelper(); registerHelper(ConcatExpressionCollectionHelper.class, helper); } return helper; } protected ConditionalClauseCollectionHelper getConditionalClauseCollectionHelper() { ConditionalClauseCollectionHelper helper = getHelper(ConditionalClauseCollectionHelper.class); if (helper == null) { helper = buildConditionalClauseCollectionHelper(); registerHelper(ConditionalClauseCollectionHelper.class, helper); } return helper; } protected ConstrutorCollectionHelper getConstructorCollectionHelper() { ConstrutorCollectionHelper helper = getHelper(ConstrutorCollectionHelper.class); if (helper == null) { helper = buildConstrutorCollectionHelper(); registerHelper(ConstrutorCollectionHelper.class, helper); } return helper; } protected DeclarationVisitor getDeclarationVisitor() { DeclarationVisitor helper = getHelper(DeclarationVisitor.class); if (helper == null) { helper = buildDeclarationVisitor(); registerHelper(DeclarationVisitor.class, helper); } return helper; } protected MappingCollector getDefaultMappingCollector() { DefaultMappingCollector helper = getHelper(DefaultMappingCollector.class); if (helper == null) { helper = buildDefaultMappingCollector(); registerHelper(DefaultMappingCollector.class, helper); } return helper; } protected DeleteClauseCollectionHelper getDeleteClauseCollectionHelper() { DeleteClauseCollectionHelper helper = getHelper(DeleteClauseCollectionHelper.class); if (helper == null) { helper = buildDeleteClauseCollectionHelper(); registerHelper(DeleteClauseCollectionHelper.class, helper); } return helper; } protected DeleteClauseStatementHelper getDeleteClauseStatementHelper() { DeleteClauseStatementHelper helper = getHelper(DeleteClauseStatementHelper.class); if (helper == null) { helper = buildDeleteClauseStatementHelper(); registerHelper(DeleteClauseStatementHelper.class, helper); } return helper; } protected DoubleEncapsulatedCollectionHelper getDoubleEncapsulatedCollectionHelper() { DoubleEncapsulatedCollectionHelper helper = getHelper(DoubleEncapsulatedCollectionHelper.class); if (helper == null) { helper = buildDoubleEncapsulatedCollectionHelper(); registerHelper(DoubleEncapsulatedCollectionHelper.class, helper); } return helper; } protected EncapsulatedExpressionVisitor getEncapsulatedExpressionVisitor() { EncapsulatedExpressionVisitor helper = getHelper(EncapsulatedExpressionVisitor.class); if (helper == null) { helper = buildEncapsulatedExpressionVisitor(); registerHelper(EncapsulatedExpressionVisitor.class, helper); } return helper; } protected EndingQueryPositionBuilder getEndingQueryPositionBuilder() { EndingQueryPositionBuilder visitor = getHelper(EndingQueryPositionBuilder.class); if (visitor == null) { visitor = buildEndingQueryPositionBuilder(); registerHelper(EndingQueryPositionBuilder.class, visitor); } return visitor; } protected EnumVisitor getEnumVisitor() { EnumVisitor helper = getHelper(EnumVisitor.class); if (helper == null) { helper = buildEnumVisitor(); helpers.put(EnumVisitor.class, helper); } return helper; } /** * Retrieves the registered {@link ExpressionFactory} that was registered for the given unique identifier. * * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory} to retrieve * @return The {@link ExpressionFactory} mapped with the given unique identifier */ protected ExpressionFactory getExpressionFactory(String expressionFactoryId) { return queryContext.getExpressionRegistry().getExpressionFactory(expressionFactoryId); } protected AcceptableTypeVisitor getExpressionTypeVisitor() { AcceptableTypeVisitor helper = getHelper(AcceptableTypeVisitor.class); if (helper == null) { helper = buildAcceptableTypeVisitor(); registerHelper(AcceptableTypeVisitor.class, helper); } return helper; } protected Filter<Expression> getFilter(String identifier) { Filter<Expression> filter = identifierFilters.get(identifier); return (filter != null) ? filter : INVALID_IDENTIFIER_FILTER; } protected FollowingClausesVisitor getFollowingClausesVisitor() { FollowingClausesVisitor helper = getHelper(FollowingClausesVisitor.class); if (helper == null) { helper = buildFollowingClausesVisitor(); helpers.put(FollowingClausesVisitor.class, helper); } return helper; } protected FollowingInvalidExpressionVisitor getFollowingInvalidExpressionVisitor() { FollowingInvalidExpressionVisitor helper = getHelper(FollowingInvalidExpressionVisitor.class); if (helper == null) { helper = buildFollowingInvalidExpressionVisitor(); registerHelper(FollowingInvalidExpressionVisitor.class, helper); } return helper; } protected FromClauseCollectionHelper getFromClauseCollectionHelper() { FromClauseCollectionHelper helper = getHelper(FromClauseCollectionHelper.class); if (helper == null) { helper = buildFromClauseCollectionHelper(); registerHelper(FromClauseCollectionHelper.class, helper); } return helper; } protected FromClauseStatementHelper getFromClauseStatementHelper() { FromClauseStatementHelper helper = getHelper(FromClauseStatementHelper.class); if (helper == null) { helper = buildFromClauseStatementHelper(); registerHelper(FromClauseStatementHelper.class, helper); } return helper; } protected GroupByClauseCollectionHelper getGroupByClauseCollectionHelper() { GroupByClauseCollectionHelper helper = getHelper(GroupByClauseCollectionHelper.class); if (helper == null) { helper = buildGroupByClauseCollectionHelper(); registerHelper(GroupByClauseCollectionHelper.class, helper); } return helper; } protected GroupByClauseStatementHelper getGroupByClauseStatementHelper() { GroupByClauseStatementHelper helper = getHelper(GroupByClauseStatementHelper.class); if (helper == null) { helper = buildGroupByClauseStatementHelper(); registerHelper(GroupByClauseStatementHelper.class, helper); } return helper; } protected HavingClauseStatementHelper getHavingClauseStatementHelper() { HavingClauseStatementHelper helper = getHelper(HavingClauseStatementHelper.class); if (helper == null) { helper = buildHavingClauseStatementHelper(); registerHelper(HavingClauseStatementHelper.class, helper); } return helper; } /** * Retrieves the helper associated with the given helper class. * * @param helperClass The Java class of the helper to retrieve * @return The helper being requested */ @SuppressWarnings("unchecked") protected final <T> T getHelper(Class<T> helperClass) { return (T) helpers.get(helperClass); } /** * Retrieves the role of the given identifier. A role helps to describe the purpose of the * identifier in a query. * * @param identifier The identifier for which its role is requested * @return The role of the given identifier */ protected IdentifierRole getIdentifierRole(String identifier) { return queryContext.getExpressionRegistry().getIdentifierRole(identifier); } protected IncompleteCollectionExpressionVisitor getIncompleteCollectionExpressionVisitor() { IncompleteCollectionExpressionVisitor helper = getHelper(IncompleteCollectionExpressionVisitor.class); if (helper == null) { helper = buildIncompleteCollectionExpressionVisitor(); registerHelper(IncompleteCollectionExpressionVisitor.class, helper); } return helper; } protected InvalidExpressionVisitor getInvalidExpressionVisitor() { InvalidExpressionVisitor visitor = getHelper(InvalidExpressionVisitor.class); if (visitor == null) { visitor = buildInvalidExpressionVisitor(); registerHelper(InvalidExpressionVisitor.class, visitor); } return visitor; } protected JoinCollectionHelper getJoinCollectionHelper() { JoinCollectionHelper helper = getHelper(JoinCollectionHelper.class); if (helper == null) { helper = buildJoinCollectionHelper(); registerHelper(JoinCollectionHelper.class, helper); } return helper; } /** * Returns the latest supported JPQL grammar, which is required when checking the validity of a * JPQL identifier. This is due to the fact that any JPQL identifiers defined in a more recent * spec will be seen to be registered in version 1.0 of the spec. By returning the latest version, * any JPQL identifiers should have a version registered. * * @return The latest {@link JPQLGrammar} supported by the provider */ protected abstract JPQLGrammar getLatestGrammar(); protected Filter<IMapping> getMappingCollectionFilter() { CollectionMappingFilter helper = getHelper(CollectionMappingFilter.class); if (helper == null) { helper = buildCollectionMappingFilter(); registerHelper(CollectionMappingFilter.class, helper); } return helper; } protected MappingFilterBuilder getMappingFilterBuilder() { MappingFilterBuilder helper = getHelper(MappingFilterBuilder.class); if (helper == null) { helper = buildMappingFilterBuilder(); helpers.put(MappingFilterBuilder.class, helper); } return helper; } protected Filter<IMapping> getMappingPropertyFilter() { PropertyMappingFilter helper = getHelper(PropertyMappingFilter.class); if (helper == null) { helper = buildPropertyMappingFilter(); helpers.put(PropertyMappingFilter.class, helper); } return helper; } protected NotExpressionVisitor getNotExpressionVisitor() { NotExpressionVisitor helper = getHelper(NotExpressionVisitor.class); if (helper == null) { helper = buildNotExpressionVisitor(); registerHelper(NotExpressionVisitor.class, helper); } return helper; } protected OrderByClauseCollectionHelper getOrderByClauseCollectionHelper() { OrderByClauseCollectionHelper helper = getHelper(OrderByClauseCollectionHelper.class); if (helper == null) { helper = buildOrderByClauseCollectionHelper(); registerHelper(OrderByClauseCollectionHelper.class, helper); } return helper; } protected OrderByClauseStatementHelper getOrderByClauseStatementHelper() { OrderByClauseStatementHelper helper = getHelper(OrderByClauseStatementHelper.class); if (helper == null) { helper = buildOrderByClauseStatementHelper(); registerHelper(OrderByClauseStatementHelper.class, helper); } return helper; } /** * Retrieves the {@link JPQLQueryBNF} 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 queryContext.getExpressionRegistry().getQueryBNF(queryBNFId); } protected RangeVariableDeclarationVisitor getRangeVariableDeclarationVisitor() { RangeVariableDeclarationVisitor helper = getHelper(RangeVariableDeclarationVisitor.class); if (helper == null) { helper = buildRangeVariableDeclarationVisitor(); registerHelper(RangeVariableDeclarationVisitor.class, helper); } return helper; } protected ResultVariableVisitor getResultVariableVisitor() { ResultVariableVisitor helper = getHelper(ResultVariableVisitor.class); if (helper == null) { helper = buildResultVariableVisitor(); registerHelper(ResultVariableVisitor.class, helper); } return helper; } protected SelectClauseCollectionHelper getSelectClauseCollectionHelper() { SelectClauseCollectionHelper helper = getHelper(SelectClauseCollectionHelper.class); if (helper == null) { helper = buildSelectClauseCollectionHelper(); registerHelper(SelectClauseCollectionHelper.class, helper); } return helper; } protected SelectClauseStatementHelper getSelectClauseStatementHelper() { SelectClauseStatementHelper helper = getHelper(SelectClauseStatementHelper.class); if (helper == null) { helper = buildSelectClauseStatementHelper(); registerHelper(SelectClauseStatementHelper.class, helper); } return helper; } protected SimpleFromClauseStatementHelper getSimpleFromClauseStatementHelper() { SimpleFromClauseStatementHelper helper = getHelper(SimpleFromClauseStatementHelper.class); if (helper == null) { helper = buildSimpleFromClauseStatementHelper(); registerHelper(SimpleFromClauseStatementHelper.class, helper); } return helper; } protected SimpleGroupByClauseStatementHelper getSimpleGroupByClauseStatementHelper() { SimpleGroupByClauseStatementHelper helper = getHelper(SimpleGroupByClauseStatementHelper.class); if (helper == null) { helper = buildSimpleGroupByClauseStatementHelper(); registerHelper(SimpleGroupByClauseStatementHelper.class, helper); } return helper; } protected SimpleHavingClauseStatementHelper getSimpleHavingClauseStatementHelper() { SimpleHavingClauseStatementHelper helper = getHelper(SimpleHavingClauseStatementHelper.class); if (helper == null) { helper = buildSimpleHavingClauseStatementHelper(); registerHelper(SimpleHavingClauseStatementHelper.class, helper); } return helper; } protected SimpleSelectClauseCollectionHelper getSimpleSelectClauseCollectionHelper() { SimpleSelectClauseCollectionHelper helper = getHelper(SimpleSelectClauseCollectionHelper.class); if (helper == null) { helper = buildSimpleSelectClauseCollectionHelper(); registerHelper(SimpleSelectClauseCollectionHelper.class, helper); } return helper; } protected SimpleSelectClauseStatementHelper getSimpleSelectClauseStatementHelper() { SimpleSelectClauseStatementHelper helper = getHelper(SimpleSelectClauseStatementHelper.class); if (helper == null) { helper = buildSimpleSelectClauseStatementHelper(); registerHelper(SimpleSelectClauseStatementHelper.class, helper); } return helper; } protected SimpleWhereClauseSelectStatementHelper getSimpleWhereClauseSelectStatementHelper() { SimpleWhereClauseSelectStatementHelper helper = getHelper(SimpleWhereClauseSelectStatementHelper.class); if (helper == null) { helper = buildSimpleWhereClauseSelectStatementHelper(); registerHelper(SimpleWhereClauseSelectStatementHelper.class, helper); } return helper; } protected SubqueryAppendableExpressionVisitor getSubqueryAppendableExpressionVisitor() { SubqueryAppendableExpressionVisitor helper = getHelper(SubqueryAppendableExpressionVisitor.class); if (helper == null) { helper = buildSubqueryAppendableExpressionVisitor(); registerHelper(SubqueryAppendableExpressionVisitor.class, helper); } return helper; } protected SubqueryVisitor getSubqueryVisitor() { SubqueryVisitor helper = getHelper(SubqueryVisitor.class); if (helper == null) { helper = buildSubqueryVisitor(); registerHelper(SubqueryVisitor.class, helper); } return helper; } protected TripleEncapsulatedCollectionHelper getTripleEncapsulatedCollectionHelper() { TripleEncapsulatedCollectionHelper helper = getHelper(TripleEncapsulatedCollectionHelper.class); if (helper == null) { helper = buildTripleEncapsulatedCollectionHelper(); registerHelper(TripleEncapsulatedCollectionHelper.class, helper); } return helper; } protected UpdateClauseStatementHelper getUpdateClauseStatementHelper() { UpdateClauseStatementHelper helper = getHelper(UpdateClauseStatementHelper.class); if (helper == null) { helper = buildUpdateClauseStatementHelper(); registerHelper(UpdateClauseStatementHelper.class, helper); } return helper; } protected UpdateItemCollectionHelper getUpdateItemCollectionHelper() { UpdateItemCollectionHelper helper = getHelper(UpdateItemCollectionHelper.class); if (helper == null) { helper = buildUpdateItemCollectionHelper(); registerHelper(UpdateItemCollectionHelper.class, helper); } return helper; } protected VisitParentVisitor getVisitParentVisitor() { VisitParentVisitor helper = getHelper(VisitParentVisitor.class); if (helper == null) { helper = buildVisitParentVisitor(); registerHelper(VisitParentVisitor.class, helper); } return helper; } protected WhenClauseConditionalClauseCollectionHelper getWhenClauseConditionalClauseCollectionHelper() { WhenClauseConditionalClauseCollectionHelper helper = getHelper(WhenClauseConditionalClauseCollectionHelper.class); if (helper == null) { helper = buildWhenClauseConditionalClauseCollectionHelper(); registerHelper(WhenClauseConditionalClauseCollectionHelper.class, helper); } return helper; } protected WhereClauseDeleteStatementHelper getWhereClauseDeleteStatementHelper() { WhereClauseDeleteStatementHelper helper = getHelper(WhereClauseDeleteStatementHelper.class); if (helper == null) { helper = buildWhereClauseDeleteStatementHelper(); registerHelper(WhereClauseDeleteStatementHelper.class, helper); } return helper; } protected WhereClauseSelectStatementHelper getWhereClauseSelectStatementHelper() { WhereClauseSelectStatementHelper helper = getHelper(WhereClauseSelectStatementHelper.class); if (helper == null) { helper = buildWhereClauseSelectStatementHelper(); registerHelper(WhereClauseSelectStatementHelper.class, helper); } return helper; } protected WhereClauseUpdateStatementHelper getWhereClauseUpdateStatementHelper() { WhereClauseUpdateStatementHelper helper = getHelper(WhereClauseUpdateStatementHelper.class); if (helper == null) { helper = buildWhereClauseUpdateStatementHelper(); registerHelper(WhereClauseUpdateStatementHelper.class, helper); } return helper; } protected WithinInvalidExpressionVisitor getWithinInvalidExpressionVisitor() { WithinInvalidExpressionVisitor helper = getHelper(WithinInvalidExpressionVisitor.class); if (helper == null) { helper = buildWithinInvalidExpressionVisitor(); registerHelper(WithinInvalidExpressionVisitor.class, helper); } return helper; } protected boolean hasClausesDefinedBetween(Expression expression, String afterIdentifier, String beforeIdentifier) { FollowingClausesVisitor visitor = getFollowingClausesVisitor(); try { visitor.afterIdentifier = afterIdentifier; visitor.beforeIdentifier = beforeIdentifier; expression.accept(visitor); return visitor.hasFollowUpClauses; } finally { visitor.dispose(); } } /** * Determines whether there is a virtual space to be considered or not. * * @return <code>true</code> if there is a virtual space to count as a real one or not */ protected final boolean hasVirtualSpace() { return virtualSpaces.peek() > 0; } /** * Initializes this visitor. */ protected void initialize() { helpers = new HashMap<Class<?>, Object>(); lockedExpressions = new Stack<Expression>(); virtualSpaces = new Stack<Integer>(); virtualSpaces.add(0); corrections = new Stack<Integer>(); corrections.add(0); identifierFilters = new HashMap<String, Filter<Expression>>(); identifierFilters.put(DIFFERENT, VALID_IDENTIFIER_FILTER); identifierFilters.put(EQUAL, VALID_IDENTIFIER_FILTER); identifierFilters.put(MEMBER, VALID_IDENTIFIER_FILTER); identifierFilters.put(MEMBER_OF, VALID_IDENTIFIER_FILTER); identifierFilters.put(NOT_MEMBER, VALID_IDENTIFIER_FILTER); identifierFilters.put(NOT_MEMBER_OF, VALID_IDENTIFIER_FILTER); identifierFilters.put(IS_NULL, VALID_IDENTIFIER_FILTER); identifierFilters.put(IS_NOT_NULL, VALID_IDENTIFIER_FILTER); identifierFilters.put(IN, VALID_IDENTIFIER_FILTER); identifierFilters.put(NOT_IN, VALID_IDENTIFIER_FILTER); // 'EMPTY', 'NOT EMPTY' Filter<Expression> filter = buildCollectionCompoundTypeFilter(); identifierFilters.put(IS_EMPTY, filter); identifierFilters.put(IS_NOT_EMPTY, filter); // 'BETWEEN', 'NOT BETWEEN' filter = buildNonCollectionCompoundTypeFilter(); identifierFilters.put(BETWEEN, filter); identifierFilters.put(NOT_BETWEEN, filter); identifierFilters.put(LIKE, filter); identifierFilters.put(NOT_LIKE, filter); // '<', '<=', '>=', '>' filter = buildDifferentComparisonFilter(); identifierFilters.put(GREATER_THAN, filter); identifierFilters.put(GREATER_THAN_OR_EQUAL, filter); identifierFilters.put(LOWER_THAN, filter); identifierFilters.put(LOWER_THAN_OR_EQUAL, filter); } /** * Determines whether the given JPQL identifier used in an aggregate expression; for instance * <b>AND</b>. * * @param identifier The identifier to validate * @return <code>true</code> if the given identifier is used in an aggregate expression; * <code>false</code> otherwise */ protected boolean isAggregate(String identifier) { return getIdentifierRole(identifier) == IdentifierRole.AGGREGATE; } /** * Determines whether a certain type of JPQL identifiers can be appended to the JPQL query based * on the given {@link Expression}. * * @param expression The {@link Expression} that determines what can be appended * @param appendableType The type of identifiers to append to the JPQL query * @return <code>true</code> if the JPQL identifiers with the given type can be appended to the * JPQL query; <code>false</code> if they cannot */ protected boolean isAppendable(Expression expression, AppendableType appendableType) { AppendableExpressionVisitor visitor = getAppendableExpressionVisitor(); try { visitor.appendableType = appendableType; expression.accept(visitor); return visitor.isAppendable(); } finally { visitor.dispose(); } } /** * Determines whether * * @param expression * @return */ protected boolean isAppendableToCollection(Expression expression) { IncompleteCollectionExpressionVisitor visitor = getIncompleteCollectionExpressionVisitor(); try { expression.accept(visitor); return visitor.isComplete(); } finally { visitor.dispose(); } } /** * Determines whether the given JPQL identifier used in a clause; for instance <b>SELECT</b>. * * @param identifier The identifier to validate * @return <code>true</code> if the given identifier is a clause; <code>false</code> otherwise */ protected boolean isClause(String identifier) { return getIdentifierRole(identifier) == IdentifierRole.CLAUSE; } /** * Determines whether the identifiers identifying clauses can be appended to the JPQL query based * on the given {@link Expression}. * * @param expression The {@link Expression} that determines what can be appended * @return <code>true</code> if the following clauses can be appended to the JPQL query; * <code>false</code> if they cannot */ protected boolean isClauseAppendable(Expression expression) { return isAppendable(expression, AppendableType.CLAUSE); } /** * Determines whether the given {@link Expression} is grammatically complete. * * @param expression The {@link Expression} to verify its grammar integrity * @return <code>true</code> if the {@link Expression} is grammatically complete; * <code>false</code> otherwise */ protected boolean isComplete(Expression expression) { return isAppendable(expression, AppendableType.COMPLETE); } /** * Determines whether the given {@link Expression} can be used as the left side of a compound * expression. * * @param expression The {@link Expression} that determines if the JPQL identifiers with * {@link IdentifierRole#COMPOUND_FUNCTION} can be used to create a compound expression * @return <code>true</code> if the compound identifiers can be appended to the JPQL query; * <code>false</code> if they cannot */ protected boolean isCompoundable(Expression expression) { return isAppendable(expression, AppendableType.COMPOUNDABLE); } /** * Determines whether the given JPQL identifier used in a compound expression; an example would * be <b>BETWEEN</b> or <b>MEMBER</b>. * * @param identifier The identifier to validate * @return <code>true</code> if the given identifier is used in a compound expression; * <code>false</code> otherwise */ protected boolean isCompoundFunction(String identifier) { // Only the full JPQL identifier is valid if (identifier == IS || identifier == OF) { return false; } return getIdentifierRole(identifier) == IdentifierRole.COMPOUND_FUNCTION; } /** * Determines whether the given {@link AbstractPathExpression} is found within a declaration expression. * * @param expression The {@link AbstractPathExpression} to visit * @return <code>true</code> if the visited {@link CollectionValuedPathExpression} is owned by * a {@link RangeVariableDeclaration}, which indicates it is used to define the "root" object; * <code>false</code> if it is not */ protected boolean isDeclaration(AbstractPathExpression expression) { DeclarationVisitor visitor = getDeclarationVisitor(); try { expression.accept(visitor); return visitor.isDeclaration(); } finally { visitor.dispose(); } } /** * Determines whether the given {@link Expression} is being encapsulated or not. * * @param expression The {@link Expression} to scan for encapsulation * @return <code>true</code> if the given {@link Expression} is within parenthesis; * <code>false</code> otherwise */ protected boolean isEncapsulated(Expression expression) { EncapsulatedExpressionVisitor visitor = getEncapsulatedExpressionVisitor(); try { expression.accept(visitor); return visitor.isEncapsulated(); } finally { visitor.dispose(); } } /** * Determines whether the given {@link AbstractPathExpression} could potentially represent a * fully qualified enum constant, which is dictated by the location of the path expression within * the query. Only a few location allows an enum constant. * * @param expression The {@link AbstractPathExpression} to visit * @return <code>true</code> if the path expression represents a enum constant; * <code>false</code> otherwise */ protected boolean isEnumAllowed(AbstractPathExpression expression) { EnumVisitor visitor = getEnumVisitor(); try { visitor.pathExpression = expression; expression.accept(visitor); return visitor.isValid(); } finally { visitor.dispose(); } } /** * Determines whether the given {@link Expression} is preceded by an invalid expression. * * @param expression The {@link Expression} to visit * @return <code>true</code> if the visited {@link Expression} is part of a collection of * expressions and an invalid expression precede it; <code>false</code> otherwise */ protected boolean isFollowingInvalidExpression(Expression expression) { FollowingInvalidExpressionVisitor visitor = getFollowingInvalidExpressionVisitor(); try { visitor.expression = expression; expression.accept(visitor); return visitor.isFollowingInvalidExpression(); } finally { visitor.dispose(); } } /** * Determines whether the given JPQL identifier is a function, an example would be <b>AVG</b>. * * @param identifier The identifier to validate * @return <code>true</code> if the given identifier is a function; <code>false</code> otherwise */ protected boolean isFunction(String identifier) { return getIdentifierRole(identifier) == IdentifierRole.FUNCTION; } /** * Determines whether the given {@link Expression} is in a subquery or in the top-level query. * * @param expression The {@link Expression} to visit its parent hierarchy * @return <code>true</code> if the owning query is a subquery; <code>false</code> if it's the * top-level query */ protected boolean isInSubquery(Expression expression) { SubqueryVisitor visitor = getSubqueryVisitor(); try { expression.accept(visitor); return visitor.isInSubquery(); } finally { visitor.dispose(); } } /** * Determines whether the given {@link Expression} represents an invalid fragment. * * @param expression The {@link Expression} to visit * @return <code>true</code> if the {@link Expression} is an invalid fragment; * <code>false</code> otherwise */ protected boolean isInvalidExpression(Expression expression) { InvalidExpressionVisitor visitor = getInvalidExpressionVisitor(); try { expression.accept(visitor); return visitor.isInvalid(); } finally { visitor.dispose(); } } /** * Determines whether a <code><b>JOIN FETCH</b></code> expression can be identified by with an * identification variable or not. * * @return <code>true</code> if the expression can have an identification variable; * <code>false</code> otherwise */ protected abstract boolean isJoinFetchIdentifiable(); /** * Determines whether the given {@link Expression} has been set has the lock to prevent an * infinite recursion. * * @param expression The {@link Expression} to check if it is locked * @return <code>true</code> if the given {@link Expression} has been marked as locked; * <code>false</code> otherwise */ protected boolean isLocked(Expression expression) { return !lockedExpressions.empty() && (lockedExpressions.peek() == expression); } /** * Determines whether the given {@link Expression} represents the negated expression. * * @param expression The {@link Expression} to visit * @return <code>true</code> if the {@link Expression} is {@link NotExpression}; * <code>false</code> otherwise */ protected boolean isNotExpression(Expression expression) { NotExpressionVisitor visitor = getNotExpressionVisitor(); try { expression.accept(visitor); return visitor.isNotExpression(); } finally { visitor.dispose(); } } /** * Determines whether the given position is within the given word. * <p> * Example: position=0, word="JPQL" {@literal =>} true * Example: position=1, word="JPQL" {@literal =>} true * Example: position=4, word="JPQL" {@literal =>} true * Example: position=5, word="JPQL" {@literal =>} true * Example: position=5, offset 2, (actual cursor position is 3), word="JPQL" {@literal =>} true * * @param position The position of the cursor * @param offset The offset to adjust the position * @param word The word to check if the cursor is positioned in it * @return <code>true</code> if the given position is within the given word; * <code>false</code> otherwise */ protected boolean isPositionWithin(int position, int offset, String word) { return (position >= offset) && (position - offset <= word.length()); } /** * Determines whether the given position is within the given word. * <p> * Example: position=0, word="JPQL" {@literal =>} true * Example: position=1, word="JPQL" {@literal =>} true * Example: position=4, word="JPQL" {@literal =>} true * Example: position=5, word="JPQL" {@literal =>} true * * @param position The position of the cursor * @param word The word to check if the cursor is positioned in it * @return <code>true</code> if the given position is within the given word; * <code>false</code> otherwise */ protected boolean isPositionWithin(int position, String word) { return isPositionWithin(position, 0, word); } /** * Determines whether the JPQL identifier starting a subquery (<code><b>SELECT</b></code>) can * be appended based on the given {@link Expression} which is preceding the position of the cursor. * * @param expression The {@link Expression} that precedes the position of the cursor * @return <code>true</code> if a subquery can be appended; <code>false</code> otherwise */ protected boolean isSubqueryAppendable(Expression expression) { AbstractAppendableExpressionVisitor visitor = getSubqueryAppendableExpressionVisitor(); try { expression.accept(visitor); return visitor.isAppendable(); } finally { visitor.dispose(); } } /** * Determines whether the given {@link Expression} part is an expression of the given query BNF. * * @param expression The {@link Expression} to validate based on the query BNF * @param queryBNF * @return <code>true</code> if the {@link Expression} part is a child of the given query BNF; * <code>false</code> otherwise */ protected boolean isValid(Expression expression, JPQLQueryBNF queryBNF) { JPQLQueryBNFValidator validator = buildJPQLQueryBNFValidator(queryBNF); try { expression.accept(validator); return validator.isValid(); } finally { validator.dispose(); } } /** * Determines whether the given {@link Expression} part is an expression of the given query BNF. * * @param expression The {@link Expression} to validate based on the query BNF * @param queryBNFId * @return <code>true</code> if the {@link Expression} part is a child of the given query BNF; * <code>false</code> otherwise */ protected boolean isValid(Expression expression, String queryBNFId) { return isValid(expression, getQueryBNF(queryBNFId)); } /** * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}. * * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant * @param queryBNF The BNF to check if it is a descendant of the parent * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent * {@link JPQLQueryBNF}; <code>false</code> otherwise */ protected boolean isValid(JPQLQueryBNF parentQueryBNF, JPQLQueryBNF queryBNF) { return isValid(parentQueryBNF, queryBNF, false); } /** * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}. * * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant * @param queryBNF The BNF to check if it is a descendant of the parent * @param bypassCompound Indicates whether a {@link JPQLQueryBNF} representing a compound * expression should be considered when doing the validation * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent * {@link JPQLQueryBNF}; <code>false</code> otherwise */ protected boolean isValid(JPQLQueryBNF parentQueryBNF, JPQLQueryBNF queryBNF, boolean bypassCompound) { JPQLQueryBNFValidator validator = buildJPQLQueryBNFValidator(parentQueryBNF); try { validator.setBypassCompound(bypassCompound); validator.validate(queryBNF); return validator.isValid(); } finally { validator.dispose(); } } /** * Determines whether the given {@link JPQLQueryBNF} part is the given parent {@link JPQLQueryBNF}. * * @param parentQueryBNF The "root" of the BNF used to determine if the other is a descendant * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} to check if it is a * descendant of the parent * @param bypassCompound Indicates whether a {@link JPQLQueryBNF} representing a compound * expression should be considered when doing the validation * @return <code>true</code> if the {@link JPQLQueryBNF} is a descendant of the given parent * {@link JPQLQueryBNF}; <code>false</code> otherwise */ protected boolean isValid(JPQLQueryBNF parentQueryBNF, String queryBNFId, boolean bypassCompound) { return isValid(parentQueryBNF, getQueryBNF(queryBNFId), bypassCompound); } /** * Determines whether the given proposal is a valid, which is based on the content of the given * word. If the word is not an empty string, the proposal must start with the content of the word. * * @param proposal The proposal to validate * @param word The word, which is what was parsed before the position of the cursor * @return <code>true</code> if the proposal is valid; <code>false</code> otherwise */ protected boolean isValidProposal(String proposal, String word) { // There is no word to match the first letters if (word.length() == 0) { return true; } char character = word.charAt(0); if (character == '+' || character == '-' || character == '*' || character == '/') { return true; } // The word is longer than the proposal if (word.length() > proposal.length()) { return false; } // Check to see if the proposal starts with the word for (int index = 0, length = word.length(); index < length; index++) { char character1 = proposal.charAt(index); char character2 = word .charAt(index); // If characters don't match but case may be ignored, try converting // both characters to uppercase. If the results match, then the // comparison scan should continue char upperCase1 = Character.toUpperCase(character1); char upperCase2 = Character.toUpperCase(character2); if (upperCase1 != upperCase2) { return false; } // Unfortunately, conversion to uppercase does not work properly for // the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before exiting if (Character.toLowerCase(upperCase1) != Character.toLowerCase(upperCase2)) { return false; } } return true; } /** * Determines whether the given JPQL identifier can be a valid proposal, i.e. if it's part of the * grammar of the JPA version that was used to parse the JPQL query. * * @param identifier The JPQL identifier to validate * @return <code>true</code> if the given identifier is part of the current JPA version or was * defined in previous release; <code>false</code> otherwise */ protected boolean isValidVersion(String identifier) { JPAVersion identifierVersion = getLatestGrammar().getExpressionRegistry().getIdentifierVersion(identifier); return queryContext.getJPAVersion().isNewerThanOrEqual(identifierVersion); } /** * Determines whether the given {@link Expression} is part of an invalid fragment * * @param expression The {@link Expression} to verify its location within the JPQL query * @return <code>true</code> if the given {@link Expression} is within an invalid fragment; * <code>false</code> if it is not */ protected boolean isWithinInvalidExpression(Expression expression) { WithinInvalidExpressionVisitor validator = getWithinInvalidExpressionVisitor(); try { expression.accept(validator); return validator.isWithinInvalidExpression(); } finally { validator.dispose(); } } /** * Registers the given helper associated with the given helper class. * * @param helperClass The Java class of the helper to retrieve * @param helper The helper being registered * @see #getHelper(Class) */ protected final <T> void registerHelper(Class<T> helperClass, T helper) { helpers.put(helperClass, helper); } /** * Removes the last virtual space from the stack. */ protected final void removeVirtualSpace() { virtualSpaces.pop(); } /** * {@inheritDoc} */ @Override public String toString() { return proposals.toString(); } /** * {@inheritDoc} */ @Override public void visit(AbsExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { // First visit with the adjustment corrections.add(queryPosition.getPosition(expression)); super.visit(expression); corrections.pop(); // Now visit without the adjustment super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(AdditionExpression expression) { super.visit(expression); visitArithmeticExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(AllOrAnyExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, ALL, ANY, SOME); } /** * {@inheritDoc} */ @Override public void visit(AndExpression expression) { super.visit(expression); visitLogicalExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(ArithmeticFactor expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 1; if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(ArithmeticPrimaryBNF.ID); } else if (expression.hasSpaceAfterArithmeticOperator() || hasVirtualSpace()) { length++; if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(ArithmeticPrimaryBNF.ID); } } } /** * {@inheritDoc} */ @Override public void visit(AvgFunction expression) { super.visit(expression); visitAggregateFunction(expression); } /** * {@inheritDoc} */ @Override public void visit(BadExpression expression) { super.visit(expression); visitInvalidExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(BetweenExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasExpression()) { length += expression.getExpression().getLength() + SPACE_LENGTH; } // Within "NOT BETWEEN" or "BETWEEN" if (expression.hasNot() && isPositionWithin(position, length, NOT_BETWEEN) || !expression.hasNot() && isPositionWithin(position, length, BETWEEN)) { proposals.addIdentifier(BETWEEN); proposals.addIdentifier(NOT_BETWEEN); } // After "BETWEEN " else if (expression.hasSpaceAfterBetween()) { length += expression.getIdentifier().length() + SPACE_LENGTH; // TODO: Check for the BETWEEN's expression type // Right after "BETWEEN " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(expression.getBoundExpressionQueryBNFId()); } // After lower bound if (expression.hasLowerBoundExpression()) { // Check for something like "<lower bound> <word>" int lowerBoundLength = expression.getLowerBoundExpression().getLength(); if (!expression.hasAnd() && (position > length) && (position < length + lowerBoundLength) && isAppendableToCollection(expression.getLowerBoundExpression())) { addIdentifier(AND); } length += lowerBoundLength; if (expression.hasSpaceAfterLowerBound()) { length++; // Right before "AND" if (position == length) { proposals.addIdentifier(AND); } else { // Within "AND" if (expression.hasAnd() && isPositionWithin(position, length, AND)) { proposals.addIdentifier(AND); } // After "AND " else if (expression.hasSpaceAfterAnd()) { length += AND.length() + SPACE_LENGTH; // TODO: Check for the BETWEEN's expression type if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(InternalBetweenExpressionBNF.ID); } } else if (!expression.hasAnd() && expression.hasUpperBoundExpression()) { length += expression.getUpperBoundExpression().getLength(); if (position == length) { addIdentifier(AND); } } } } } } } /** * {@inheritDoc} */ @Override public void visit(CaseExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "CASE" if (isPositionWithin(position, CASE)) { addIdentifier(CASE); addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // After "CASE " else if (expression.hasSpaceAfterCase()) { int length = CASE.length() + SPACE_LENGTH; // Right after "CASE " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(CaseOperandBNF.ID); proposals.addIdentifier(WHEN); } // After "<case operand> " if (expression.hasCaseOperand() && expression.hasSpaceAfterCaseOperand()) { length += expression.getCaseOperand().getLength() + SPACE_LENGTH; // Right after "<case operand> " if (position == length) { proposals.addIdentifier(WHEN); } } // After "<when clauses> " if (expression.hasWhenClauses() && expression.hasSpaceAfterWhenClauses()) { length += expression.getWhenClauses().getLength() + SPACE_LENGTH; // Right after "<when clauses> " if (isPositionWithin(position, length, ELSE)) { proposals.addIdentifier(ELSE); } // After "ELSE " if (expression.hasElse() && expression.hasSpaceAfterElse()) { length += ELSE.length() + SPACE_LENGTH; // Right after "ELSE " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(ScalarExpressionBNF.ID); } // After "<else expression> " if (expression.hasElseExpression() && expression.hasSpaceAfterElseExpression()) { length += expression.getElseExpression().getLength() + SPACE_LENGTH; // Right after "<else expression> " if (isPositionWithin(position, length, END)) { proposals.addIdentifier(END); } } } } } } /** * {@inheritDoc} */ @Override public void visit(CoalesceExpression expression) { super.visit(expression); if (isFollowingInvalidExpression(expression)) { return; } int position = queryPosition.getPosition(expression) - corrections.peek(); // Within the identifier if (isPositionWithin(position, COALESCE)) { addIdentifier(COALESCE); addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // Right after "<identifier>(" else if (expression.hasLeftParenthesis()) { int length = COALESCE.length() + 1 /* '(' */; if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId()); } } } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { if (!isLocked(expression)) { super.visit(expression); } } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberDeclaration expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "IN" if (isPositionWithin(position, IN)) { if (!isWithinInvalidExpression(expression)) { proposals.addIdentifier(IN); } addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // In a subquery only // After "IN " if (isInSubquery(expression) && expression.hasSpaceAfterIn()) { int length = IN.length() + SPACE_LENGTH; // Right after "IN " if (position == length) { // TODO: Type.SuperQueryIdentificationVariable addLeftIdentificationVariables(expression); } } // In a top-level query or subquery // After "IN(" else if (expression.hasLeftParenthesis()) { int length = IN.length() + 1 /* '(' */; // Right after "IN(" if (position == length) { addLeftIdentificationVariables(expression); addFunctionIdentifiers(CollectionValuedPathExpressionBNF.ID); } // After "<collection-valued path expression>)" if (expression.hasRightParenthesis()) { length += expression.getCollectionValuedPathExpression().getLength() + 1 /* ')' */; // Right after "<collection-valued path expression>)" if ((position == length) && !expression.hasSpaceAfterRightParenthesis()) { proposals.addIdentifier(AS); } if (expression.hasSpaceAfterRightParenthesis()) { length++; } // Within "AS" if (isPositionWithin(position, length, AS)) { proposals.addIdentifier(AS); } } } } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); String identifier = expression.getIdentifier(); int length = 0; if (expression.hasEntityExpression()) { length = expression.getEntityExpression().getLength() + SPACE_LENGTH; } // Within the <identifier> if (isPositionWithin(position, length, identifier)) { proposals.addIdentifier(NOT_MEMBER); proposals.addIdentifier(NOT_MEMBER_OF); proposals.addIdentifier(MEMBER); proposals.addIdentifier(MEMBER_OF); } // After the <identifier> else if (expression.hasOf() && expression.hasSpaceAfterOf() || !expression.hasOf() && expression.hasSpaceAfterMember()) { length += identifier.length() + SPACE_LENGTH; // Right after the <identifier> if (position == length) { if (!expression.hasOf()) { addIdentifier(OF); } addIdentificationVariables(); } } } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { super.visit(expression); if (!isFollowingInvalidExpression(expression)) { visitPathExpression(expression); if (isDeclaration(expression)) { proposals.setClassNamePrefix(word, ClassType.INSTANTIABLE); } } } /** * {@inheritDoc} */ @Override public void visit(ComparisonExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasLeftExpression()) { length += expression.getLeftExpression().getLength() + SPACE_LENGTH; } // Within the comparison operator if (isPositionWithin(position, length, expression.getComparisonOperator())) { addExpressionFactoryIdentifiers(ComparisonExpressionFactory.ID); } // After the comparison operator length += expression.getComparisonOperator().length(); if (expression.hasSpaceAfterIdentifier()) { length++; } // Right after the comparison operator if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(expression.getRightExpressionQueryBNFId()); addClauseIdentifiers(expression.getRightExpressionQueryBNFId()); } } /** * {@inheritDoc} */ @Override public void visit(ConcatExpression expression) { super.visit(expression); visitCollectionExpression(expression, CONCAT, getConcatExpressionCollectionHelper()); } /** * {@inheritDoc} */ @Override public void visit(ConstructorExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // NEW if (isPositionWithin(position, NEW)) { proposals.addIdentifier(NEW); } // After "NEW " else if (expression.hasSpaceAfterNew()) { int length = NEW.length() + SPACE_LENGTH; String className = expression.getClassName(); int classNameLength = className.length(); // Right after "NEW " or within the fully qualified class name if ((position >= length) && (position <= length + classNameLength)) { proposals.setClassNamePrefix(className.substring(0, position - length), ClassType.INSTANTIABLE); } // After "(" else if (expression.hasLeftParenthesis()) { length += classNameLength + SPACE_LENGTH; // Right after "(" if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(ConstructorItemBNF.ID); } else { visitCollectionExpression(expression, NEW, getConstructorCollectionHelper()); } } } } /** * {@inheritDoc} */ @Override public void visit(CountFunction expression) { super.visit(expression); visitAggregateFunction(expression); } /** * {@inheritDoc} */ @Override public void visit(DateTime expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within the identifier if (expression.isCurrentDate() && isPositionWithin(position, CURRENT_DATE) || expression.isCurrentTime() && isPositionWithin(position, CURRENT_TIME) || expression.isCurrentTimestamp() && isPositionWithin(position, CURRENT_TIMESTAMP)) { proposals.addIdentifier(CURRENT_DATE); proposals.addIdentifier(CURRENT_TIME); proposals.addIdentifier(CURRENT_TIMESTAMP); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } } /** * {@inheritDoc} */ @Override public void visit(DeleteClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, DELETE_FROM, getDeleteClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(DeleteStatement expression) { if (!isLocked(expression)) { super.visit(expression); visitStatement(expression, getDeleteClauseStatementHelper()); } } /** * {@inheritDoc} */ @Override public void visit(DivisionExpression expression) { super.visit(expression); visitArithmeticExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(EmptyCollectionComparisonExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasExpression()) { length = expression.getExpression().getLength() + SPACE_LENGTH; } // Within the <identifier> if (isPositionWithin(position, length, expression.getIdentifier())) { proposals.addIdentifier(IS_EMPTY); proposals.addIdentifier(IS_NOT_EMPTY); } } /** * {@inheritDoc} */ @Override public void visit(EntityTypeLiteral expression) { // Adjust the position to be the "beginning" of the expression by adding a "correction" corrections.add(queryPosition.getPosition(expression)); super.visit(expression); corrections.pop(); // Add the possible abstract schema names addEntities(); } /** * {@inheritDoc} */ @Override public void visit(EntryExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.COLLECTION); } /** * {@inheritDoc} */ @Override public void visit(ExistsExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, EXISTS, NOT_EXISTS); } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(FromClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(FunctionExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(GroupByClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, GROUP_BY, getGroupByClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(HavingClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { // First visit with the adjustment corrections.add(queryPosition.getPosition(expression)); super.visit(expression); corrections.pop(); // Now visit without the adjustment super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); if ((position > 0) && (expression.hasSpace() || hasVirtualSpace())) { Expression rangeVariableDeclaration = expression.getRangeVariableDeclaration(); int length = rangeVariableDeclaration.getLength() + SPACE_LENGTH; if (position == length) { if (isComplete(rangeVariableDeclaration)) { addJoinIdentifiers(); } } else { visitCollectionExpression(expression, ExpressionTools.EMPTY_STRING, getJoinCollectionHelper()); } } } /** * {@inheritDoc} */ @Override public void visit(IndexExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(InExpression expression) { expression.accept(getVisitParentVisitor()); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasExpression()) { length = expression.getExpression().getLength() + SPACE_LENGTH; } // Within "IN" if (isPositionWithin(position, length, expression.getIdentifier())) { // Make sure there is an expression left of 'IN' if (length > 2) { proposals.addIdentifier(IN); proposals.addIdentifier(NOT_IN); } addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // After "IN(" else if (expression.hasLeftParenthesis()) { length += expression.getIdentifier().length() + SPACE_LENGTH; // Right after "IN(" if (position == length) { addFunctionIdentifiers(InExpressionItemBNF.ID); proposals.addIdentifier(SELECT); } } } /** * {@inheritDoc} */ @Override public void visit(InputParameter expression) { // No content assist can be provider for an input parameter super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(Join expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); String identifier = expression.getIdentifier(); boolean joinFetch = expression.hasFetch(); // Within "<join>" if (isPositionWithin(position, identifier)) { // Add JOIN identifiers proposals.addIdentifier(JOIN); proposals.addIdentifier(INNER_JOIN); proposals.addIdentifier(LEFT_JOIN); proposals.addIdentifier(LEFT_OUTER_JOIN); // Add JOIN FETCH identifiers if allowed or // if there is no 'AS identification_variable' if (isJoinFetchIdentifiable() || !expression.hasAs() && !expression.hasIdentificationVariable()) { proposals.addIdentifier(JOIN_FETCH); proposals.addIdentifier(INNER_JOIN_FETCH); proposals.addIdentifier(LEFT_JOIN_FETCH); proposals.addIdentifier(LEFT_OUTER_JOIN_FETCH); } } // After "<join> " else if (expression.hasSpaceAfterJoin()) { int length = identifier.length() + SPACE_LENGTH; // Right after "<join> " if (position == length) { // Only add some JOIN identifiers if the actual identifier is shorter or incomplete if (identifier == LEFT) { addIdentifier(LEFT_JOIN); addIdentifier(LEFT_OUTER_JOIN); if (isJoinFetchIdentifiable() || !expression.hasAs() && !expression.hasIdentificationVariable()) { addIdentifier(LEFT_JOIN_FETCH); addIdentifier(LEFT_OUTER_JOIN_FETCH); } } else if (identifier == INNER) { addIdentifier(INNER_JOIN); if (isJoinFetchIdentifiable() || !expression.hasAs() && !expression.hasIdentificationVariable()) { addIdentifier(INNER_JOIN_FETCH); } } else if (identifier.equals("LEFT_OUTER")) { addIdentifier(LEFT_OUTER_JOIN); if (isJoinFetchIdentifiable() || !expression.hasAs() && !expression.hasIdentificationVariable()) { addIdentifier(LEFT_OUTER_JOIN_FETCH); } } else { addLeftIdentificationVariables(expression); } } // After "join association path expression " if (expression.hasJoinAssociationPath() && expression.hasSpaceAfterJoinAssociation()) { length += expression.getJoinAssociationPath().getLength() + SPACE_LENGTH; // Right after "join association path expression " // Make sure to verify if AS can be added if it's a JOIN FETCH expression if (!joinFetch || joinFetch && isJoinFetchIdentifiable()) { if (isPositionWithin(position, length, AS)) { addIdentifier(AS); } if (expression.hasAs()) { length += 2; if (expression.hasSpaceAfterAs()) { length++; } } } if (joinFetch || expression.hasIdentificationVariable()) { length += expression.getIdentificationVariable().getLength(); if (expression.hasSpaceAfterIdentificationVariable()) { length += SPACE_LENGTH; } // After "join association path expression [AS] identification variable" if (position == length) { addIdentifier(ON); } } } } } /** * {@inheritDoc} */ @Override public void visit(JPQLExpression expression) { int position = queryPosition.getPosition(expression) - corrections.peek(); // At the beginning of the query if (position == 0) { addIdentifier(DELETE_FROM); addIdentifier(SELECT); addIdentifier(UPDATE); } } /** * {@inheritDoc} */ @Override public void visit(KeyExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION); } /** * {@inheritDoc} */ @Override public void visit(KeywordExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); String keyword = expression.getText(); // Within the identifier if ((keyword == TRUE) && isPositionWithin(position, TRUE) || (keyword == FALSE) && isPositionWithin(position, FALSE) || (keyword == NULL) && isPositionWithin(position, NULL)) { proposals.addIdentifier(TRUE); proposals.addIdentifier(FALSE); proposals.addIdentifier(NULL); addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } } /** * {@inheritDoc} */ @Override public void visit(LengthExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(LikeExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); if (expression.hasStringExpression()) { int length = expression.getStringExpression().getLength() + SPACE_LENGTH; // Within "LIKE" or "NOT LIKE" if (isPositionWithin(position, length, expression.getIdentifier())) { proposals.addIdentifier(LIKE); proposals.addIdentifier(NOT_LIKE); } // After "LIKE " or "NOT LIKE " else if (expression.hasSpaceAfterLike()) { length += expression.getIdentifier().length() + SPACE_LENGTH; // After "<pattern value> " if (expression.hasPatternValue() && expression.hasSpaceAfterPatternValue()) { length += expression.getPatternValue().getLength() + SPACE_LENGTH; // Within "ESCAPE" if (isPositionWithin(position, length, ESCAPE)) { proposals.addIdentifier(ESCAPE); } } } } } /** * {@inheritDoc} */ @Override public void visit(LocateExpression expression) { super.visit(expression); visitCollectionExpression(expression, LOCATE, getTripleEncapsulatedCollectionHelper()); } /** * {@inheritDoc} */ @Override public void visit(LowerExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(MaxFunction expression) { super.visit(expression); visitAggregateFunction(expression); } /** * {@inheritDoc} */ @Override public void visit(MinFunction expression) { super.visit(expression); visitAggregateFunction(expression); } /** * {@inheritDoc} */ @Override public void visit(ModExpression expression) { super.visit(expression); visitCollectionExpression(expression, MOD, getDoubleEncapsulatedCollectionHelper()); } /** * {@inheritDoc} */ @Override public void visit(MultiplicationExpression expression) { super.visit(expression); visitArithmeticExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(NotExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "NOT" if (isPositionWithin(position, NOT)) { proposals.addIdentifier(NOT); } } /** * {@inheritDoc} */ @Override public void visit(NullComparisonExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasExpression()) { length += expression.getExpression().getLength() + SPACE_LENGTH; } // Within "IS NULL" or "IS NOT NULL" if (isPositionWithin(position, length, expression.getIdentifier())) { proposals.addIdentifier(IS_NULL); proposals.addIdentifier(IS_NOT_NULL); } } /** * {@inheritDoc} */ @Override public void visit(NullExpression expression) { // No content assist can be provider super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(NullIfExpression expression) { super.visit(expression); visitCollectionExpression(expression, NULLIF, getDoubleEncapsulatedCollectionHelper()); } /** * {@inheritDoc} */ @Override public void visit(NumericLiteral expression) { // No content assist can be provider for a numerical value super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(ObjectExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(OnClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(OrderByClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, ORDER_BY, getOrderByClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(OrderByItem expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // After the order by item if (expression.hasExpression()) { int length = expression.getExpression().getLength(); // Within the expression if ((position > -1) && (position <= length)) { if (!expression.hasNulls() && !expression.hasOrdering()) { addIdentifier(ASC); addIdentifier(DESC); } } // After the expression else if (expression.hasSpaceAfterExpression()) { length++; // Right before "ASC" or "DESC" if (position == length) { proposals.addIdentifier(ASC); proposals.addIdentifier(DESC); } else { // Right after the space Ordering ordering = expression.getOrdering(); // Within "ASC" or "DESC" if ((ordering != Ordering.DEFAULT) && isPositionWithin(position, length, ordering.name())) { proposals.addIdentifier(ASC); proposals.addIdentifier(DESC); } } } } } /** * {@inheritDoc} */ @Override public void visit(OrExpression expression) { super.visit(expression); visitLogicalExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // After "<abstract schema name> " if (expression.hasRootObject()) { int length = expression.getRootObject().getLength(); // After "<abstract schema name> " if (expression.hasSpaceAfterRootObject()) { length++; // Right after "<abstract schema name> " if (position == length) { addIdentifier(AS); } // Within 'AS' else if (expression.hasAs() && isPositionWithin(position, length, AS)) { addIdentifier(AS); } } } } /** * {@inheritDoc} */ @Override public void visit(ResultVariable expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); if (expression.hasSelectExpression()) { Expression selectItem = expression.getSelectExpression(); int length = selectItem.getLength() + SPACE_LENGTH; // Right before "AS" if (position == length) { if (isComplete(selectItem)) { addIdentifier(AS); } if (areArithmeticSymbolsAppendable(selectItem)) { addArithmeticIdentifiers(); } } // Within "AS" else if (expression.hasAs() && isPositionWithin(position, length, AS)) { if (isComplete(selectItem)) { addIdentifier(AS); } addFunctionIdentifiers(selectItem); } } // Now add other functions as well, example " A|S e" could become "AVG e" else if (expression.hasAs() && isPositionWithin(position, AS)) { addFunctionIdentifiers(expression); } } /** * {@inheritDoc} */ @Override public void visit(SelectClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, SELECT, getSelectClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(SelectStatement expression) { if (!isLocked(expression)) { super.visit(expression); visitStatement(expression, getSelectClauseStatementHelper()); } } /** * {@inheritDoc} */ @Override public void visit(SimpleFromClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, SELECT, getSimpleSelectClauseCollectionHelper()); } } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { if (!isLocked(expression)) { // Don't continue traversing the parent hierarchy because a subquery // will handle all the possible proposals visitStatement(expression, getSimpleSelectClauseStatementHelper()); } } /** * {@inheritDoc} */ @Override public void visit(SizeExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(SqrtExpression expression) { expression.accept(getVisitParentVisitor()); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { super.visit(expression); if (!isFollowingInvalidExpression(expression)) { visitPathExpression(expression); } } /** * {@inheritDoc} */ @Override public void visit(StringLiteral expression) { // No content assist required super.visit(expression); } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); if (position == 1) { addFunctionIdentifiers(expression); } } /** * {@inheritDoc} */ @Override public void visit(SubstringExpression expression) { super.visit(expression); visitCollectionExpression(expression, SUBSTRING, getTripleEncapsulatedCollectionHelper()); } /** * {@inheritDoc} */ @Override public void visit(SubtractionExpression expression) { super.visit(expression); visitArithmeticExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(SumFunction expression) { super.visit(expression); visitAggregateFunction(expression); } /** * {@inheritDoc} */ @Override public void visit(TreatExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "TREAT" if (isPositionWithin(position, TREAT)) { addIdentifier(TREAT); addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // After "TREAT(" else if (expression.hasLeftParenthesis()) { int length = TREAT.length() + 1; // Right after "TREAT(" if (position == length) { addLeftIdentificationVariables(expression); } // After "<collection-valued path expression> " if (expression.hasCollectionValuedPathExpression() && expression.hasSpaceAfterCollectionValuedPathExpression()) { Expression collectionValuedPathExpression = expression.getCollectionValuedPathExpression(); length += collectionValuedPathExpression.getLength() + SPACE_LENGTH; // Within "AS" if (isPositionWithin(position, length, AS)) { addIdentifier(AS); // If the entity type is not specified, then we can add // the possible abstract schema names if (!expression.hasEntityType()) { // If the type of the path expression is resolvable, // then filter the abstract schema types IType type = queryContext.getType(collectionValuedPathExpression); if (type.isResolvable()) { addEntities(type); } else { addEntities(); } } } } // After "AS " if (expression.hasAs() && expression.hasSpaceAfterAs()) { length += AS.length() + SPACE_LENGTH; // Right after "AS " if (position == length) { // If the type of the path expression is resolvable, // then filter the abstract schema types IType type = queryContext.getType(expression.getCollectionValuedPathExpression()); if (type.isResolvable()) { addEntities(type); } else { addEntities(); } } } } } /** * {@inheritDoc} */ @Override public void visit(TrimExpression expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; // Within "TRIM" if (isPositionWithin(position, TRIM)) { addIdentifier(TRIM); addIdentificationVariables(); addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } // After "TRIM(" else if (expression.hasLeftParenthesis()) { length += TRIM.length() + 1; // Right after "TRIM(" if (position == length) { addIdentifier(BOTH); addIdentifier(LEADING); addIdentifier(TRAILING); if (!expression.hasTrimCharacter() && !expression.hasFrom()) { addIdentificationVariables(); addFunctionIdentifiers(StringPrimaryBNF.ID); } } // Within the trim specification if (expression.hasSpecification()) { String specification = expression.getSpecification().name(); if (isPositionWithin(position, length, specification)) { addIdentifier(BOTH); addIdentifier(LEADING); addIdentifier(TRAILING); if (!expression.hasTrimCharacter() && !expression.hasFrom()) { addIdentificationVariables(); addFunctionIdentifiers(StringPrimaryBNF.ID); } } length += specification.length(); } if (expression.hasSpaceAfterSpecification()) { length += SPACE_LENGTH; } // Trim character if (expression.hasTrimCharacter()) { length += expression.getTrimCharacter().getLength(); } if (expression.hasSpaceAfterTrimCharacter()) { length += SPACE_LENGTH; } // Right after "<trim_character> " if (position == length) { addIdentifier(FROM); if (!expression.hasFrom()) { addIdentificationVariables(); addFunctionIdentifiers(StringPrimaryBNF.ID); } } if (expression.hasFrom()) { // Within "FROM" if (isPositionWithin(position, length, FROM)) { addIdentifier(FROM); } length += FROM.length(); } if (expression.hasSpaceAfterFrom()) { length += SPACE_LENGTH; } // Right after "FROM " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(StringPrimaryBNF.ID); } // Right after the string literal but there is no trim character, // nor FROM and there is a virtual space if (expression.hasExpression()) { length += expression.getExpression().getLength(); if ((position == length + virtualSpaces.peek()) && !expression.hasTrimCharacter() && !expression.hasFrom()) { addIdentifier(FROM); } } } } /** * {@inheritDoc} */ @Override public void visit(TypeExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(UnknownExpression expression) { super.visit(expression); visitInvalidExpression(expression); } /** * {@inheritDoc} */ @Override public void visit(UpdateClause expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "UPDATE" if (isPositionWithin(position, UPDATE)) { proposals.addIdentifier(UPDATE); } // After "UPDATE " else if (expression.hasSpaceAfterUpdate()) { int length = UPDATE.length() + SPACE_LENGTH; // Right after "UPDATE " if (position == length) { addEntities(); } // After "<range variable declaration> " else if (expression.hasRangeVariableDeclaration()) { RangeVariableDeclaration rangeVariableDeclaration = findRangeVariableDeclaration(expression); if ((rangeVariableDeclaration != null) && rangeVariableDeclaration.hasRootObject() && rangeVariableDeclaration.hasSpaceAfterRootObject()) { length += rangeVariableDeclaration.getRootObject().getLength() + SPACE_LENGTH; // Example: "UPDATE System s" if (!expression.hasSet() && !rangeVariableDeclaration.hasAs() && isPositionWithin(position, length, SET)) { addIdentifier(SET); } // Example: "UPDATE System s " // Example: "UPDATE System AS s " else { if (rangeVariableDeclaration.hasAs()) { length += 2; } if (rangeVariableDeclaration.hasSpaceAfterAs()) { length++; } if (rangeVariableDeclaration.hasIdentificationVariable()) { length += rangeVariableDeclaration.getIdentificationVariable().getLength(); } if (expression.hasSpaceAfterRangeVariableDeclaration()) { length++; } // Within "SET" if ((rangeVariableDeclaration.hasAs() && rangeVariableDeclaration.hasIdentificationVariable() || !rangeVariableDeclaration.hasAs() && rangeVariableDeclaration.hasIdentificationVariable()) && isPositionWithin(position, length, SET)) { addIdentifier(SET); } // After "SET " else if (expression.hasSet() && expression.hasSpaceAfterSet()) { length += SET.length() + SPACE_LENGTH; // Right after "SET " if (position == length) { addIdentificationVariables(); } // Within the new value expressions else { visitCollectionExpression(expression, ExpressionTools.EMPTY_STRING, getUpdateItemCollectionHelper()); } } } } } } } /** * {@inheritDoc} */ @Override public void visit(UpdateItem expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; // At the beginning if (position == length) { addIdentificationVariables(); } else if (expression.hasStateFieldPathExpression() && expression.hasSpaceAfterStateFieldPathExpression()) { length += expression.getStateFieldPathExpression().getLength() + SPACE_LENGTH; // Within "=" if (position == length) { proposals.addIdentifier(EQUAL); } // After "=" else if (expression.hasEqualSign()) { length++; // Right after "=" if (position == length) { proposals.addIdentifier(EQUAL); addIdentificationVariables(); addFunctionIdentifiers(NewValueBNF.ID); } else if (expression.hasSpaceAfterEqualSign()) { length++; // Right after "= " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(NewValueBNF.ID); } } } } } /** * {@inheritDoc} */ @Override public void visit(UpdateStatement expression) { if (!isLocked(expression)) { super.visit(expression); visitStatement(expression, getUpdateClauseStatementHelper()); } } /** * {@inheritDoc} */ @Override public void visit(UpperExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); } /** * {@inheritDoc} */ @Override public void visit(ValueExpression expression) { super.visit(expression); visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION); } /** * {@inheritDoc} */ @Override public void visit(WhenClause expression) { super.visit(expression); int position = queryPosition.getPosition(expression) - corrections.peek(); // Within "WHEN" if (isPositionWithin(position, WHEN)) { if (!isWithinInvalidExpression(expression)) { proposals.addIdentifier(WHEN); } } // After "WHEN " else if (expression.hasSpaceAfterWhen()) { int length = 5 /* WHEN + space */; // Right after "WHEN " if (position == length) { visitCollectionExpression(expression, WHEN, getWhenClauseConditionalClauseCollectionHelper()); } else { length += expression.getWhenExpression().getLength(); // After "WHEN <expression> " => THEN if (expression.hasSpaceAfterWhenExpression()) { length++; // Right after "WHEN <expression> " => THEN if (position == length) { // Check an incomplete WHEN expression visitCollectionExpression(expression, WHEN, getWhenClauseConditionalClauseCollectionHelper()); // If the expression is complete, then "THEN" is a valid proposal if (isComplete(expression)) { proposals.addIdentifier(THEN); } } else if (expression.hasThen()) { // Within "THEN" if (isPositionWithin(position, length, THEN)) { proposals.addIdentifier(THEN); } else { length += 4 /* THEN */; // After "WHEN <expression> THEN " if (expression.hasSpaceAfterThen()) { length++; // Right after "WHEN <expression> THEN " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(ScalarExpressionBNF.ID); } } } } } } } } /** * {@inheritDoc} */ @Override public void visit(WhereClause expression) { if (!isLocked(expression)) { super.visit(expression); visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper()); } } /** * Visits the given {@link AggregateFunction} and attempts to find valid proposals. * * @param expression The {@link AggregateFunction} to inspect */ protected void visitAggregateFunction(AggregateFunction expression) { if (isFollowingInvalidExpression(expression)) { return; } int position = queryPosition.getPosition(expression) - corrections.peek(); String identifier = expression.getIdentifier(); // Within "<identifier>" if (isPositionWithin(position, identifier)) { addIdentifier(identifier); addFunctionIdentifiers(expression); } // After "<identifier>(" else if (expression.hasLeftParenthesis()) { int length = identifier.length() + 1 /* '(' */; boolean hasDistinct = expression.hasDistinct(); // Within "DISTINCT" if (hasDistinct && isPositionWithin(position, length, DISTINCT) ) { addIdentifier(DISTINCT); } // After "(" else { if (hasDistinct && expression.hasSpaceAfterDistinct()) { length += DISTINCT.length() + SPACE_LENGTH; } // Right after "(" or right after "(DISTINCT " if (position == length) { if (!hasDistinct) { addIdentifier(DISTINCT); } addIdentificationVariables(); addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId()); } } } } /** * Visits the given {@link ArithmeticExpression} and attempts to find valid proposals. * * @param expression The {@link ArithmeticExpression} to inspect */ protected void visitArithmeticExpression(ArithmeticExpression expression) { int position = queryPosition.getPosition(expression) - corrections.peek(); int length = 0; if (expression.hasLeftExpression()) { length += expression.getLeftExpression().getLength() + SPACE_LENGTH; } // Within the arithmetic sign if (isPositionWithin(position, length, PLUS)) { addAggregateIdentifiers(expression.getQueryBNF()); } // After the arithmetic sign, with or without the space else if (expression.hasSpaceAfterIdentifier()) { length += 2; // Right after the space if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(expression.getRightExpressionQueryBNFId()); } } } /** * Adds the possible proposals for the given {@link Expression expression} based on the location of * the cursor and the content of the expression. * * @param expression The {@link Expression expression} being visited * @param identifier The JPQL identifier of the {@link Expression} being visited, if the {@link * Expression} does not have an identifier, than an empty string should be passed * @param helper This helper completes the behavior of this method by retrieving the information * from the given {@link Expression} */ protected <T extends Expression> void visitCollectionExpression(T expression, String identifier, CollectionExpressionHelper<T> helper) { if (isFollowingInvalidExpression(expression)) { return; } int position = queryPosition.getPosition(expression) - corrections.peek(); boolean hasIdentifier = (identifier.length() > 0); boolean virtualSpace = hasVirtualSpace(); // Within the identifier if (hasIdentifier && isPositionWithin(position, identifier)) { helper.addIdentifier(expression, identifier); } // After "<identifier>(" or "<identifier> " else if (helper.hasDelimiterAfterIdentifier(expression)) { int length = !hasIdentifier ? 0 : identifier.length() + 1 /* delimiter, either space or ( */; length += helper.preExpressionLength(expression); // Right after "<identifier>(" or "<identifier> " if (position == length) { helper.addTheBeginningOfChild(expression, null, 0, false); } // Within the encapsulated expressions else { // Create a collection representation of the encapsulated expression(s) CollectionExpression collectionExpression = helper.buildCollectionExpression(expression); boolean hasComma = false; boolean previousHasComma = false; // Determine the maximum children count, it is possible the query contains more children // than the expession's grammar would actually allow. The content assist will only // provide assistance from the first child to the last allowed child int childrenCount = collectionExpression.childrenSize(); int count = Math.min(childrenCount, helper.maxCollectionSize(expression)); // Iterate through each child of the collection for (int index = 0; index < count; index++) { Expression child = collectionExpression.getChild(index); int childLength = 0; // At the beginning of the child if (position == length) { helper.addTheBeginningOfChild(expression, collectionExpression, index, hasComma); break; } // Each expression within the collection has to be separated by a comma, the previous // expression and the expression at the current index are not separated by a comma // Example: "SELECT e FROM Employee e GROUP" <- [ "Employee e", "GROUP" ] else if ((index > 0) && !hasComma) { length += child.getLength(); // At the end of the child if (position == length) { helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, false); break; } // To be valid, each child has to be separated by a comma, // ask the helper if it should continue with the next child if (!helper.canContinue(expression, collectionExpression, index)) { break; } // Special case when reaching the end of the collection else if (index + 1 == count) { helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, false); } } else { childLength = child.getLength(); // At the end of the child if ((position == length + childLength) || (virtualSpace && (position == length + childLength + SPACE_LENGTH))) { helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, virtualSpace); break; } } // Now add the child's length and length used by the comma and space length += childLength; // Move after the comma previousHasComma = hasComma; hasComma = collectionExpression.hasComma(index); if (hasComma) { // Two items were not separated by a comma and the next one is, this is invalid if ((index > 0) && !previousHasComma) { return; } length++; // After the comma, add the proposals if (position == length) { helper.addTheBeginningOfChild(expression, collectionExpression, index + 1, hasComma); break; } } // Move after the space that follows the comma if (collectionExpression.hasSpace(index)) { length++; // At the end of the child if (!hasComma && (position == length)) { helper.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, true); } } // Nothing more can be looked at if (position < length) { break; } } } } } protected void visitEndingExpression(Expression expression) { // Keep track of the original QueryPosition, the new one will differ // to properly accommodate for the invalid portion of the query QueryPosition oldQueryPosition = queryPosition; // // Step 1 // // Create a new QueryPosition for which the positions is set to be at the end // of the valid fragment of the JPQL query, which is up to right before the // beginning of the invalid fragment // Example: "SELECT e FROM Employee e S| WHERE e.name = 'JPQL'" <- | is the position of the cursor // Valid fragment: "SELECT e FROM Employee e " // Invalid fragment: "S WHERE e.name = 'JPQL'" queryPosition = buildEndingPositionFromInvalidExpression(expression, expression, new boolean[1]); // Adjust the position to include the whitespace owned by the parent expression expression = queryPosition.getExpression(); while (expression != null) { int position = queryPosition.getPosition(expression); if (position == -1) { expression = null; } else { queryPosition.addPosition(expression, position + 1); expression = expression.getParent(); } } // // Step 2 // // Now make sure the correction is reset temporarily corrections.add(0); addVirtualSpace(); // Now traverse the tree starting at the leaf from the valid fragment queryPosition.getExpression().accept(this); // Revert the data queryPosition = oldQueryPosition; corrections.pop(); removeVirtualSpace(); } protected void visitEnumConstant(AbstractPathExpression expression) { int position = queryPosition.getPosition(expression); String text = expression.toActualText(); int lastDotIndex = text.lastIndexOf(DOT); // Check to see if an enum constant can be used at the expression's location if (isEnumAllowed(expression)) { boolean enumConstant = false; // The position is after the last dot, check for enum constants if (position > lastDotIndex) { // Retrieve the enum type if the path up to the last dot is a fully qualified enum type String enumType = expression.toParsedText().substring(0, lastDotIndex); IType type = queryContext.getType(enumType); // The path expression before the last dot is an enum type if (type.isResolvable() && type.isEnum()) { enumConstant = true; // Now retrieve the portion of the enum constant based on the cursor position String word = text.substring(lastDotIndex + 1, position); // Add the enum constants and filter them based on what's already proposed addEnumConstants(type, word); } } // Enum type if (!enumConstant) { // Now retrieve the portion of the enum constant based on the cursor position text = text.substring(0, position); // Set the possible starting of a fully qualified enum type proposals.setClassNamePrefix(text, ClassType.ENUM); } } } protected void visitInvalidExpression(Expression expression) { if (!isLocked(expression)) { int position = queryPosition.getPosition(expression) - corrections.peek(); boolean virtualSpace = (position == 1) && (word.length() == 0); // 1. Within the first word of the invalid fragment // 2. Or after the ending whitespace // Otherwise no need to do anything if beyond it if (isPositionWithin(position, word) || virtualSpace) { lockedExpressions.add(expression); // Keep track of the original QueryPosition, the new one will differ // to properly accommodate for the invalid portion of the query QueryPosition oldQueryPosition = queryPosition; boolean[] spaces = { false }; // // Step 1 // // Create a new QueryPosition for which the positions is set to be at the end // of the valid fragment of the JPQL query, which is up to right before the // beginning of the invalid fragment // Example: "SELECT e FROM Employee e S| WHERE e.name = 'JPQL'" <- | is the position of the cursor // Valid fragment: "SELECT e FROM Employee e " // Invalid fragment: "S WHERE e.name = 'JPQL'" queryPosition = buildEndingPositionFromInvalidExpression(expression, expression.getRoot(), spaces); // // Step 2 // // Now make sure the correction is reset temporarily corrections.add(0); // This means the valid fragment ends with a whitespace, keep track of it if (spaces[0] || virtualSpace) { addVirtualSpace(); } // Now traverse the tree starting at the leaf from the valid fragment queryPosition.getExpression().accept(this); // Revert the data queryPosition = oldQueryPosition; corrections.pop(); lockedExpressions.pop(); if (spaces[0] || virtualSpace) { removeVirtualSpace(); } } } } /** * Visits the given {@link LogicalExpression} and attempts to find valid proposals. * * @param expression The {@link LogicalExpression} to inspect */ protected void visitLogicalExpression(LogicalExpression expression) { if (isFollowingInvalidExpression(expression)) { return; } int position = queryPosition.getPosition(expression) - corrections.peek(); String identifier = expression.getIdentifier(); int length = 0; if (expression.hasLeftExpression()) { length += expression.getLeftExpression().getLength() + SPACE_LENGTH; } // Within "AND" or "OR" if (isPositionWithin(position, length, identifier)) { proposals.addIdentifier(identifier); } // After "AND " or "OR " else if (expression.hasSpaceAfterIdentifier()) { length += identifier.length() + SPACE_LENGTH; // Right after "AND " or "OR " if (position == length) { addIdentificationVariables(); addFunctionIdentifiers(expression.getRightExpressionQueryBNFId()); } } } /** * Visits the given {@link AbstractPathExpression} and attempts to find valid proposals. * <p> * Note: A path expression can represent many things: state field, relationship field, collection * field, enum constant, etc. This will consider all variations. * * @param expression The {@link AbstractPathExpression} to inspect */ protected void visitPathExpression(AbstractPathExpression expression) { int position = queryPosition.getPosition(expression); String text = expression.toActualText(); int dotIndex = text.indexOf(DOT); if (position > -1) { String variableName = queryContext.literal( expression.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); boolean variable = ExpressionTools.stringIsNotEmpty(variableName); // The position if after the identification variable if ((dotIndex > -1) && (position > dotIndex)) { // Retrieve the filter based on the location of the state field path, for instance, in // a JOIN or IN expression, the filter has to filter out the property and accept the // fields of collection type visitPathExpression(expression, buildMappingFilter(expression)); // Don't do anything if the first path is not an identification variable, // which means it's either KEY() or VALUE() if (variable) { // Attempts to resolve a possible fully qualified enum constant visitEnumConstant(expression); // Attempts to resolve third party option visitThirdPartyPathExpression(expression, variableName); } } // The position is within the identification variable but don't do anything if the // identification variable is either KEY() or VALUE() else if (variable) { corrections.add(queryPosition.getPosition(expression)); visit(expression); corrections.pop(); } } } /** * Visits the given {@link AbstractPathExpression} by attempting to resolve the path. * * @param expression The {@link AbstractPathExpression} to inspect * @param filter The {@link Filter} is used to filter out {@link IMapping} that are not valid * based on their type and the type that is allowed */ protected void visitPathExpression(AbstractPathExpression expression, Filter<IMapping> filter) { MappingCollector mappingCollector = getDefaultMappingCollector(); int position = queryPosition.getPosition(expression); boolean mappingCollectorCreated = false; Resolver resolver = null; int length = 0; for (int index = 0, count = expression.pathSize(); index < count; index++) { String path = expression.getPath(index); // We're at the position, create the ChoiceBuilder if (position <= length + path.length()) { if (length == position) { path = ExpressionTools.EMPTY_STRING; } else if (position - length > -1) { path = path.substring(0, position - length); } // Special case where the path expression only has the // identification variable set if (resolver == null) { break; } mappingCollector = buildFilteringMappingCollector(expression, resolver, filter, path); mappingCollectorCreated = true; break; } // The path is entirely before the position of the cursor else { // The first path is always an identification variable if (resolver == null) { resolver = queryContext.getResolver(expression.getIdentificationVariable()); } // Any other path is a property or collection-valued path else if ((index + 1 < count) || expression.endsWithDot()) { Resolver childResolver = resolver.getChild(path); if (childResolver == null) { childResolver = new StateFieldResolver(resolver, path); resolver.addChild(path, childResolver); resolver = childResolver; } } // Move the cursor after the path and dot length += path.length() + 1; } } if (!mappingCollectorCreated && (resolver != null)) { mappingCollector = buildMappingCollector(expression, resolver, filter); } proposals.addMappings(mappingCollector.buildProposals()); } /** * Adds the possible proposals for the given {@link AbstractSingleEncapsulatedExpression expression} * based on the location of the cursor and the content of the expression. * * @param expression The {@link AbstractSingleEncapsulatedExpression expression} being visited * @param identificationVariableType The type of identification variables that can be added as * possible proposals */ protected void visitSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression, IdentificationVariableType identificationVariableType) { visitSingleEncapsulatedExpression( expression, identificationVariableType, expression.getIdentifier() ); } /** * Adds the possible proposals for the given {@link AbstractSingleEncapsulatedExpression expression} * based on the location of the cursor and the content of the expression. * * @param expression The {@link AbstractSingleEncapsulatedExpression expression} being visited * @param identificationVariableType The type of identification variables that can be added as * possible proposals * @param expressionIdentifiers Sometimes the expression may have more than one possible identifier, * such as <b>ALL</b>, <b>ANY</b> and <b>SOME</b> are a possible JPQL identifier for a single * expression ({@link AllOrAnyExpression} */ protected void visitSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression, IdentificationVariableType identificationVariableType, String... expressionIdentifiers) { if (isFollowingInvalidExpression(expression)) { return; } int position = queryPosition.getPosition(expression) - corrections.peek(); String actualIdentifier = expression.getIdentifier(); boolean added = false; for (String identifier : expressionIdentifiers) { // Within the identifier if (isPositionWithin(position, actualIdentifier)) { addFunctionIdentifiers(expression); // If the expression is marked as bad, then the identifiers // for the encapsulated expression are no added if (!isWithinInvalidExpression(expression)) { for (String jpqlIdentifier : expressionIdentifiers) { proposals.addIdentifier(identifier); } } // Remove any identifier that got added by addAdditionalFunctions() else { for (String jpqlIdentifier : expressionIdentifiers) { proposals.removeIdentifier(identifier); } } } // Right after "<identifier>(" else if (expression.hasLeftParenthesis()) { int length = identifier.length() + 1 /* '(' */; if (!added && (position == length)) { added = true; addIdentificationVariables(expression, identificationVariableType); String queryBNF = expression.getEncapsulatedExpressionQueryBNFId(); addFunctionIdentifiers(queryBNF); addClauseIdentifiers(queryBNF); } } } } /** * Visits the given {@link AbstractSelectStatement} and checks to see if the identifiers of the * following clauses can be added a valid proposals. * * @param expression * @param helper This helper handles one clause from the given <code><b>SELECT</b></code> statement */ @SuppressWarnings("unchecked") protected <T extends Expression> void visitStatement(T expression, StatementHelper<T> helper) { lockedExpressions.add(expression); try { int position = queryPosition.getPosition(expression); int length = 0; while (helper != null) { // Add the length of the clause to the current length Expression clause = helper.getClause(expression); length += clause.getLength(); // Within the clause, not handled here if (position < length) { break; } // Now check if the clause is complete boolean complete = false; // At the end of the clause if (position == length) { // Check to see if the following clause identifiers can be appended // Example: "SELECT e f|" <- FROM can be a valid proposal // Example: "SELECT AVG(e.age)|" <- No clause identifiers can be added // Example: "SELECT e FROM Employee e|" <- No clause identifiers can be added // Example: "SELECT e FROM Employee e H|" <- HAVING can be a valid proposal complete = isClauseAppendable(clause); } // Check whether a whitespace is after the clause (owned by the select statement) else if (helper.hasSpaceAfterClause(expression)) { length++; if (position == length) { // Ask the helper to visit the clause's expression // Example: "SELECT e FROM Employee e WHERE e.name = 'JPQL' |" // AND|OR can be added as valid proposals visitEndingExpression(clause); // Check to see if the following clause identifiers can be appended // Example: "SELECT e |" <- FROM can be a valid proposal // Example: "SELECT AVG(e.age) |" <- FROM can be a valid proposal // Example: "SELECT e FROM Employee e |" <- The following clause identifiers can be valid proposals complete = helper.isClauseComplete(expression); } } // Continue to with the next helper if (!complete) { helper = (StatementHelper<T>) helper.getNextHelper(); } // Add the following clause identifiers until one is defined else { // Append any internal clause identifiers helper.addInternalClauseProposals(expression); // Iterate through the helpers for the following clauses do { helper = (StatementHelper<T>) helper.getNextHelper(); if (helper != null) { // Add the clause identifiers helper.addClauseProposals(); // The following clauses cannot be added because either // the clause is required or defined if (helper.isRequired() || helper.hasClause(expression)) { helper = null; } } } while (helper != null); } } } finally { lockedExpressions.pop(); } } /** * Visits the given {@link AbstractPathExpression} and attempts to find valid proposals that is * not provided by the default implementation. Subclasses can add additional proposals that is * outside of the scope of generic JPA metadata. * * @param expression The {@link AbstractPathExpression} to inspect * @param variableName The beginning of the path expression */ protected void visitThirdPartyPathExpression(AbstractPathExpression expression, String variableName) { } // Made static for performance reasons. protected static class AbstractAppendableExpressionVisitor extends AnonymousExpressionVisitor { /** * Flag used to determine if JPQL identifiers can be appended to the expression. */ protected boolean appendable; /** * Disposes of the internal data. */ public void dispose() { appendable = false; } /** * Determines whether JPQL identifiers can be appended to the expression. * * @return <code>true</code> */ public boolean isAppendable() { return appendable; } } // Made static for performance reasons. /** * Common helper with visitor reference. */ private static abstract class AbstractVisitorHelper { /** * Enclosing visitor instance. */ protected final AbstractContentAssistVisitor visitor; AbstractVisitorHelper(AbstractContentAssistVisitor visitor) { this.visitor = visitor; } } // Made static for performance reasons. /** * This helper handles adding proposals within a conditional expression that might be parsed as * a single expression or has a collection of expression, which means the fragment is either * incomplete or invalid. */ protected static abstract class AbstractConditionalClauseCollectionHelper<T extends Expression> extends AbstractVisitorHelper implements CollectionExpressionHelper<T> { protected AbstractConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(Expression expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // Coming from addTheBeginningOfChild(), require to bypass the above check if (index < 0) { index = (-index / 10) - 1; } // The only thing that is appendable is an arithmetic operator // Example: "SELECT e FROM Employee e WHERE e.name|" // Example: "SELECT e FROM Employee e WHERE I|" if ((index == 0) && !virtualSpace) { Expression child = collectionExpression.getChild(0); if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } else { Object[] result = findChild(collectionExpression, index); if (result == null) { return; } Expression child = (Expression) result[0]; boolean hasIs = (Boolean) result[1]; boolean hasNot = (Boolean) result[2]; // If 'IS' or 'IS NOT' is present, then none of the following are valid proposals if (!hasIs && !hasNot) { if (visitor.areLogicalSymbolsAppendable(child)) { visitor.addLogicalIdentifiers(); } if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } if (visitor.areComparisonSymbolsAppendable(child)) { visitor.addComparisonIdentifiers(child); } } if (visitor.isCompoundable(child)) { visitor.addCompoundIdentifiers(ConditionalExpressionBNF.ID, child, hasIs, hasNot); } } } /** * {@inheritDoc} */ public void addIdentifier(Expression expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(Expression expression, CollectionExpression collectionExpression, int index, boolean hasComma) { if (index == 0) { visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(ConditionalExpressionBNF.ID); if ((collectionExpression != null) && visitor.isSubqueryAppendable(collectionExpression.getChild(index))) { visitor.addIdentifier(SELECT); } } else { addAtTheEndOfChild(expression, collectionExpression, index * -10, hasComma, true); } } /** * {@inheritDoc} */ public boolean canContinue(Expression expression, CollectionExpression collectionExpression, int index) { Expression child = collectionExpression.getChild(index); if (visitor.isNotExpression(child)) { return true; } String text = child.toParsedText(); return text.equalsIgnoreCase(IS) || text.equalsIgnoreCase(NOT) || text.equalsIgnoreCase("IS NOT"); } /** * Handles a special case for a compound identifier like 'IS EMPTY' or 'IS NOT EMPTY'. * * @param collectionExpression The {@link CollectionExpression} is used to find the {@link * Expression} to use when filtering out compound identifier * @param index The position to start the search, which goes from that index to the beginning * @return An array of three elements. The first one is the child {@link Expression} that can * be used when filtering out compound identifier. The second boolean element indicates if * <code><b>IS</b></code> was detected after the child. The third boolean element indicates if * <code><b>NOT</b></code> was detected after the child, which would also be after <code><b>IS</b></code> * if it was detected. <code>null</code> is returned if nothing could be found */ protected Object[] findChild(CollectionExpression collectionExpression, int index) { boolean notFound = false; boolean isFound = false; boolean scanPrevious = false; for (; index > -1; index--) { Expression child = collectionExpression.getChild(index); String text = child.toParsedText(); // Handle 'NOT' if (text.equalsIgnoreCase(NOT) || visitor.isNotExpression(child)) { // Two consecutive 'NOT' or 'IS' is invalid or 'NOT IS' is not valid if (isFound || notFound) { break; } notFound = true; } // Handle 'IS' else if (text.equalsIgnoreCase(IS)) { // Two consecutive 'IS' is invalid if (isFound) { break; } isFound = true; } else if ("IS NOT".equalsIgnoreCase(text)) { // Two consecutive 'NOT' or 'IS' is invalid or 'NOT IS' is not valid if (isFound || notFound) { break; } isFound = true; notFound = true; } // Anything else else { // Make sure the previous item is not 'IS', this can happen // when the correction value is changed (happens with IdentificationVariable) if (index > 0) { Object[] result = findChild(collectionExpression, index - 1); isFound |= (Boolean) result[1]; notFound |= (Boolean) result[2]; } return new Object[] { child, isFound, notFound }; } } return null; } /** * {@inheritDoc} */ public int maxCollectionSize(Expression expression) { // The actual number is 0 but an incomplete fragment like "WHERE e.phoneNumbers IS N" // is a collection of 3 expressions return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(Expression expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(Expression expression, int index) { return visitor.getQueryBNF(ConditionalExpressionBNF.ID); } } // Made static for performance reasons. protected static abstract class AbstractFromClauseStatementHelper<T extends AbstractSelectStatement> extends AbstractVisitorHelper implements StatementHelper<T> { protected AbstractFromClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(FROM); } /** * {@inheritDoc} */ public void addInternalClauseProposals(T expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(T expression) { return expression.getFromClause(); } /** * {@inheritDoc} */ public boolean hasClause(T expression) { return expression.hasFromClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(T expression) { return expression.hasSpaceAfterFrom(); } /** * {@inheritDoc} */ public boolean isClauseComplete(T expression) { return visitor.isComplete(expression.getFromClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return true; } } // Made static for performance reasons. protected static abstract class AbstractGroupByClauseStatementHelper<T extends AbstractSelectStatement> extends AbstractVisitorHelper implements StatementHelper<T> { protected AbstractGroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addCompositeIdentifier(GROUP_BY, -1); } /** * {@inheritDoc} */ public void addInternalClauseProposals(T expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(T expression) { return expression.getGroupByClause(); } /** * {@inheritDoc} */ public boolean hasClause(T expression) { return expression.hasGroupByClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(T expression) { return expression.hasSpaceAfterGroupBy(); } /** * {@inheritDoc} */ public boolean isClauseComplete(T expression) { return visitor.isComplete(expression.getGroupByClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static for performance reasons. protected static abstract class AbstractHavingClauseStatementHelper<T extends AbstractSelectStatement> extends AbstractVisitorHelper implements StatementHelper<T> { protected AbstractHavingClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(HAVING); } /** * {@inheritDoc} */ public void addInternalClauseProposals(T expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(T expression) { return expression.getHavingClause(); } /** * {@inheritDoc} */ public boolean hasClause(T expression) { return expression.hasHavingClause(); } /** * {@inheritDoc} */ public boolean isClauseComplete(T expression) { return visitor.isComplete(expression.getHavingClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static for performance reasons. protected static abstract class AbstractSelectClauseCollectionHelper<T extends AbstractSelectClause> extends AbstractVisitorHelper implements CollectionExpressionHelper<T> { protected AbstractSelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(T expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // The only thing that is appendable is an arithmetic operator // Example: "SELECT e.name|" // Example: "SELECT e|" if (queryBNF(expression, index).handleAggregate()) { Expression child = collectionExpression.getChild(index); if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } } /** * {@inheritDoc} */ public void addIdentifier(T expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(T expression, CollectionExpression collectionExpression, int index, boolean hasComma) { if ((index == 0) || hasComma) { visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(expression.getSelectItemQueryBNFId()); } } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(T expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getSelectExpression()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(T expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(T expression) { return expression.hasSpaceAfterSelect(); } /** * {@inheritDoc} */ public int maxCollectionSize(T expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(T expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(T expression, int index) { return visitor.getQueryBNF(expression.getSelectItemQueryBNFId()); } } // Made static for performance reasons. protected static abstract class AbstractSelectClauseStatementHelper extends AbstractVisitorHelper implements StatementHelper<AbstractSelectStatement> { protected AbstractSelectClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(SELECT); } /** * {@inheritDoc} */ public void addInternalClauseProposals(AbstractSelectStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(AbstractSelectStatement expression) { return expression.getSelectClause(); } /** * {@inheritDoc} */ public boolean hasClause(AbstractSelectStatement expression) { return expression.hasSelectClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(AbstractSelectStatement expression) { return expression.hasSpaceAfterSelect(); } /** * {@inheritDoc} */ public boolean isClauseComplete(AbstractSelectStatement expression) { return visitor.isComplete(expression.getSelectClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return true; } } // Made static for performance reasons. protected static abstract class AbstractWhereClauseSelectStatementHelper<T extends AbstractSelectStatement> extends AbstractVisitorHelper implements StatementHelper<T> { protected AbstractWhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(WHERE); } /** * {@inheritDoc} */ public void addInternalClauseProposals(T expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(T expression) { return expression.getWhereClause(); } /** * {@inheritDoc} */ public boolean hasClause(T expression) { return expression.hasWhereClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(T expression) { return expression.hasSpaceAfterWhere(); } /** * {@inheritDoc} */ public boolean isClauseComplete(T expression) { return visitor.isComplete(expression.getWhereClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static for performance reasons. /** * This visitor retrieves the permitted type from the path expression's parent. For instance, * <b>SUM</b> or <b>AVG</b> only accepts state fields that have a numeric type. */ protected static abstract class AcceptableTypeVisitor extends AbstractExpressionVisitor { /** * The type that is retrieved based on the expression, it determines what is acceptable. */ protected IType type; /** * Disposes of the internal data. */ public void dispose() { type = null; } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { expression.getParent().accept(this); } } // Made static for performance reasons. /** * This visitor scans the visited {@link Expression} and determines if a JPQL identifier can be * added ("appended") when the position of the cursor is at the end of the expression. * <p> * For instance: * <ul> * <li>In "<code>SELECT e, AVG(e.age) F|</code>", F is parsed as a result variable but * can also be seen as the first letter for <b>FROM</b>;</li> * <li>In "<code>SELECT e FROM Employee e WHERE e.name |</code>", the compound identifiers can be * added, eg: 'IS NOT NULL', or '=', etc</li> * <li>In "<code>SELECT e FROM Employee e WHERE e.name NOT B|</code>", only the composite * identifier "BETWEEN" and "NOT BETWEEN" can be added because the <code>NOT</code> expression * does not have a valid expression: "B" is not a valid expression.</li> * <li>In "<code>SELECT e FROM Employee e FROM e.age|</code>", the arithmetic and comparison * identifiers are allowed, but the logical and compound identifiers.</li> * </ul> */ protected static class AppendableExpressionVisitor extends AbstractAppendableExpressionVisitor { /** * Enclosing visitor instance. */ protected final AbstractContentAssistVisitor visitor; /** * The type of the JPQL identifiers can can be possible proposals. */ protected AppendableType appendableType; /** * Internal flag indicating if a clause is being visited which can have a collection of children. */ protected boolean clauseOfItems; /** * Caches the visited {@link CollectionExpression} so a child could use it. */ protected CollectionExpression collectionExpression; /** * Internal flag indicating the {@link Expression} being visited is a conditional expression. */ protected boolean conditionalExpression; /** * When visiting a {@link CollectionExpression}, this indicates if there is a comma before the * child being visited. */ protected boolean hasComma; /** * When visiting a {@link CollectionExpression}, this indicates the position within that * collection of the child being visited. */ protected int positionInCollection; /** * Internal flag indicating the {@link Expression} being visited is encapsulated by parenthesis. */ protected boolean subExpression; /** * Creates a new <code>AppendableExpressionVisitor</code>. */ AppendableExpressionVisitor(AbstractContentAssistVisitor visitor) { super(); this.visitor = visitor; this.positionInCollection = -1; } /** * {@inheritDoc} */ @Override public void visit(AbsExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(AbstractSchemaName expression) { appendable = !conditionalExpression; } /** * {@inheritDoc} */ @Override public void visit(AdditionExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(AllOrAnyExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(AndExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(ArithmeticFactor expression) { if (expression.hasExpression()) { expression.getExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(AvgFunction expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(BadExpression expression) { appendable = true; } /** * {@inheritDoc} */ @Override public void visit(BetweenExpression expression) { // Example 2: "x BETWEEN y A" is not complete // Example 1: "x between y SQRT(e.age)" is seen as complete if (!expression.hasAnd()) { String variable = visitor.queryContext.literal( expression.getUpperBoundExpression(), LiteralType.IDENTIFICATION_VARIABLE ); if (variable != ExpressionTools.EMPTY_STRING) { appendable = false; } else { expression.getUpperBoundExpression().accept(this); } } else { boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; expression.getUpperBoundExpression().accept(this); conditionalExpression = oldConditionalExpression; } } /** * {@inheritDoc} */ @Override public void visit(CaseExpression expression) { appendable = !conditionalExpression && expression.hasEnd(); } /** * {@inheritDoc} */ @Override public void visit(CoalesceExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { collectionExpression = expression; positionInCollection = expression.childrenSize() - 1; hasComma = expression.hasComma(positionInCollection - 1); if ((appendableType == AppendableType.CLAUSE) && (positionInCollection + 1 < collectionExpression.childrenSize())) { appendable = false; } else { expression.accept(positionInCollection, this); } hasComma = false; positionInCollection = -1; collectionExpression = null; } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberDeclaration expression) { if (appendableType == AppendableType.COMPLETE) { if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); } } else { // Only the identifier is parsed appendable = !expression.hasAs() && !expression.hasSpaceAfterIn() && !expression.hasLeftParenthesis() && !expression.hasRightParenthesis() && !expression.hasIdentificationVariable() && !expression.hasSpaceAfterRightParenthesis() && !expression.hasCollectionValuedPathExpression(); } } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberExpression expression) { // Only AND, OR, NOT or clause identifiers can be followed this expression appendable = (appendableType == AppendableType.LOGICAL || appendableType == AppendableType.COMPLETE) && expression.hasCollectionValuedPathExpression(); if (appendable) { boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; expression.getCollectionValuedPathExpression().accept(this); conditionalExpression = oldConditionalExpression; } } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { appendable = !conditionalExpression && !expression.endsWithDot(); } /** * {@inheritDoc} */ @Override public void visit(ComparisonExpression expression) { if (appendableType == AppendableType.ARITHMETIC || appendableType == AppendableType.COMPARISON || appendableType == AppendableType.COMPOUNDABLE) { appendable = false; } // "x = y |" <- AND and OR are valid proposals // "(x = y) |" <- AND and OR are valid proposals else if (appendableType == AppendableType.LOGICAL) { AppendableType oldAppendableType = appendableType; appendableType = AppendableType.COMPLETE; expression.getRightExpression().accept(this); appendableType = oldAppendableType; } else if (expression.hasRightExpression()) { boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; expression.getRightExpression().accept(this); conditionalExpression = oldConditionalExpression; } } /** * {@inheritDoc} */ @Override public void visit(ConcatExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(ConstructorExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(CountFunction expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(DateTime expression) { if (conditionalExpression && (appendableType == AppendableType.COMPLETE)) { appendable = false; } else { appendable = // Valid ending (!expression.isJDBCDate() || expression.toActualText().endsWith("}")) && // Valid AppendableType (appendableType == AppendableType.ARITHMETIC || appendableType == AppendableType.COMPARISON || appendableType == AppendableType.COMPLETE || appendableType == AppendableType.COMPOUNDABLE); } } /** * {@inheritDoc} */ @Override public void visit(DeleteClause expression) { if (expression.hasRangeVariableDeclaration()) { expression.getRangeVariableDeclaration().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(DeleteStatement expression) { if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else { expression.getDeleteClause().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(DivisionExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(EmptyCollectionComparisonExpression expression) { // Only AND, OR, NOT or clause identifiers can follow this expression // when the type is LOGICAL or CLAUSE appendable = appendableType == AppendableType.COMPLETE || appendableType == AppendableType.LOGICAL; } /** * {@inheritDoc} */ @Override public void visit(EntityTypeLiteral expression) { appendable = !conditionalExpression; } /** * {@inheritDoc} */ @Override public void visit(EntryExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(ExistsExpression expression) { // Only AND, OR, NOT or clause identifiers can follow this expression // when the type is LOGICAL or CLAUSE appendable = (appendableType == AppendableType.COMPLETE || appendableType == AppendableType.LOGICAL) && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { // Anything other than what's a valid conditional expression is not valid // "... WHERE AVG(a.ge) " <- Invalid and the following clauses cannot be appended appendable = false; } /** * {@inheritDoc} */ @Override public void visit(FromClause expression) { if (expression.hasAsOfClause()) { expression.getAsOfClause().accept(this); } else if (expression.hasHierarchicalQueryClause()) { expression.getHierarchicalQueryClause().accept(this); } else if (expression.hasDeclaration()) { clauseOfItems = true; expression.getDeclaration().accept(this); clauseOfItems = false; } } /** * {@inheritDoc} */ @Override public void visit(FunctionExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(GroupByClause expression) { if (expression.hasGroupByItems()) { clauseOfItems = true; expression.getGroupByItems().accept(this); clauseOfItems = false; } } /** * {@inheritDoc} */ @Override public void visit(HavingClause expression) { if (expression.hasConditionalExpression()) { conditionalExpression = true; expression.getConditionalExpression().accept(this); conditionalExpression = false; } } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { if (appendableType == AppendableType.COMPLETE) { appendable = true; } // The WHERE/HAVING clauses do not have a collection of expressions but has an // aggregation of expressions. If the appendable type is CLASUE, then the only // possible way for this to be valid is to have a collection of expressions where // the first child is the conditional expression and the second child is the // beginning of the following clause. // Example: "SELECT e FROM Employee e G|" <- appendable // Example: "SELECT e FROM Employee e WHERE e.phoneNumbers IS NOT E|" <- Not appendable else if (clauseOfItems || (!clauseOfItems && (appendableType == AppendableType.CLAUSE))) { appendable = !hasComma && (positionInCollection > -1) && !visitor.isFollowingInvalidExpression(expression); } else { // Special case if what's before is 'IS' or 'IS NOT', then it's not appendable if (positionInCollection > 1) { Expression child = collectionExpression.getChild(positionInCollection - 1); String text = child.toActualText(); appendable = !text.equals(IS) && !text.equals("IS NOT"); } else { switch (appendableType) { case ARITHMETIC: { appendable = false; break; } case SUBQUERY: case LOGICAL: case COMPOUNDABLE: { appendable = true; // appendable = (positionInCollection > -1) || subExpression; break; } case CLAUSE: { appendable = false; break; } case COMPARISON: case COMPLETE: { appendable = true; break; } } } } } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { // 1) The FROM clause needs to have more than one identification variable declaration // before the next clauses identifiers can be added. Example: "SELECT e FROM E" where // 'E' cannot be the beginning of a clause identifier // 2) The next clause identifiers cannot be added if there is a comma before the last // item. Example: "SELECT e FROM Employee e, I" where 'I' cannot be the beginning of // a clause identifier if ((appendableType != AppendableType.COMPLETE) && ((positionInCollection == -1) || hasComma)) { appendable = false; } else if (expression.hasJoins()) { expression.getJoins().accept(this); } else { expression.getRangeVariableDeclaration().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(IndexExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(InExpression expression) { // Only AND, OR, NOT or clause identifiers can follow this expression // when the type is LOGICAL or CLAUSE appendable = (appendableType == AppendableType.COMPLETE || appendableType == AppendableType.LOGICAL) && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(InputParameter expression) { appendable = !conditionalExpression; } /** * {@inheritDoc} */ @Override public void visit(Join expression) { if (expression.hasOnClause()) { expression.getOnClause().accept(this); } else if (expression.hasFetch()) { if (expression.hasAs()) { appendable = expression.hasIdentificationVariable(); } else { appendable = expression.hasJoinAssociationPath(); } } else { appendable = expression.hasIdentificationVariable(); } } /** * {@inheritDoc} */ @Override public void visit(KeyExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(KeywordExpression expression) { // Nothing can be added right after NULL, TRUE, FALSE appendable = !conditionalExpression && (appendableType == AppendableType.LOGICAL || appendableType == AppendableType.COMPLETE); } /** * {@inheritDoc} */ @Override public void visit(LengthExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(LikeExpression expression) { // Only AND, OR, NOT or clause identifiers can follow this expression // when the type is LOGICAL or CLAUSE appendable = appendableType == AppendableType.COMPLETE || appendableType == AppendableType.LOGICAL; if (appendable) { boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; if (expression.hasEscape()) { expression.getEscapeCharacter().accept(this); } else { expression.getPatternValue().accept(this); } conditionalExpression = oldConditionalExpression; } } /** * {@inheritDoc} */ @Override public void visit(LocateExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(LowerExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(MaxFunction expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(MinFunction expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(ModExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(MultiplicationExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(NotExpression expression) { // Example: "NOT B" can only have compound identifiers like 'NOT BETWEEN' if (expression.hasExpression()) { String variable = visitor.queryContext.literal( expression.getExpression(), LiteralType.IDENTIFICATION_VARIABLE ); if (variable != ExpressionTools.EMPTY_STRING) { appendable = (appendableType == AppendableType.COMPOUNDABLE); } else { boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; expression.getExpression().accept(this); conditionalExpression = oldConditionalExpression; } } } /** * {@inheritDoc} */ @Override public void visit(NullComparisonExpression expression) { // Only AND, OR, NOT or clause identifiers can follow this expression // when the type is LOGICAL or CLAUSE appendable = appendableType == AppendableType.COMPLETE || appendableType == AppendableType.LOGICAL; } /** * {@inheritDoc} */ @Override public void visit(NullIfExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(NumericLiteral expression) { appendable = !conditionalExpression; } /** * {@inheritDoc} */ @Override public void visit(ObjectExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(OnClause expression) { if (expression.hasConditionalExpression()) { conditionalExpression = true; expression.getConditionalExpression().accept(this); conditionalExpression = false; } } /** * {@inheritDoc} */ @Override public void visit(OrderByClause expression) { if (expression.hasOrderByItems()) { clauseOfItems = true; expression.getOrderByItems().accept(this); clauseOfItems = false; } } /** * {@inheritDoc} */ @Override public void visit(OrderByItem expression) { appendable = expression.hasSpaceAfterExpression() && expression.isDefault() || expression.hasSpaceAfterOrdering() && !expression.isNullsFirst() && !expression.isNullsLast(); } /** * {@inheritDoc} */ @Override public void visit(OrExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { if (appendableType == AppendableType.COMPLETE) { if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); } } else { // Only the abstract schema name is parsed appendable = !expression.hasSpaceAfterRootObject() && !expression.hasAs() && !expression.hasIdentificationVariable(); if (!appendable && expression.hasRootObject() && expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); } } } /** * {@inheritDoc} */ @Override public void visit(ResultVariable expression) { // The result variable is parsed without AS appendable = !expression.hasAs() && !expression.hasSpaceAfterAs() && expression.hasResultVariable(); } /** * {@inheritDoc} */ @Override public void visit(SelectClause expression) { if (expression.hasSelectExpression()) { clauseOfItems = true; expression.getSelectExpression().accept(this); clauseOfItems = false; } } /** * {@inheritDoc} */ @Override public void visit(SelectStatement expression) { if (expression.hasUnionClauses()) { expression.getUnionClauses().accept(this); } else if (expression.hasOrderByClause()) { expression.getOrderByClause().accept(this); } else if (expression.hasHavingClause()) { expression.getHavingClause().accept(this); } else if (expression.hasGroupByClause()) { expression.getGroupByClause().accept(this); } else if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else if (expression.hasFromClause()) { expression.getFromClause().accept(this); } else { expression.getSelectClause().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(SimpleFromClause expression) { if (expression.hasAsOfClause()) { expression.getAsOfClause().accept(this); } else if (expression.hasHierarchicalQueryClause()) { expression.getHierarchicalQueryClause().accept(this); } else if (expression.hasDeclaration()) { clauseOfItems = true; expression.getDeclaration().accept(this); clauseOfItems = true; } } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectClause expression) { if (expression.hasSelectExpression()) { clauseOfItems = true; expression.getSelectExpression().accept(this); clauseOfItems = true; } } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { if (subExpression && (appendableType == AppendableType.ARITHMETIC)) { appendable = true; } else { if (expression.hasHavingClause()) { expression.getHavingClause().accept(this); } else if (expression.hasGroupByClause()) { expression.getGroupByClause().accept(this); } else if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else if (expression.hasFromClause()) { expression.getFromClause().accept(this); } else { expression.getSelectClause().accept(this); } } } /** * {@inheritDoc} */ @Override public void visit(SizeExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(SqrtExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { // "e.employee." cannot be appended with something if (expression.endsWithDot()) { appendable = false; } // "... WHERE e.age " <- Invalid to be followed by another clause // "... GROUP BY e.age " <- Valid to be followed by another clause else if (appendableType == AppendableType.CLAUSE || appendableType == AppendableType.COMPLETE) { appendable = !conditionalExpression; } else { // Resolve the mapping so we can determine what is valid based on its type IMapping mapping = visitor.queryContext.getMapping(expression); if (mapping == null) { appendable = false; } else { IType type = mapping.getType(); switch (appendableType) { case ARITHMETIC: { // e.name (String) cannot be followed by +,-,/,* // e.age (int) can be followed by an arithmetic operator appendable = visitor.queryContext.getTypeHelper().isNumericType(type); break; } case COMPARISON: { TypeHelper typeHelper = visitor.queryContext.getTypeHelper(); appendable = !typeHelper.isCollectionType(type) && !typeHelper.isMapType(type); break; } case COMPOUNDABLE: { // The type will be calculated later appendable = true; break; } case LOGICAL: { appendable = visitor.queryContext.getTypeHelper().isBooleanType(type); break; } } } } } /** * {@inheritDoc} */ @Override public void visit(StringLiteral expression) { appendable = !conditionalExpression && expression.hasCloseQuote(); } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { if (appendableType == AppendableType.COMPLETE) { appendable = expression.hasRightParenthesis(); } else if (expression.hasExpression()) { subExpression = true; boolean oldConditionalExpression = conditionalExpression; conditionalExpression = false; expression.getExpression().accept(this); appendable &= expression.hasRightParenthesis(); subExpression = false; conditionalExpression = oldConditionalExpression; } else { // Only a subquery could be a valid proposal, everything // else cannot start an encapsulated expression appendable = (appendableType == AppendableType.SUBQUERY); } } /** * {@inheritDoc} */ @Override public void visit(SubstringExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(SubtractionExpression expression) { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(SumFunction expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(TreatExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(TrimExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(TypeExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(UpdateClause expression) { if (expression.hasUpdateItems()) { expression.getUpdateItems().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(UpdateItem expression) { expression.getNewValue().accept(this); } /** * {@inheritDoc} */ @Override public void visit(UpdateStatement expression) { if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else { expression.getUpdateClause().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(UpperExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(ValueExpression expression) { appendable = !conditionalExpression && expression.hasRightParenthesis(); } /** * {@inheritDoc} */ @Override public void visit(WhenClause expression) { if (expression.hasWhenExpression()) { conditionalExpression = true; expression.getWhenExpression().accept(this); conditionalExpression = false; } } /** * {@inheritDoc} */ @Override public void visit(WhereClause expression) { if (expression.hasConditionalExpression()) { conditionalExpression = true; expression.getConditionalExpression().accept(this); conditionalExpression = false; } } } // Made static for performance reasons. /** * This is used to determine how {@link AppendableExpressionVisitor} should perform the check. */ protected static enum AppendableType { /** * Determines whether the arithmetic operators (+, -, *, /) can be appended as valid proposals. */ ARITHMETIC, /** * Determines whether the JPQL identifiers identifying a clause (eg: <code><b>WHERE</b></code>) * can be appended as valid proposals. */ CLAUSE, /** * Determines whether the comparison operators {@literal (<, <=, <>, >=, =)} can be appended as valid proposals. */ COMPARISON, /** * Determines whether the visited {@link Expression} is grammatically complete by making sure * its ending is valid. */ COMPLETE, /** * Determines whether the compound identifiers (eg: <code><b>IS NULL</b></code>) can be * appended as valid proposals. */ COMPOUNDABLE, /** * Determines whether the logical identifiers (<code><b>AND</b></code> and <code><b>OR</b></code>) * can be appended as valid proposals. */ LOGICAL, /** * Determines whether the JPQL identifiers identifying a subquery (eg: <code><b>SELECT</b></code>) * can be appended as valid proposals. */ SUBQUERY } // Made static for performance reasons. /** * This helper is used to determine how to add proposals within a collection of expressions. Each * expression is usually separated by either a whitespace or by a comma. */ protected static interface CollectionExpressionHelper<T extends Expression> { /** * Adds the proposals because the cursor is at the end of the child at the given position. * * @param expression The {@link Expression} being visited * @param collectionExpression The {@link CollectionExpression} is either the child of the * given {@link Expression} or a temporary generated one that usually contains a single item * @param index The position of that child in the collection of children * @param hasComma Indicates whether a comma is present before the child at the given position; * if the index is 0, then this is <code>false</code> by default * @param virtualSpace Indicates if this method is called because the cursor is at the end of * the child at the specified index but by considering there is a virtual space at the end of * that child */ void addAtTheEndOfChild(T expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace); /** * Adds the given JPQL identifier as a valid proposal. * * @param expression The {@link Expression} being visited * @param identifier The JPQL identifier to add as a valid proposal */ void addIdentifier(T expression, String identifier); /** * Adds the proposals because the cursor is at the beginning of the child {@link Expression} * at the given position. * * @param expression The {@link Expression} being visited * @param collectionExpression The {@link CollectionExpression} is either the child of the * given {@link Expression} or a temporary generated one that usually contains a single item. * This can be null if the position is at the beginning * @param index The position of the child that was scanned * @param hasComma Indicates whether a comma is present before the child at the given position; * if the index is 0, then this is <code>false</code> by default */ void addTheBeginningOfChild(T expression, CollectionExpression collectionExpression, int index, boolean hasComma); /** * Either returns the given {@link Expression}'s child, which is already a {@link CollectionExpression} * or requests this helper to return a "virtual" {@link CollectionExpression} that is wrapping * the single element. * * @param expression The parent of the children to retrieve * @return The given expression's child or a "virtual" one */ CollectionExpression buildCollectionExpression(T expression); /** * Asks this helper if the search can continue even though two child expressions are not * separated by a comma. * * @param expression The {@link Expression} being visited * @param collectionExpression The {@link CollectionExpression} * @param index The position of the child being scanned * @return <code>true</code> if the check can continue even though the previous child was not * separated by a comma; <code>false</code> to stop the check */ boolean canContinue(T expression, CollectionExpression collectionExpression, int index); /** * Determines whether a delimiter like a whitespace or an open parenthesis was parsed after * the identifier. * * @param expression The {@link Expression} being visited * @return <code>true</code> if something is present; <code>false</code> otherwise */ boolean hasDelimiterAfterIdentifier(T expression); /** * Returns the maximum number of encapsulated {@link Expression expressions} the {@link Expression} * allows. Some expression only allow 2, others 3 and others allow an unlimited number. * * @param expression The {@link Expression} for which its maximum number of children * @return The maximum number of children the expression can have */ int maxCollectionSize(T expression); /** * Returns the length of anything that can be defined before the first child. An example can * be "<code>DISTINCT </code>" in "<code>AVG(DISTINCT e.name)</code>". * * @param expression The {@link Expression} being visited * @return The length of anything that was parsed before the first child or 0 if nothing was parsed */ int preExpressionLength(T expression); /** * Returns the {@link JPQLQueryBNF} that defines the fragment at the given position. * * @param expression The {@link Expression} being visited * @param index The position of the element to retrieve the BNF defined in the JPQL grammar * @return The {@link JPQLQueryBNF} that defines the fragment at the given position */ JPQLQueryBNF queryBNF(T expression, int index); } // Made static final for performance reasons. /** * This visitor retrieves the {@link CollectionExpression} if it is visited. */ protected static final class CollectionExpressionVisitor extends AbstractExpressionVisitor { /** * The {@link CollectionExpression} if it is the {@link Expression} that was visited. */ protected CollectionExpression expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class CollectionMappingFilter implements Filter<IMapping> { /** * {@inheritDoc} */ public boolean accept(IMapping value) { // Both association and collection field are accepted // Example: e.address is incomplete but it is not the entire path // Example: e.projects is the complete path return value.isRelationship(); } } // Made static final for performance reasons. protected static final class ConcatExpressionCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<ConcatExpression> { protected ConcatExpressionCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(ConcatExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // The only thing that is appendable is an arithmetic operator // Example: "SELECT e.name|" // Example: "SELECT e|" if (queryBNF(expression, index).handleAggregate()) { Expression child = collectionExpression.getChild(index); if ((index == 0) && !virtualSpace) { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } else { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } if (visitor.areComparisonSymbolsAppendable(child)) { visitor.addComparisonIdentifiers(child); } } } } /** * {@inheritDoc} */ public void addIdentifier(ConcatExpression expression, String identifier) { visitor.proposals.addIdentifier(identifier); visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(ConcatExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(queryBNF(expression, index)); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(ConcatExpression expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getExpression()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(ConcatExpression expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(ConcatExpression expression) { return expression.hasSpaceAfterIdentifier() || expression.hasLeftParenthesis(); } /** * {@inheritDoc} */ public int maxCollectionSize(ConcatExpression expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(ConcatExpression expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(ConcatExpression expression, int index) { return visitor.getQueryBNF(expression.getEncapsulatedExpressionQueryBNFId()); } } // Made static final for performance reasons. /** * This helpers handles adding proposals for {@link AbstractConditionalClause}. */ protected static final class ConditionalClauseCollectionHelper extends AbstractConditionalClauseCollectionHelper<AbstractConditionalClause> { protected ConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(AbstractConditionalClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConditionalExpression()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(AbstractConditionalClause expression) { return expression.hasSpaceAfterIdentifier(); } } // Made static final for performance reasons. protected static final class ConstrutorCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<ConstructorExpression> { protected ConstrutorCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(ConstructorExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // The only thing that is appendable is an arithmetic operator // Example: "SELECT e.name|" // Example: "SELECT e|" if (queryBNF(expression, index).handleAggregate()) { Expression child = collectionExpression.getChild(index); if ((index == 0) && !virtualSpace) { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } else { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } if (visitor.areComparisonSymbolsAppendable(child)) { visitor.addComparisonIdentifiers(child); } } } } /** * {@inheritDoc} */ public void addIdentifier(ConstructorExpression expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(ConstructorExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addIdentificationVariables(expression, IdentificationVariableType.ALL); visitor.addFunctionIdentifiers(ConstructorItemBNF.ID); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(ConstructorExpression expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConstructorItems()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(ConstructorExpression expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(ConstructorExpression expression) { return expression.hasLeftParenthesis(); } /** * {@inheritDoc} */ public int maxCollectionSize(ConstructorExpression expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(ConstructorExpression expression) { if (expression.hasSpaceAfterNew()) { return expression.getClassName().length() + SPACE_LENGTH; } return expression.getClassName().length(); } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(ConstructorExpression expression, int index) { return visitor.getQueryBNF(ConstructorItemBNF.ID); } } // Made static final for performance reasons. protected static final class DeclarationVisitor extends AnonymousExpressionVisitor { /** * Indicates if the visited {@link CollectionValuedPathExpression} is found within a * declaration expression. */ protected boolean declaration; /** * Disposes of the internal data. */ public void dispose() { declaration = false; } /** * Determines whether the visited path expression is found within a declaration expression. * * @return <code>true</code> if the visited {@link CollectionValuedPathExpression} is owned by * a {@link RangeVariableDeclaration}, which indicates it is used to define the "root" object; * <code>false</code> if it is not */ public boolean isDeclaration() { return declaration; } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { declaration = true; } } // Made static final for performance reasons. /** * The default implementation of {@link MappingCollector}, which simply returns an empty collection. */ protected static final class DefaultMappingCollector implements MappingCollector { /** * {@inheritDoc} */ public Collection<IMapping> buildProposals() { return Collections.emptyList(); } } // Made static final for performance reasons. protected static final class DeleteClauseCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<DeleteClause> { protected DeleteClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(DeleteClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { } /** * {@inheritDoc} */ public void addIdentifier(DeleteClause expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(DeleteClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { if (index == 0) { visitor.addEntities(); } } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(DeleteClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getRangeVariableDeclaration()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(DeleteClause expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(DeleteClause expression) { return expression.hasSpaceAfterFrom(); } /** * {@inheritDoc} */ public int maxCollectionSize(DeleteClause expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(DeleteClause expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(DeleteClause expression, int index) { return visitor.getQueryBNF(RangeVariableDeclarationBNF.ID); } } // Made static final for performance reasons. protected static final class DeleteClauseStatementHelper extends AbstractVisitorHelper implements StatementHelper<DeleteStatement> { protected DeleteClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(DELETE_FROM); } /** * {@inheritDoc} */ public void addInternalClauseProposals(DeleteStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(DeleteStatement expression) { return expression.getDeleteClause(); } /** * {@inheritDoc} */ public WhereClauseDeleteStatementHelper getNextHelper() { return visitor.getWhereClauseDeleteStatementHelper(); } /** * {@inheritDoc} */ public boolean hasClause(DeleteStatement expression) { return true; } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(DeleteStatement expression) { return expression.hasSpaceAfterDeleteClause(); } /** * {@inheritDoc} */ public boolean isClauseComplete(DeleteStatement expression) { return visitor.isComplete(expression.getDeleteClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return true; } } // Made static final for performance reasons. protected static final class DifferentComparisonFilter extends AnonymousExpressionVisitor implements Filter<Expression> { /** * Determines whether {@literal '<', '<=', '>=', '>'} are valid comparison operators. */ protected boolean valid; /** * {@inheritDoc} */ public boolean accept(Expression expression) { try { expression.accept(this); return valid; } finally { valid = false; } } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { valid = true; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { valid = false; } } // Made static final for performance reasons. protected static final class DoubleEncapsulatedCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractDoubleEncapsulatedExpression> { protected DoubleEncapsulatedCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(AbstractDoubleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // The only thing that is appendable is an arithmetic operator // Example: "SELECT e.name|" // Example: "SELECT e|" if (queryBNF(expression, index).handleAggregate()) { Expression child = collectionExpression.getChild(index); if ((index == 0) && !virtualSpace) { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } else { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } if (visitor.areComparisonSymbolsAppendable(child)) { visitor.addComparisonIdentifiers(child); } } } } /** * {@inheritDoc} */ public void addIdentifier(AbstractDoubleEncapsulatedExpression expression, String identifier) { visitor.proposals.addIdentifier(identifier); visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(AbstractDoubleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(queryBNF(expression, index)); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(AbstractDoubleEncapsulatedExpression expression) { return expression.buildCollectionExpression(); } /** * {@inheritDoc} */ public boolean canContinue(AbstractDoubleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(AbstractDoubleEncapsulatedExpression expression) { return expression.hasSpaceAfterIdentifier() || expression.hasLeftParenthesis(); } /** * {@inheritDoc} */ public int maxCollectionSize(AbstractDoubleEncapsulatedExpression expression) { // Both MOD and NULLIF allows a fixed 2 encapsulated expressions return 2; } /** * {@inheritDoc} */ public int preExpressionLength(AbstractDoubleEncapsulatedExpression expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(AbstractDoubleEncapsulatedExpression expression, int index) { return visitor.getQueryBNF(expression.parameterExpressionBNF(index)); } } // Made static final for performance reasons. protected static final class EncapsulatedExpressionVisitor extends AnonymousExpressionVisitor { /** * Determines whether the visited {@link Expression} is being encapsulated or not. */ protected boolean encapsulated; /** * Internal flag that prevent infinite recursion. */ protected boolean visited; /** * Disposes of the internal data. */ public void dispose() { encapsulated = false; } /** * Determines whether the visited {@link Expression} is being encapsulated or not. * * @return <code>true</code> if the visited {@link Expression} is within parenthesis; * <code>false</code> otherwise */ public boolean isEncapsulated() { return encapsulated; } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { if (!visited) { visited = true; expression.getParent().accept(this); visited = false; } } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { encapsulated = true; } } // Made static for performance reasons. /** * This builder populates a {@link QueryPosition} by traversing the valid portion of the JPQL * query. The position is the end of each {@link Expression}. * <p> * For instance, "SELECT e FROM Employee e O WHERE e.name = 'JPQL'", the valid fragment is * "SELECT e FROM Employee e", the positions will be: * <ul> * <li>JPQLExpression = 24</li> * <li>SelectStatement = 24</li> * <li>FromClause = 15</li> * <li>IdentificationVariableDeclaration = 10</li> * <li>RangeVariableDeclaration = 10</li> * <li>IdentificationVariable = 1</li> * </ul> */ protected static class EndingQueryPositionBuilder extends AbstractVisitorHelper implements ExpressionVisitor { /** * This internal flag helps to determine if the {@link Expression} where the cursor is located * was flagged to be invalid. If so, this helps to determine how to handle the calculation of * the new position within the query. * <p> * For instance: "SELECT e FROM Employee e AS" has a bad expression wrapping the identifier * "AS", which is parsed as a result variable. In this case, the position would actually be * 2 within the bad expression. If the bad expression was something more complex than just a * single word, then that expression should not be included in the position. */ protected boolean badExpression; /** * This is used to correct the length of an {@link Expression} */ protected int correction; /** * The {@link Expression} containing the invalid fragment. */ protected Expression invalidExpression; /** * The position of the cursor within the invalid expression. */ protected int positionWithinInvalidExpression; /** * This {@link QueryPosition} has the position of each {@link Expression} within the valid * fragment of the JPQL query. */ public QueryPosition queryPosition; /** * Indicates whether a virtual space should be added to the stack or not. */ public boolean virtualSpace; protected EndingQueryPositionBuilder(AbstractContentAssistVisitor visitor) { super(visitor); } /** * Disposes the internal data. */ public void dispose() { correction = 0; virtualSpace = false; queryPosition = null; invalidExpression = null; positionWithinInvalidExpression = -1; } /** * Returns the new {@link QueryPosition} that was created. * * @return */ public QueryPosition getQueryPosition() { return queryPosition; } /** * Determines whether a virtual space should be added to the stack or not. * * @return <code>true</code> if a virtual space should be considered; <code>false</code> otherwise */ public boolean hasVirtualSpace() { return virtualSpace; } /** * Prepares this visitor before visiting an {@link Expression}. {@link #dispose()} is called * after the visit operation is complete. * * @param invalidExpression The {@link Expression} containing the invalid fragment */ public void prepare(Expression invalidExpression) { QueryPosition oldQueryPosition = visitor.queryPosition; this.invalidExpression = invalidExpression; this.positionWithinInvalidExpression = oldQueryPosition.getPosition(invalidExpression); this.queryPosition = new QueryPosition(oldQueryPosition.getPosition(invalidExpression.getParent())); } /** * {@inheritDoc} */ public void visit(AbsExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(AbstractSchemaName expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; } if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(AdditionExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(AllOrAnyExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(AndExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(ArithmeticFactor expression) { if (!badExpression) { if (expression.hasExpression()) { expression.getExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(AvgFunction expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(BadExpression expression) { badExpression = true; expression.getExpression().accept(this); badExpression = false; } /** * {@inheritDoc} */ public void visit(BetweenExpression expression) { if (badExpression) { if (!expression.hasExpression() && !expression.hasNot() && positionWithinInvalidExpression <= 7 /* BETWEEN */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasUpperBoundExpression()) { expression.getUpperBoundExpression().accept(this); } else if (expression.hasLowerBoundExpression() && !expression.hasAnd()) { expression.getLowerBoundExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(CaseExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= 4 /* CASE */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (!expression.hasEnd()) { if (expression.hasElseExpression()) { expression.getElseExpression().accept(this); } else if (expression.hasWhenClauses()) { expression.getWhenClauses(); } else if (expression.hasCaseOperand()) { expression.getCaseOperand().accept(this); } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(CoalesceExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(CollectionExpression expression) { if (!badExpression) { // First find the index of the child expression owning the invalid fragment int index = 0; for (Expression child : expression.children()) { if (child.isAncestor(invalidExpression)) { break; } index++; } // This visitor is not used for an invalid expression but for looking at the // end of an expression and the whitespace is owned by a parent expression if (index == expression.childrenSize()) { index--; } // Now traverse the child Expression child = expression.getChild(index); child.accept(this); // Rather than marking the CollectionExpression as the Expression to visit // with the adjusted QueryPosition, the child that to the left of the invalid // expression will be used instead. // Example: "SELECT e FROM Employee e WHERE CONCAT(e.name, A|S a)" <- | is the cursor // In this example, "CONCAT(e.name, AS a)" is wrapped with a BadExpression, but // "CONCAT(e.name, " is actually valid so this will allow the new QueryPosition // to be "inside" the CONCAT expression and the proposals will be available if (index > 0) { Expression previousChild = expression.getChild(index - 1); if (!visitor.isComplete(previousChild)) { queryPosition.setExpression(previousChild); queryPosition.addPosition(previousChild, previousChild.getLength()); } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } // Adjust the length so it's within the collection expression // up to the position of the cursor int length = expression.toActualText(index + 1).length() - correction; queryPosition.addPosition(expression, length); // Now reset the correction so the parent expression does // not use the entire length of CollectionExpression correction = expression.getLength() - length; } } /** * {@inheritDoc} */ public void visit(CollectionMemberDeclaration expression) { if (badExpression) { if (positionWithinInvalidExpression <= 2 /* IN */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); } else if (expression.hasCollectionValuedPathExpression() && !expression.hasAs()) { expression.getCollectionValuedPathExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(CollectionMemberExpression expression) { if (badExpression) { if (!expression.hasEntityExpression() && !expression.hasNot() && positionWithinInvalidExpression <= 6 /* MEMBER */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasCollectionValuedPathExpression()) { expression.getCollectionValuedPathExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(CollectionValuedPathExpression expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; } if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } /** * {@inheritDoc} */ public void visit(ComparisonExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(ConcatExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(ConstructorExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= 3 /* NEW */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasConstructorItems() && !expression.hasRightParenthesis()) { expression.getConstructorItems().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(CountFunction expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(DateTime expression) { if (!expression.isJDBCDate()) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } else if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } } /** * {@inheritDoc} */ public void visit(DeleteClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 6/* DELETE */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasRangeVariableDeclaration()) { expression.getRangeVariableDeclaration().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(DeleteStatement expression) { if (!badExpression) { if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else { expression.getDeleteClause().accept(this); if (expression.hasSpaceAfterDeleteClause()) { virtualSpace = true; } } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(DivisionExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(EmptyCollectionComparisonExpression expression) { if (badExpression) { if (!expression.hasExpression() && !expression.hasNot() && positionWithinInvalidExpression <= 5 /* EMPTY */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } } /** * {@inheritDoc} */ public void visit(EntityTypeLiteral expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; } if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } /** * {@inheritDoc} */ public void visit(EntryExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(ExistsExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(FromClause expression) { visitAbstractFromClause(expression); } /** * {@inheritDoc} */ public void visit(FunctionExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(GroupByClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 5 /* GROUP */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasGroupByItems()) { expression.getGroupByItems().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(HavingClause expression) { visitAbstractConditionalClause(expression); } /** * {@inheritDoc} */ public void visit(IdentificationVariable expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; } queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } /** * {@inheritDoc} */ public void visit(IdentificationVariableDeclaration expression) { if (!badExpression) { if (expression.hasJoins()) { expression.getJoins().accept(this); } else if (expression.hasRangeVariableDeclaration()) { expression.getRangeVariableDeclaration().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(IndexExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(InExpression expression) { if (badExpression) { if (!expression.hasExpression() && positionWithinInvalidExpression <= 2 /* IN */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasInItems() && !expression.hasRightParenthesis()) { expression.getInItems(); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(InputParameter expression) { if (!badExpression) { if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } } /** * {@inheritDoc} */ public void visit(Join expression) { if (badExpression) { if (positionWithinInvalidExpression <= 4 /* JOIN */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasOnClause()) { expression.getOnClause().accept(this); } else if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); if (expression.hasSpaceAfterIdentificationVariable()) { virtualSpace = true; } } else if (expression.hasJoinAssociationPath() && !expression.hasAs()) { expression.getJoinAssociationPath().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(JPQLExpression expression) { if (expression.hasQueryStatement()) { Expression queryStatement = expression.getQueryStatement(); queryStatement.accept(this); queryPosition.addPosition(expression, queryStatement.getLength() - correction); } else { queryPosition.setExpression(expression); queryPosition.addPosition(expression, 0); } } /** * {@inheritDoc} */ public void visit(KeyExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(KeywordExpression expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } else if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } /** * {@inheritDoc} */ public void visit(LengthExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(LikeExpression expression) { if (badExpression) { if (!expression.hasStringExpression() && positionWithinInvalidExpression <= 4 /* LIKE */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasEscapeCharacter()) { expression.getEscapeCharacter().accept(this); } else if (expression.hasPatternValue() && !expression.hasEscape()) { expression.getPatternValue().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(LocateExpression expression) { visitAbstractTripleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(LowerExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(MaxFunction expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(MinFunction expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(ModExpression expression) { visitAbstractDoubleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(MultiplicationExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(NotExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= 3 /* NOT */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasExpression()) { expression.getExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(NullComparisonExpression expression) { if (badExpression) { if (!expression.hasExpression() && !expression.hasNot() && positionWithinInvalidExpression <= 2) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasExpression()) { expression.getExpression().accept(this); } queryPosition.setExpression(expression); queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(NullExpression expression) { if (!badExpression) { if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, 0); } } } /** * {@inheritDoc} */ public void visit(NullIfExpression expression) { visitAbstractDoubleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(NumericLiteral expression) { if (!badExpression) { if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } } /** * {@inheritDoc} */ public void visit(ObjectExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(OnClause expression) { visitAbstractConditionalClause(expression); } /** * {@inheritDoc} */ public void visit(OrderByClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 5 /* ORDER */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasOrderByItems()) { expression.getOrderByItems().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(OrderByItem expression) { if (!badExpression) { if (expression.hasExpression()) { expression.getExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(OrExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(RangeVariableDeclaration expression) { if (!badExpression) { if (expression.hasIdentificationVariable()) { expression.getIdentificationVariable().accept(this); } else if (!expression.hasAs()) { expression.getRootObject().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(ResultVariable expression) { if (badExpression) { // Special case for a result variable with nothing but the AS identifier if (!expression.hasResultVariable() && expression.hasAs() || !expression.hasSelectExpression()) { correction = expression.getLength() - positionWithinInvalidExpression; queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasResultVariable()) { expression.getResultVariable().accept(this); } else if (!expression.hasAs()) { expression.getSelectExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(SelectClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 6 /* SELECT */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasSelectExpression()) { expression.getSelectExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(SelectStatement expression) { if (!badExpression) { if (expression.hasUnionClauses()) { expression.getUnionClauses().accept(this); } else if (expression.hasOrderByClause()) { expression.getOrderByClause().accept(this); if (expression.hasSpaceBeforeUnion()) { virtualSpace = true; } } else if (expression.hasHavingClause()) { expression.getHavingClause().accept(this); if (expression.hasSpaceBeforeOrderBy()) { virtualSpace = true; } } else if (expression.hasGroupByClause()) { expression.getGroupByClause().accept(this); if (expression.hasSpaceAfterGroupBy()) { virtualSpace = true; } } else if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); if (expression.hasSpaceAfterWhere()) { virtualSpace = true; } } else if (expression.hasFromClause()) { expression.getFromClause().accept(this); if (expression.hasSpaceAfterFrom()) { virtualSpace = true; } } else { expression.getSelectClause().accept(this); if (expression.hasSpaceAfterSelect()) { virtualSpace = true; } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(SimpleFromClause expression) { visitAbstractFromClause(expression); } /** * {@inheritDoc} */ public void visit(SimpleSelectClause expression) { if (!badExpression) { if (expression.hasSelectExpression()) { expression.getSelectExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(SimpleSelectStatement expression) { if (!badExpression) { if (expression.hasHavingClause()) { expression.getHavingClause().accept(this); } else if (expression.hasGroupByClause()) { expression.getGroupByClause().accept(this); if (expression.hasSpaceAfterGroupBy()) { virtualSpace = true; } } else if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); if (expression.hasSpaceAfterWhere()) { virtualSpace = true; } } else if (expression.hasFromClause()) { expression.getFromClause().accept(this); if (expression.hasSpaceAfterFrom()) { virtualSpace = true; } } else { expression.getSelectClause().accept(this); if (expression.hasSpaceAfterSelect()) { virtualSpace = true; } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(SizeExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(SqrtExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(StateFieldPathExpression expression) { if (badExpression) { correction = expression.getLength() - positionWithinInvalidExpression; } if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(StringLiteral expression) { if (!badExpression) { if (invalidExpression == expression) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } } /** * {@inheritDoc} */ public void visit(SubExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(SubstringExpression expression) { visitAbstractTripleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(SubtractionExpression expression) { visitCompoundExpression(expression); } /** * {@inheritDoc} */ public void visit(SumFunction expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(TreatExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= 5 /* TREAT */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasEntityType() && !expression.hasRightParenthesis()) { expression.getEntityType().accept(this); } else if (expression.hasCollectionValuedPathExpression() && !expression.hasAs()) { expression.getCollectionValuedPathExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(TrimExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(TypeExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(UnknownExpression expression) { // Nothing to do, this is the expression that needs // to be handled by the valid portion of the JPQL query } /** * {@inheritDoc} */ public void visit(UpdateClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 6 /* UPDATE */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasUpdateItems()) { expression.getUpdateItems().accept(this); } else if (expression.hasRangeVariableDeclaration()) { expression.getRangeVariableDeclaration().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(UpdateItem expression) { if (!badExpression) { if (expression.hasNewValue()) { expression.getNewValue().accept(this); } else if (!expression.hasEqualSign() && expression.hasSpaceAfterStateFieldPathExpression()) { expression.getStateFieldPathExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(UpdateStatement expression) { if (!badExpression) { if (expression.hasWhereClause()) { expression.getWhereClause().accept(this); } else { expression.getUpdateClause().accept(this); if (expression.hasSpaceAfterUpdateClause()) { virtualSpace = true; } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(UpperExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(ValueExpression expression) { visitAbstractSingleEncapsulatedExpression(expression); } /** * {@inheritDoc} */ public void visit(WhenClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 4 /* WHEN */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasThenExpression()) { expression.getThenExpression().accept(this); } else if (expression.hasWhenExpression() && expression.hasThen()) { expression.getWhenExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } /** * {@inheritDoc} */ public void visit(WhereClause expression) { visitAbstractConditionalClause(expression); } protected void visitAbstractConditionalClause(AbstractConditionalClause expression) { if (!badExpression) { if (expression.hasConditionalExpression()) { expression.getConditionalExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } protected void visitAbstractDoubleEncapsulatedExpression(AbstractDoubleEncapsulatedExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= expression.getIdentifier().length()) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasSecondExpression() && !expression.hasRightParenthesis()) { expression.getSecondExpression().accept(this); } else if (expression.hasFirstExpression() && !expression.hasSpaceAfterComma()) { expression.getFirstExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } protected void visitAbstractFromClause(AbstractFromClause expression) { if (badExpression) { if (positionWithinInvalidExpression <= 4 /* FROM */) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasAsOfClause()) { expression.getAsOfClause().accept(this); } else if (expression.hasHierarchicalQueryClause()) { expression.getHierarchicalQueryClause().accept(this); if (expression.hasSpaceAfterHierarchicalQueryClause()) { virtualSpace = true; } } else if (expression.hasDeclaration()) { expression.getDeclaration().accept(this); if (expression.hasSpaceAfterDeclaration()) { virtualSpace = true; } } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } protected void visitAbstractSingleEncapsulatedExpression(AbstractSingleEncapsulatedExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= expression.getIdentifier().length()) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasEncapsulatedExpression() && !expression.hasRightParenthesis()) { expression.getExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } protected void visitAbstractTripleEncapsulatedExpression(AbstractTripleEncapsulatedExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= expression.getIdentifier().length()) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasThirdExpression() && !expression.hasRightParenthesis()) { expression.getThirdExpression().accept(this); } else if (expression.hasSecondExpression() && !expression.hasSecondComma()) { expression.getSecondExpression().accept(this); } else if (expression.hasFirstExpression() && !expression.hasFirstComma()) { expression.getFirstExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } protected void visitCompoundExpression(CompoundExpression expression) { if (badExpression) { if (positionWithinInvalidExpression <= expression.getIdentifier().length() && !expression.hasLeftExpression()) { queryPosition.setExpression(expression); queryPosition.addPosition(expression, positionWithinInvalidExpression); } } else { if (expression.hasRightExpression()) { expression.getRightExpression().accept(this); } if (queryPosition.getExpression() == null) { queryPosition.setExpression(expression); } queryPosition.addPosition(expression, expression.getLength() - correction); } } } // Made static final for performance reasons. /** * This visitor determines whether a path expression can be resolved as a fully qualified enum * type and an enum constant. * <p> * The valid locations are: * <ul> * <li>{@link CollectionMemberExpression} : entity_or_value_expression (before <code><b>MEMBER</b></code> identifier);</li> * <li>{@link InExpression} : One of the items;</li> * <li>{@link CaseExpression} : The <code><b>ELSE</b> expression</code>;</li> * <li>{@link WhenClause} : The <code><b>WHEN</b></code> or <code><b>THEN</b></code> expressions;</li> * <li>{@link FunctionExpression} : One of the function items;</li> * <li>{@link ComparisonExpression} : The left or right expression if the comparison identifier * is either <code><b>=</b></code> or <code><b><></b></code>;</li> * <li>{@link UpdateItem} : The new value;</li> * <li>{@link ConstructorExpression} : One of the constructor items;</li> * <li>{@link CoalesceExpression} : The expression at index 1 or greater;</li> * <li>{@link NullIfExpression} : The second expression;</li> * </ul> */ protected static final class EnumVisitor extends AbstractExpressionVisitor { /** * The {@link AbstractPathExpression} being scanned for its location within the JPQL query. */ protected AbstractPathExpression pathExpression; /** * Determines whether the path expression could potentially represent a fully qualified * enum constant, which is dictated by the location of the path expression within the query. * Only a few location allows an enum constant. */ protected boolean valid; /** * Disposes of the internal data. */ public void dispose() { valid = false; pathExpression = null; } /** * Determines whether the path expression could potentially represent a fully qualified * enum constant, which is dictated by the location of the path expression within the query. * Only a few location allows an enum constant. * * @return <code>true</code> if the path expression represents a enum constant; * <code>false</code> otherwise */ public boolean isValid() { return valid; } /** * {@inheritDoc} */ @Override public void visit(CaseExpression expression) { valid = (pathExpression == expression.getElseExpression()); } /** * {@inheritDoc} */ @Override public void visit(CoalesceExpression expression) { // TODO valid = (pathExpression == expression.getExpression()); } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberExpression expression) { valid = (pathExpression == expression.getEntityExpression()); } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(ComparisonExpression expression) { String identifier = expression.getComparisonOperator(); valid = ((identifier == EQUAL) || (identifier == DIFFERENT)); } /** * {@inheritDoc} */ @Override public void visit(ConstructorExpression expression) { valid = true; } /** * {@inheritDoc} */ @Override public void visit(FunctionExpression expression) { valid = true; } /** * {@inheritDoc} */ @Override public void visit(InExpression expression) { valid = (pathExpression != expression.getExpression()); } /** * {@inheritDoc} */ @Override public void visit(NullIfExpression expression) { valid = (pathExpression == expression.getSecondExpression()); } /** * {@inheritDoc} */ @Override public void visit(StateFieldPathExpression expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(UpdateItem expression) { valid = (pathExpression == expression.getNewValue()); } /** * {@inheritDoc} */ @Override public void visit(WhenClause expression) { valid = (pathExpression == expression.getThenExpression() || pathExpression == expression.getWhenExpression()); } } // Made static final for performance reasons. /** * This {@link MappingCollector} returns the possible mappings (non-collection type or * collection type) from a managed type. */ protected static final class FilteringMappingCollector implements MappingCollector { /** * The {@link Filter} used to filter out either the collection type properties or the non- * collection type properties. */ protected final Filter<IMapping> filter; /** * This resolver is used to retrieve the managed type, which is the parent path of this one. */ protected final Resolver resolver; /** * The suffix is used to determine if the mapping name needs to be filtered out or not. */ protected final String suffix; /** * Creates a new <code>FilteringMappingCollector</code>. * * @param resolver This resolver is used to retrieve the managed type, which is the parent * path of this one * @param filter The filter used to filter out either the collection type properties or the * non-collection type properties * @param suffix The suffix is used to determine if the property name needs to be filtered out * or not */ FilteringMappingCollector(Resolver resolver, Filter<IMapping> filter, String suffix) { super(); this.filter = filter; this.suffix = suffix; this.resolver = resolver; } protected void addFilteredMappings(IManagedType managedType, List<IMapping> mappings) { Filter<IMapping> filter = buildFilter(suffix); for (IMapping mapping : managedType.mappings()) { if (filter.accept(mapping)) { mappings.add(mapping); } } } protected Filter<IMapping> buildFilter(String suffix) { if (suffix.length() == 0) { return filter; } return new AndFilter<IMapping>(filter, buildMappingNameFilter(suffix)); } protected Filter<IMapping> buildMappingNameFilter(final String suffix) { return new Filter<IMapping>() { public boolean accept(IMapping mapping) { return mapping.getName().startsWith(suffix); } }; } /** * {@inheritDoc} */ public Collection<IMapping> buildProposals() { IManagedType managedType = resolver.getManagedType(); if (managedType == null) { return Collections.emptyList(); } ArrayList<IMapping> mappings = new ArrayList<IMapping>(); addFilteredMappings(managedType, mappings); return mappings; } } // Made static for performance reasons. protected static class FollowingClausesVisitor extends AbstractTraverseParentVisitor { /** * The JPQL identifier of the clause used to determine if there is any clause defined after it. */ protected String afterIdentifier; /** * The JPQL identifier of the clause used to determine if there is any clause defined before it. */ protected String beforeIdentifier; /** * Determines whether there is at least one clause defined after the clause defined by {@link #afterIdentifier}. */ protected boolean hasFollowUpClauses; /** * Disposes of the internal data. */ public void dispose() { afterIdentifier = null; beforeIdentifier = null; hasFollowUpClauses = false; } /** * Determines if the <code><b>FROM</b></code> clause has been defined or not. The end limit * of the check is also taken into consideration. * * @param expression The <code><b>SELECT</b></code> expression being scanned for what has been * defined between the range of clauses * @return <code>true</code> if the <code><b>FROM</b></code> clause is defined; * <code>false</code> otherwise */ protected boolean hasFromClause(AbstractSelectStatement expression) { // No need to check if the check is after the clause itself return (afterIdentifier != FROM) && expression.hasFromClause(); } /** * {@inheritDoc} */ @Override public void visit(SelectStatement expression) { if (afterIdentifier == SELECT) { if (beforeIdentifier == WHERE) { hasFollowUpClauses = hasFromClause(expression); } else if (beforeIdentifier == GROUP_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause(); } else if (beforeIdentifier == HAVING) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause(); } else if (beforeIdentifier == ORDER_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause() || expression.hasOrderByClause(); } else { hasFollowUpClauses = hasFromClause(expression); } } else if (afterIdentifier == FROM) { if (beforeIdentifier == GROUP_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause(); } else if (beforeIdentifier == HAVING) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause(); } else if (beforeIdentifier == ORDER_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause() || expression.hasOrderByClause(); } else { hasFollowUpClauses = hasFromClause(expression); } } else if (afterIdentifier == WHERE) { if (beforeIdentifier == HAVING) { hasFollowUpClauses = expression.hasGroupByClause(); } else if (beforeIdentifier == ORDER_BY) { hasFollowUpClauses = expression.hasGroupByClause() || expression.hasHavingClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = expression.hasGroupByClause() || expression.hasHavingClause() || expression.hasOrderByClause(); } } else if (afterIdentifier == GROUP_BY) { if (beforeIdentifier == ORDER_BY) { hasFollowUpClauses = expression.hasHavingClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = expression.hasHavingClause() || expression.hasOrderByClause(); } } else if (afterIdentifier == HAVING) { hasFollowUpClauses = expression.hasOrderByClause(); } else { hasFollowUpClauses = hasFromClause(expression); } } /** * {@inheritDoc} */ @Override public void visit(SimpleFromClause expression) { expression.getParent().accept(this); } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { if (afterIdentifier == SELECT) { if (beforeIdentifier == WHERE) { hasFollowUpClauses = hasFromClause(expression); } else if (beforeIdentifier == GROUP_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause(); } else if (beforeIdentifier == HAVING) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause(); } } else if (afterIdentifier == FROM) { if (beforeIdentifier == GROUP_BY) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause(); } else if (beforeIdentifier == HAVING) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = hasFromClause(expression) || expression.hasWhereClause() || expression.hasGroupByClause() || expression.hasHavingClause(); } } else if (afterIdentifier == WHERE) { if (beforeIdentifier == HAVING) { hasFollowUpClauses = expression.hasGroupByClause(); } else if (beforeIdentifier == null) { hasFollowUpClauses = expression.hasGroupByClause() || expression.hasHavingClause(); } } } } // Made static final for performance reasons. protected static final class FollowingInvalidExpressionVisitor extends AbstractTraverseParentVisitor { /** * Enclosing visitor instance. */ protected final AbstractContentAssistVisitor visitor; /** * The {@link Expression} used to determine if it follows an invalid fragment or not. */ protected Expression expression; /** * Determines whether the visited {@link Expression} is preceded by an invalid expression. */ protected boolean followingInvalidExpression; protected FollowingInvalidExpressionVisitor(AbstractContentAssistVisitor visitor) { super(); this.visitor = visitor; } /** * Disposes of the internal data. */ public void dispose() { expression = null; followingInvalidExpression = false; } /** * Determines whether the visited {@link Expression} is preceded by an invalid expression. * * @return <code>true</code> if the visited {@link Expression} is part of a collection of * expressions and an invalid expression precede it; <code>false</code> otherwise */ public boolean isFollowingInvalidExpression() { return followingInvalidExpression; } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { int index = expression.childrenSize(); // Find the index of the expression for which the tree is been visited while (--index >= 0) { Expression child = expression.getChild(index); if (child == this.expression) { break; } } // Check to see if an expression before the index is invalid while (--index >= 0) { Expression child = expression.getChild(index); followingInvalidExpression = visitor.isInvalidExpression(child); if (followingInvalidExpression) { index = -1; } } } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { this.expression = expression; super.visit(expression); } } // Made static for performance reasons. protected static class FromClauseCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractFromClause> { protected FromClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(AbstractFromClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // At the end of a range variable declaration, JOIN clauses can be added // Example: "SELECT e FROM Employee e |" // Example: "SELECT e FROM Employee e, Address a |" if (((index == 0) || hasComma) && virtualSpace) { // "... JOIN e.mananager m |" <- Valid to add JOIN identifiers // "... JOIN e.mananager m ON e.name |" <- Invalid to add JOIN identifiers if (visitor.isComplete(collectionExpression.getChild(0))) { visitor.addJoinIdentifiers(); visitor.addIdentifier(ON); } } // Special case to handle a range variable declaration that can also // be either the beginning of the following clauses else { boolean end = (index + 1 == collectionExpression.childrenSize()); if ((index > 0) && end && !hasComma) { int position = visitor.queryPosition.getPosition(); if (!visitor.hasClausesDefinedBetween(expression, FROM, HAVING)) { visitor.addCompositeIdentifier(GROUP_BY, 4 /* GROUP - 1 */); } if (!visitor.hasClausesDefinedBetween(expression, FROM, ORDER_BY)) { visitor.addCompositeIdentifier(ORDER_BY, 4 /* ORDER - 1 */); } } } } /** * {@inheritDoc} */ public void addIdentifier(AbstractFromClause expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(AbstractFromClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { // 1. At the beginning of the FROM declaration, entities are valid proposals // 2. To be valid elsewhere, the declarations have to be separated by a comma // "SELECT e FROM Employee e W|" <- entity names are not valid proposals, only 'WHERE' is if ((index == 0) || hasComma) { visitor.addEntities(); } // In any other case, the JOIN identifiers are the only valid choices // "SELECT e FROM Employee e J|" <- only 'JOIN' and 'JOIN FETCH' are valid proposals else if ((index > 0) && !hasComma) { visitor.addJoinIdentifiers(); } // The identifier for a collection member declaration can only be added // after the first declaration, as long as there is a comma before it // "SELECT e FROM Employee e, |" <- 'IN' is a valid proposal // "SELECT e FROM Employee e, I|" <- 'IN' is a valid proposal // "SELECT e FROM Employee e I|" <- 'IN' is NOT a valid proposal if ((index > 0) && hasComma) { visitor.addIdentifier(IN); } } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(AbstractFromClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getDeclaration()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(AbstractFromClause expression, CollectionExpression collectionExpression, int index) { return true; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(AbstractFromClause expression) { return expression.hasSpaceAfterFrom(); } /** * {@inheritDoc} */ public int maxCollectionSize(AbstractFromClause expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(AbstractFromClause expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(AbstractFromClause expression, int index) { return visitor.getQueryBNF(expression.getDeclarationQueryBNFId()); } } // Made static for performance reasons. protected static class FromClauseStatementHelper extends AbstractFromClauseStatementHelper<SelectStatement> { protected FromClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public WhereClauseSelectStatementHelper getNextHelper() { return visitor.getWhereClauseSelectStatementHelper(); } } // Made static final for performance reasons. protected static final class GroupByClauseCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<GroupByClause> { protected GroupByClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(GroupByClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { } /** * {@inheritDoc} */ public void addIdentifier(GroupByClause expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(GroupByClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { if ((index == 0) || hasComma) { visitor.addFunctionIdentifiers(GroupByItemBNF.ID); visitor.addIdentificationVariables(); } } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(GroupByClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getGroupByItems()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(GroupByClause expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(GroupByClause expression) { return expression.hasSpaceAfterGroupBy(); } /** * {@inheritDoc} */ public int maxCollectionSize(GroupByClause expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(GroupByClause expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(GroupByClause expression, int index) { return visitor.getQueryBNF(GroupByItemBNF.ID); } } // Made static final for performance reasons. protected static final class GroupByClauseStatementHelper extends AbstractGroupByClauseStatementHelper<SelectStatement> { protected GroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public HavingClauseStatementHelper getNextHelper() { return visitor.getHavingClauseStatementHelper(); } } // Made static final for performance reasons. protected static final class HavingClauseStatementHelper extends AbstractHavingClauseStatementHelper<SelectStatement> { protected HavingClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public OrderByClauseStatementHelper getNextHelper() { return visitor.getOrderByClauseStatementHelper(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(SelectStatement expression) { return expression.hasSpaceBeforeOrderBy(); } } // Made static for performance reasons. /** * The various ways of retrieving identification variables from the declaration expression. */ protected static enum IdentificationVariableType { /** * Retrieves all the identification variables declared in the declaration portion of the * expression, which is either in the <b>FROM</b> clause of a <b>SELECT</b> query or * <b>DELETE</b> query or in the <b>UPDATE</b> query. */ ALL { /** * {@inheritDoc} */ @Override protected boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression) { if (declaration.getType() == Type.RANGE) { contentAssist.addRangeIdentificationVariable(declaration.getVariableName()); } else { contentAssist.addIdentificationVariable(declaration.getVariableName()); } for (Join join : declaration.getJoins()) { String variableName = contentAssist.queryContext.literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); contentAssist.addIdentificationVariable(variableName); } return false; } }, /** * Only retrieves the identification variables that map a path expression defined in a * <b>JOIN</b> expression or in a <b>IN</b> expression. */ COLLECTION { /** * {@inheritDoc} */ @Override protected boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression) { contentAssist.addIdentificationVariable(declaration.getVariableName()); for (Join join : declaration.getJoins()) { String variableName = contentAssist.queryContext.literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); contentAssist.addIdentificationVariable(variableName); } return false; } }, /** * Only retrieves the identification variables that have been declared to the left of the * expression requested them, both range and collection type variables are collected. */ LEFT { /** * {@inheritDoc} */ @Override protected boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression) { boolean shouldStop = declaration.getDeclarationExpression().isAncestor(expression); if (shouldStop && !declaration.getJoins().contains(expression)) { return true; } if (declaration.getType() == Type.RANGE) { contentAssist.addRangeIdentificationVariable(declaration.getVariableName()); } else { contentAssist.addIdentificationVariable(declaration.getVariableName()); } for (Join join : declaration.getJoins()) { if (join.isAncestor(expression)) { return true; } String variableName = contentAssist.queryContext.literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); contentAssist.addIdentificationVariable(variableName); } return false; } }, /** * Only retrieves the identification variables that map a path expression defined in a * <b>JOIN</b> expression or in a <b>IN</b> expression but that have been declared to the * left of the expression requested them. */ LEFT_COLLECTION { /** * {@inheritDoc} */ @Override protected boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression) { boolean shouldStop = declaration.getDeclarationExpression().isAncestor(expression); if (shouldStop && declaration.getJoins().contains(expression)) { return true; } if (!shouldStop && (declaration.getType() == Type.COLLECTION)) { contentAssist.addIdentificationVariable(declaration.getVariableName()); } else { for (Join join : declaration.getJoins()) { if (join.isAncestor(expression)) { return true; } String variableName = contentAssist.queryContext.literal( join.getIdentificationVariable(), LiteralType.IDENTIFICATION_VARIABLE ); contentAssist.addIdentificationVariable(variableName); } } return false; } }, /** * Simply indicates the identification variables should not be collected. */ NONE { /** * {@inheritDoc} */ @Override protected boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression) { // Nothing to do, stop immediately return true; } }; /** * Adds the identification variables defined in the given {@link Declaration}. * * @param contentAssist Backpointer to the content assist class * @param declaration The {@link Declaration} in the order they are declared in the declaration clause * @param expression The {@link Expression} being visited, which can help to determine when to * stop collecting identification variables */ protected abstract boolean add(AbstractContentAssistVisitor contentAssist, Declaration declaration, Expression expression); } // Made static for performance reasons. /** * This visitor is used when a clause or a compound expression was parsed with a collection of * expressions representing an invalid fragment. * <p> * Example: <code>SELECT e FROM Employee e GROUP B</code> * <p> * In this example, the <code><b>FROM</b></code> clause contains a collection of two * identification variable declarations, in a valid query, it would be separated by a comma, but * this one just means it's incomplete and "GROUP B" is the beginning of the <code><b>GROUP BY</b></code> * clause. */ protected static class IncompleteCollectionExpressionVisitor extends AbstractExpressionVisitor { /** * The clause being visited, which is marked by its JPQL identifier. */ protected String clause; /** * Determines whether an {@link Expression} that was visited is complete or if some part is missing. */ protected boolean complete; /** * This flag is used to make sure only the last expression in a collection is tested. A single * expression cannot be used to check the "completeness". */ protected boolean insideCollection; /** * Returns the list of identifiers for the clauses following the given identifier. * * @param afterIdentifier The JPQL identifier of the clause for which the list of following * clauses is built * @return The list of JPQL identifiers defining the clauses following the clause specified * by the given identifier */ protected List<String> compositeIdentifiersAfter(String afterIdentifier) { if (clause == FROM) { return CollectionTools.list(GROUP_BY, ORDER_BY); } if (clause == WHERE) { return CollectionTools.list(GROUP_BY, ORDER_BY); } if (clause == HAVING) { return CollectionTools.list(ORDER_BY); } return new LinkedList<String>(); } /** * Disposes of the internal data. */ public void dispose() { complete = false; } /** * Determines whether an {@link Expression} that was visited is complete or if some part is missing. * * @return <code>true</code> if the visited {@link Expression} is grammatically complete; * <code>false</code> if it is incomplete */ public boolean isComplete() { return complete; } /** * Determines whether the given JPQL fragment, which is the parsed text of the expression * invalid collection expression or the portion of it based on the cursor position within the * collection, is a composite identifier or not. * * @param clause The JPQL identifier of the clause having the collection of expressions * @param fragment The parsed text of the expression to determine if it's the beginning of the * composite identifier or not. The fragment is all lower case characters */ protected boolean isPossibleCompositeIdentifier(String clause, String fragment) { for (String identifier : compositeIdentifiersAfter(clause)) { if (identifier.toLowerCase().startsWith(fragment)) { return true; } } return false; } /** * {@inheritDoc} */ @Override public void visit(CollectionExpression expression) { int lastIndex = expression.childrenSize() - 1; insideCollection = true; if (!expression.hasComma(lastIndex - 1)) { expression.getChild(lastIndex).accept(this); } insideCollection = false; } /** * {@inheritDoc} */ @Override public void visit(FromClause expression) { clause = FROM; expression.getDeclaration().accept(this); clause = null; } /** * {@inheritDoc} */ @Override public void visit(GroupByClause expression) { clause = GROUP_BY; expression.getGroupByItems().accept(this); clause = null; } /** * {@inheritDoc} */ @Override public void visit(HavingClause expression) { clause = HAVING; expression.getConditionalExpression().accept(this); clause = null; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { complete = true; } /** * {@inheritDoc} */ @Override public void visit(IdentificationVariableDeclaration expression) { if (insideCollection && !expression.hasJoins()) { expression.getRangeVariableDeclaration().accept(this); } } /** * {@inheritDoc} */ @Override public void visit(OrderByClause expression) { clause = ORDER_BY; expression.getOrderByItems().accept(this); clause = null; } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { if (insideCollection) { // The "root" object could be the first identifier of a composite identifier // (eg: GROUP) and the identification variable is 'B' complete = !expression.hasAs() && !expression.hasIdentificationVariable(); // Special case for composite identifiers if (!complete) { String fragment = expression.toParsedText().toLowerCase(); complete = isPossibleCompositeIdentifier(clause, fragment); } } } /** * {@inheritDoc} */ @Override public void visit(WhereClause expression) { clause = WHERE; expression.getConditionalExpression().accept(this); clause = null; } } // Made static final for performance reasons. /** * This visitor determines if the visited {@link Expression} is one of the two that represents * an invalid expression. */ protected static final class InvalidExpressionVisitor extends AbstractExpressionVisitor { /** * The invalid {@link Expression}, which is either {@link UnknownExpression} or {@link BadExpression}. */ protected Expression expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * Determines whether the visited {@link Expression} represents an invalid fragment. * * @return <code>true</code> if the {@link Expression} is an invalid fragment; * <code>false</code> otherwise */ public boolean isInvalid() { return expression != null; } /** * {@inheritDoc} */ @Override public void visit(BadExpression expression) { this.expression = expression; } /** * {@inheritDoc} */ @Override public void visit(UnknownExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class JoinCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<IdentificationVariableDeclaration> { protected JoinCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(IdentificationVariableDeclaration expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { } /** * {@inheritDoc} */ public void addIdentifier(IdentificationVariableDeclaration expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(IdentificationVariableDeclaration expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addJoinIdentifiers(); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(IdentificationVariableDeclaration expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getJoins()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(IdentificationVariableDeclaration expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(IdentificationVariableDeclaration expression) { return expression.hasSpace(); } /** * {@inheritDoc} */ public int maxCollectionSize(IdentificationVariableDeclaration expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(IdentificationVariableDeclaration expression) { return expression.getRangeVariableDeclaration().getLength(); } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(IdentificationVariableDeclaration expression, int index) { return visitor.getQueryBNF(InternalJoinBNF.ID); } } // Made static for performance reasons. /** * A collector is responsible to retrieve the possible proposals by using the mappings that can * complete a path expression. */ protected static interface MappingCollector { /** * Retrieves the possible proposals that can be used to complete a path expression based on * the position of the cursor. * * @return The possible proposals */ Collection<IMapping> buildProposals(); } // Made static final for performance reasons. /** * This visitor is responsible to create the right {@link Filter} based on the type of the {@link Expression}. */ protected static final class MappingFilterBuilder extends AbstractTraverseParentVisitor { /** * Enclosing visitor instance. */ protected final AbstractContentAssistVisitor visitor; /** * The {@link Filter} that will filter the various type of {@link IMapping IMappings} based * on the location of the of the path expression within the JPQL query. */ protected Filter<IMapping> filter; protected MappingFilterBuilder(AbstractContentAssistVisitor visitor) { super(); this.visitor = visitor; } /** * Disposes of the internal data. */ public void dispose() { filter = null; } /** * {@inheritDoc} */ @Override public void visit(AbsExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(AvgFunction expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(BetweenExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(CoalesceExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(CollectionMemberDeclaration expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(CollectionValuedPathExpression expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(ConcatExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(CountFunction expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(EmptyCollectionComparisonExpression expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(FunctionExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(HavingClause expression) { // No need to filter, everything is allowed // Example: SELECT e FROM Employee e WHERE e.| // 1. Could become 'e.name = 'JPQL'' // 2. Could become 'e.employees IS NOT NULL' // 3. Could become e.address.zipcode = 27519 filter = NullFilter.instance(); } /** * {@inheritDoc} */ @Override public void visit(Join expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(JPQLExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(LengthExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(LocateExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(LowerExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(MaxFunction expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(MinFunction expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(ModExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(NullComparisonExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(OnClause expression) { // No need to filter, everything is allowed // Example: SELECT e FROM Employee e WHERE e.| // 1. Could become 'e.name = 'JPQL'' // 2. Could become 'e.employees IS NOT NULL' // 3. Could become e.address.zipcode = 27519 filter = NullFilter.instance(); } /** * {@inheritDoc} */ @Override public void visit(SizeExpression expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(SqrtExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(SubstringExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(SumFunction expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(TreatExpression expression) { filter = visitor.getMappingCollectionFilter(); } /** * {@inheritDoc} */ @Override public void visit(TrimExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(UpperExpression expression) { filter = visitor.getMappingPropertyFilter(); } /** * {@inheritDoc} */ @Override public void visit(WhenClause expression) { // No need to filter, everything is allowed // Example: SELECT e FROM Employee e WHERE e.| // 1. Could become 'e.name = 'JPQL'' // 2. Could become 'e.employees IS NOT NULL' // 3. Could become e.address.zipcode = 27519 filter = NullFilter.instance(); } /** * {@inheritDoc} */ @Override public void visit(WhereClause expression) { // No need to filter, everything is allowed // Example: SELECT e FROM Employee e WHERE e.| // 1. Could become 'e.name = 'JPQL'' // 2. Could become 'e.employees IS NOT NULL' // 3. Could become e.address.zipcode = 27519 filter = NullFilter.instance(); } } // Made static final for performance reasons. /** * This {@link Filter} is responsible to filter out the mappings that can't have their type * assignable to the one passed in. */ protected static final class MappingTypeFilter extends AbstractVisitorHelper implements Filter<IMapping> { /** * The type used to determine if the mapping's type is a valid type. */ protected final IType type; /** * Creates a new <code>MappingTypeFilter</code>. * * @param type The type used to determine if the mapping's type is a valid type */ MappingTypeFilter(IType type, AbstractContentAssistVisitor visitor) { super(visitor); this.type = type; } /** * {@inheritDoc} */ public boolean accept(IMapping value) { // A reference mapping always can be used for further traversal if (value.isRelationship() && !value.isCollection()) { return true; } // Determine if it's assignable to the desired type IType mappingType = value.getType(); mappingType = visitor.queryContext.getTypeHelper().convertPrimitive(mappingType); return mappingType.isAssignableTo(type); } } // Made static final for performance reasons. protected static final class NotExpressionVisitor extends AbstractExpressionVisitor { /** * The {@link NotExpression} if it is the {@link Expression} being visited otherwise <code>null</code>. */ protected NotExpression expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * Determines whether the {@link Expression} being visited is {@link NotExpression} or not. * * @return <code>true</code> if the {@link Expression} is {@link NotExpression}; * <code>null</code> otherwise */ public boolean isNotExpression() { return expression != null; } /** * {@inheritDoc} */ @Override public void visit(NotExpression expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class OrderByClauseCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractOrderByClause> { protected OrderByClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(AbstractOrderByClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { OrderByItem item = (OrderByItem) collectionExpression.getChild(index); if (item.getOrdering() == Ordering.DEFAULT) { visitor.addIdentifier(ASC); visitor.addIdentifier(DESC); } } /** * {@inheritDoc} */ public void addIdentifier(AbstractOrderByClause expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(AbstractOrderByClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { if ((index == 0) || hasComma) { visitor.addIdentificationVariables(); visitor.addResultVariables(); } } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(AbstractOrderByClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getOrderByItems()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(AbstractOrderByClause expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(AbstractOrderByClause expression) { return expression.hasSpaceAfterIdentifier(); } /** * {@inheritDoc} */ public int maxCollectionSize(AbstractOrderByClause expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(AbstractOrderByClause expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(AbstractOrderByClause expression, int index) { return visitor.getQueryBNF(OrderByItemBNF.ID); } } // Made static for performance reasons. protected static class OrderByClauseStatementHelper extends AbstractVisitorHelper implements StatementHelper<SelectStatement> { protected OrderByClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addCompositeIdentifier(ORDER_BY, -1); } /** * {@inheritDoc} */ public void addInternalClauseProposals(SelectStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(SelectStatement expression) { return expression.getOrderByClause(); } /** * {@inheritDoc} */ public StatementHelper<SelectStatement> getNextHelper() { return null; } /** * {@inheritDoc} */ public boolean hasClause(SelectStatement expression) { return expression.hasOrderByClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(SelectStatement expression) { return false; } /** * {@inheritDoc} */ public boolean isClauseComplete(SelectStatement expression) { return visitor.isComplete(expression.getOrderByClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static final for performance reasons. protected static final class PropertyMappingFilter implements Filter<IMapping> { /** * {@inheritDoc} */ public boolean accept(IMapping value) { return !value.isTransient() && !value.isCollection(); } } // Made static final for performance reasons. protected static final class RangeVariableDeclarationVisitor extends AbstractExpressionVisitor { /** * The {@link RangeVariableDeclaration} if it was visited otherwise <code>null</code>. */ protected RangeVariableDeclaration expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * {@inheritDoc} */ @Override public void visit(RangeVariableDeclaration expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class ResultVariableVisitor extends AbstractExpressionVisitor { /** * The {@link ResultVariable} if it was visited otherwise <code>null</code>. */ protected ResultVariable expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * {@inheritDoc} */ @Override public void visit(ResultVariable expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class SelectClauseCollectionHelper extends AbstractSelectClauseCollectionHelper<SelectClause> { protected SelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ @Override public void addAtTheEndOfChild(SelectClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { super.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, virtualSpace); Expression child = collectionExpression.getChild(index); // "SELECT e |" <- Valid to add AS // "SELECT AVG(e |" <- Invalid to add AS if (virtualSpace && visitor.isComplete(child)) { visitor.addIdentifier(AS); } } /** * {@inheritDoc} */ @Override public void addTheBeginningOfChild(SelectClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { super.addTheBeginningOfChild(expression, collectionExpression, index, hasComma); if (index == 0) { visitor.addIdentifier(DISTINCT); } } /** * {@inheritDoc} */ @Override public int preExpressionLength(SelectClause expression) { int length = 0; if (expression.hasDistinct()) { length = 8 /* DISTINCT */; if (expression.hasSpaceAfterDistinct()) { length++; } } return length; } } // Made static final for performance reasons. protected static final class SelectClauseStatementHelper extends AbstractSelectClauseStatementHelper { protected SelectClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public FromClauseStatementHelper getNextHelper() { return visitor.getFromClauseStatementHelper(); } } // Made static for performance reasons. protected static class SimpleFromClauseStatementHelper extends AbstractFromClauseStatementHelper<SimpleSelectStatement> { protected SimpleFromClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public SimpleWhereClauseSelectStatementHelper getNextHelper() { return visitor.getSimpleWhereClauseSelectStatementHelper(); } } // Made static final for performance reasons. protected static final class SimpleGroupByClauseStatementHelper extends AbstractGroupByClauseStatementHelper<SimpleSelectStatement> { protected SimpleGroupByClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public SimpleHavingClauseStatementHelper getNextHelper() { return visitor.getSimpleHavingClauseStatementHelper(); } } // Made static final for performance reasons. protected static final class SimpleHavingClauseStatementHelper extends AbstractHavingClauseStatementHelper<SimpleSelectStatement> { protected SimpleHavingClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public StatementHelper<SimpleSelectStatement> getNextHelper() { return null; } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(SimpleSelectStatement expression) { return false; } } // Made static final for performance reasons. protected static final class SimpleSelectClauseCollectionHelper extends AbstractSelectClauseCollectionHelper<SimpleSelectClause> { protected SimpleSelectClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } } // Made static final for performance reasons. protected static final class SimpleSelectClauseStatementHelper extends AbstractSelectClauseStatementHelper { protected SimpleSelectClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public StatementHelper<SimpleSelectStatement> getNextHelper() { return visitor.getSimpleFromClauseStatementHelper(); } } // Made static final for performance reasons. protected static final class SimpleWhereClauseSelectStatementHelper extends AbstractWhereClauseSelectStatementHelper<SimpleSelectStatement> { protected SimpleWhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public StatementHelper<SimpleSelectStatement> getNextHelper() { return visitor.getSimpleGroupByClauseStatementHelper(); } } // Made static for performance reasons. /** * This helper helps to add JPQL identifiers for the clauses that make up a query statement and * also chains the clauses within the query. */ protected static interface StatementHelper<T extends Expression> { /** * Adds the JPQL identifier of the clause being scanned by this helper. * <p> * Note: The identifier should not be added directly to the list, it needs to be filtered out * based on the location of the cursor, it can be within a word. */ void addClauseProposals(); /** * Adds the JPQL identifier of the internal clause being scanned by this helper. For instance, * the <code><b>FROM</b></code> clause could add its own sub-clauses. * <p> * Note: The identifier should not be added directly to the list, it needs to be filtered out * based on the location of the cursor, it can be within a word. * * @param expression The {@link AbstractSelectStatement} being visited */ void addInternalClauseProposals(T expression); /** * Returns the clause being scanned by this helper. It is safe to type cast the clause because * {@link #hasClause(Expression)} is called before this one. * * @param expression The {@link AbstractSelectStatement} being visited * @return The clause being scanned */ Expression getClause(T expression); /** * Returns the {@link StatementHelper} that will scan the following clause, which is * based on the grammar and not on the actual existence of the clause in the parsed tree. * * @return The {@link StatementHelper} for the next clause */ StatementHelper<? extends T> getNextHelper(); /** * Determines whether the clause exists in the parsed tree. * * @param expression The {@link AbstractSelectStatement} being visited * @return <code>true</code> if the clause has been parsed; <code>false</code> otherwise */ boolean hasClause(T expression); /** * Determines whether there is a space (owned by the <b>SELECT</b> statement) after the clause * being scanned by this helper. * * @param expression The {@link AbstractSelectStatement} being visited * @return <code>true</code> if a space follows the clause; <code>false</code> otherwise */ boolean hasSpaceAfterClause(T expression); /** * Determines whether the clause being scanned is complete or not. * * @param expression The {@link AbstractSelectStatement} being visited * @return <code>true</code> if the clause is complete; <code>false</code> otherwise */ boolean isClauseComplete(T expression); /** * Determines whether the clause is required in order to make the JPQL query grammatically valid. * * @return <code>true</code> if the clause has to be defined; <code>false</code> if the clause * is optional */ boolean isRequired(); } // Made static final for performance reasons. protected static final class SubqueryAppendableExpressionVisitor extends AbstractAppendableExpressionVisitor { /** * For a subquery <code><b>SELECT</b></code> clause identifier to be appendable, it has to be * encapsulated by a {@link SubExpression}. */ protected boolean subExpression; /** * {@inheritDoc} */ @Override public void visit(IdentificationVariable expression) { this.appendable = subExpression; } /** * {@inheritDoc} */ @Override public void visit(NullExpression expression) { this.appendable = subExpression; } /** * {@inheritDoc} */ @Override public void visit(SubExpression expression) { subExpression = true; expression.getExpression().accept(this); subExpression = false; } } // Made static final for performance reasons. /** * This visitor determines if an {@link Expression} is in a subquery. */ protected static final class SubqueryVisitor extends AbstractTraverseParentVisitor { /** * The subquery {@link Expression} if it's the first clause visitor. Otherwise it will be * <code>null</code> if the {@link Expression} is in the top-level query. */ protected SimpleSelectStatement expression; /** * Disposes of the internal data. */ public void dispose() { expression = null; } /** * Determines whether the visited {@link Expression} is in a subquery or in the top-level query. * * @return <code>true</code> if the owning query is a subquery; <code>false</code> if it's the * top-level query */ public boolean isInSubquery() { return expression != null; } /** * {@inheritDoc} */ @Override public void visit(SimpleSelectStatement expression) { this.expression = expression; } } // Made static final for performance reasons. protected static final class TripleEncapsulatedCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<AbstractTripleEncapsulatedExpression> { protected TripleEncapsulatedCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(AbstractTripleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { // The only thing that is appendable is an arithmetic operator // Example: "SELECT e.name|" // Example: "SELECT e|" if (queryBNF(expression, index).handleAggregate()) { Expression child = collectionExpression.getChild(index); if ((index == 0) && !virtualSpace) { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } } else { if (visitor.areArithmeticSymbolsAppendable(child)) { visitor.addArithmeticIdentifiers(); } if (visitor.areComparisonSymbolsAppendable(child)) { visitor.addComparisonIdentifiers(child); } } } } /** * {@inheritDoc} */ public void addIdentifier(AbstractTripleEncapsulatedExpression expression, String identifier) { visitor.proposals.addIdentifier(identifier); visitor.addFunctionIdentifiers(expression); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(AbstractTripleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addIdentificationVariables(); visitor.addFunctionIdentifiers(queryBNF(expression, index)); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(AbstractTripleEncapsulatedExpression expression) { return expression.buildCollectionExpression(); } /** * {@inheritDoc} */ public boolean canContinue(AbstractTripleEncapsulatedExpression expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(AbstractTripleEncapsulatedExpression expression) { return expression.hasSpaceAfterIdentifier() || expression.hasLeftParenthesis(); } /** * {@inheritDoc} */ public int maxCollectionSize(AbstractTripleEncapsulatedExpression expression) { // Both LOCATE and SUBSTRING can allow up to 3 encapsulated expressions return 3; } /** * {@inheritDoc} */ public int preExpressionLength(AbstractTripleEncapsulatedExpression expression) { return 0; } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(AbstractTripleEncapsulatedExpression expression, int index) { return visitor.getQueryBNF(expression.getParameterQueryBNFId(index)); } } // Made static final for performance reasons. protected static final class UpdateClauseStatementHelper extends AbstractVisitorHelper implements StatementHelper<UpdateStatement> { protected UpdateClauseStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(UPDATE); } /** * {@inheritDoc} */ public void addInternalClauseProposals(UpdateStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(UpdateStatement expression) { return expression.getUpdateClause(); } /** * {@inheritDoc} */ public StatementHelper<UpdateStatement> getNextHelper() { return visitor.getWhereClauseUpdateStatementHelper(); } /** * {@inheritDoc} */ public boolean hasClause(UpdateStatement expression) { return true; } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(UpdateStatement expression) { return expression.hasSpaceAfterUpdateClause(); } /** * {@inheritDoc} */ public boolean isClauseComplete(UpdateStatement expression) { return visitor.isComplete(expression.getUpdateClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return true; } } // Made static final for performance reasons. protected static final class UpdateItemCollectionHelper extends AbstractVisitorHelper implements CollectionExpressionHelper<UpdateClause> { protected UpdateItemCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addAtTheEndOfChild(UpdateClause expression, CollectionExpression collectionExpression, int index, boolean hasComma, boolean virtualSpace) { visitor.addAggregateIdentifiers(NewValueBNF.ID); } /** * {@inheritDoc} */ public void addIdentifier(UpdateClause expression, String identifier) { visitor.proposals.addIdentifier(identifier); } /** * {@inheritDoc} */ public void addTheBeginningOfChild(UpdateClause expression, CollectionExpression collectionExpression, int index, boolean hasComma) { visitor.addIdentificationVariables(); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(UpdateClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getUpdateItems()); if (collectionExpression == null) { collectionExpression = expression.buildCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean canContinue(UpdateClause expression, CollectionExpression collectionExpression, int index) { return false; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(UpdateClause expression) { return expression.hasSpaceAfterUpdate(); } /** * {@inheritDoc} */ public int maxCollectionSize(UpdateClause expression) { return Integer.MAX_VALUE; } /** * {@inheritDoc} */ public int preExpressionLength(UpdateClause expression) { // There is a SPACE_LENGTH less, it's added automatically return UPDATE.length() + SPACE_LENGTH + expression.getRangeVariableDeclaration().getLength() + SPACE_LENGTH + SET.length(); } /** * {@inheritDoc} */ public JPQLQueryBNF queryBNF(UpdateClause expression, int index) { return visitor.getQueryBNF(NewValueBNF.ID); } } // Made static final for performance reasons. /** * This visitor is meant to adjust the corrections stack when traversing an {@link Expression} in * order to increase the list of valid proposals. * <p> * For instance, if the query is "<code>SELECT e FROM Employee e WHERE IN</code>" and the cursor * is at the end of the query, then <code>IN</code> would be parsed with {@link InExpression}. * However, due to how {@link AbstractContentAssistVisitor} works, the identifier <code>INDEX</code> * is not added as a valid proposal. This visitor adds that functionality. */ protected static final class VisitParentVisitor extends AnonymousExpressionVisitor { /** * Enclosing visitor instance. */ protected final AbstractContentAssistVisitor visitor; protected VisitParentVisitor(AbstractContentAssistVisitor visitor) { this.visitor = visitor; } /** * {@inheritDoc} */ @Override protected void visit(Expression expression) { expression.getParent().accept(visitor); } /** * {@inheritDoc} */ @Override public void visit(InExpression expression) { int position = visitor.queryPosition.getPosition(expression) - visitor.corrections.peek(); int length = 0; if (expression.hasExpression()) { length += expression.getExpression().getLength() + SPACE_LENGTH; } // Within "IN" if (visitor.isPositionWithin(position, length, expression.getIdentifier())) { boolean hasOnlyIdentifier = !expression.hasExpression() && !expression.hasInItems(); if (hasOnlyIdentifier) { visitor.corrections.add(visitor.queryPosition.getPosition(expression)); } super.visit(expression); if (hasOnlyIdentifier) { visitor.corrections.pop(); } } else { super.visit(expression); } } } // Made static final for performance reasons. /** * This helpers handles adding proposals for {@link WhenClause}. */ protected static final class WhenClauseConditionalClauseCollectionHelper extends AbstractConditionalClauseCollectionHelper<WhenClause> { protected WhenClauseConditionalClauseCollectionHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public CollectionExpression buildCollectionExpression(WhenClause expression) { CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getWhenExpression()); if (collectionExpression == null) { collectionExpression = expression.buildWhenCollectionExpression(); } return collectionExpression; } /** * {@inheritDoc} */ public boolean hasDelimiterAfterIdentifier(WhenClause expression) { return expression.hasSpaceAfterWhen(); } } // Made static final for performance reasons. protected static final class WhereClauseDeleteStatementHelper extends AbstractVisitorHelper implements StatementHelper<DeleteStatement> { protected WhereClauseDeleteStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(WHERE); } /** * {@inheritDoc} */ public void addInternalClauseProposals(DeleteStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(DeleteStatement expression) { return expression.getWhereClause(); } /** * {@inheritDoc} */ public StatementHelper<? extends DeleteStatement> getNextHelper() { return null; } /** * {@inheritDoc} */ public boolean hasClause(DeleteStatement expression) { return expression.hasWhereClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(DeleteStatement expression) { return false; } /** * {@inheritDoc} */ public boolean isClauseComplete(DeleteStatement expression) { return visitor.isComplete(expression.getWhereClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static final for performance reasons. protected static final class WhereClauseSelectStatementHelper extends AbstractWhereClauseSelectStatementHelper<SelectStatement> { protected WhereClauseSelectStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public StatementHelper<SelectStatement> getNextHelper() { return visitor.getGroupByClauseStatementHelper(); } } // Made static final for performance reasons. protected static final class WhereClauseUpdateStatementHelper extends AbstractVisitorHelper implements StatementHelper<UpdateStatement> { protected WhereClauseUpdateStatementHelper(AbstractContentAssistVisitor visitor) { super(visitor); } /** * {@inheritDoc} */ public void addClauseProposals() { visitor.addIdentifier(WHERE); } /** * {@inheritDoc} */ public void addInternalClauseProposals(UpdateStatement expression) { // Does not have internal clauses } /** * {@inheritDoc} */ public Expression getClause(UpdateStatement expression) { return expression.getWhereClause(); } /** * {@inheritDoc} */ public StatementHelper<? extends UpdateStatement> getNextHelper() { return null; } /** * {@inheritDoc} */ public boolean hasClause(UpdateStatement expression) { return expression.hasWhereClause(); } /** * {@inheritDoc} */ public boolean hasSpaceAfterClause(UpdateStatement expression) { return false; } /** * {@inheritDoc} */ public boolean isClauseComplete(UpdateStatement expression) { return visitor.isComplete(expression.getWhereClause()); } /** * {@inheritDoc} */ public boolean isRequired() { return false; } } // Made static final for performance reasons. protected static final class WithinInvalidExpressionVisitor extends AbstractTraverseParentVisitor { /** * Determines whether the visited {@link Expression} is an descendant of either a bad or * invalid expression. */ protected boolean withinInvalidExpression; /** * Disposes of the internal data. */ public void dispose() { withinInvalidExpression = false; } /** * Determines whether the visited {@link Expression} is part of an invalid fragment * * @return <code>true</code> if the visited {@link Expression} is within an invalid fragment; * <code>false</code> if it is not */ public boolean isWithinInvalidExpression() { return withinInvalidExpression; } /** * {@inheritDoc} */ @Override public void visit(BadExpression expression) { withinInvalidExpression = true; } /** * {@inheritDoc} */ @Override public void visit(UnknownExpression expression) { withinInvalidExpression = true; } } }