| /* |
| * Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation |
| // |
| // 09/02/2019-3.0 Alexandre Jacob |
| // - 527415: Fix code when locale is tr, az or lt |
| 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.Locale; |
| 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.*; |
| |
| /** |
| * 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>() { |
| @Override |
| 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>() { |
| @Override |
| 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>() { |
| @Override |
| 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<>(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>() { |
| @Override |
| 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<>(); |
| lockedExpressions = new Stack<>(); |
| |
| virtualSpaces = new Stack<>(); |
| virtualSpaces.add(0); |
| |
| corrections = new Stack<>(); |
| corrections.add(0); |
| |
| identifierFilters = new HashMap<>(); |
| 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 |
| * |
| */ |
| 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 |
| * @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 |
| * @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(); |
| } |
| |
| @Override |
| public String toString() { |
| return proposals.toString(); |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void visit(AdditionExpression expression) { |
| super.visit(expression); |
| visitArithmeticExpression(expression); |
| } |
| |
| @Override |
| public void visit(AllOrAnyExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, ALL, ANY, SOME); |
| } |
| |
| @Override |
| public void visit(AndExpression expression) { |
| super.visit(expression); |
| visitLogicalExpression(expression); |
| } |
| |
| @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); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| super.visit(expression); |
| visitAggregateFunction(expression); |
| } |
| |
| @Override |
| public void visit(BadExpression expression) { |
| super.visit(expression); |
| visitInvalidExpression(expression); |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @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()); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| |
| @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(); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| super.visit(expression); |
| |
| if (!isFollowingInvalidExpression(expression)) { |
| |
| visitPathExpression(expression); |
| |
| if (isDeclaration(expression)) { |
| proposals.setClassNamePrefix(word, ClassType.INSTANTIABLE); |
| } |
| } |
| } |
| |
| @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()); |
| } |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| super.visit(expression); |
| visitCollectionExpression(expression, CONCAT, getConcatExpressionCollectionHelper()); |
| } |
| |
| @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()); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| super.visit(expression); |
| visitAggregateFunction(expression); |
| } |
| |
| @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)); |
| } |
| } |
| |
| @Override |
| public void visit(DeleteClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, DELETE_FROM, getDeleteClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(DeleteStatement expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitStatement(expression, getDeleteClauseStatementHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(DivisionExpression expression) { |
| super.visit(expression); |
| visitArithmeticExpression(expression); |
| } |
| |
| @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); |
| } |
| } |
| |
| @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(); |
| } |
| |
| @Override |
| public void visit(EntryExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.COLLECTION); |
| } |
| |
| @Override |
| public void visit(ExistsExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.NONE, EXISTS, NOT_EXISTS); |
| } |
| |
| @Override |
| protected void visit(Expression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(FunctionExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(GroupByClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, GROUP_BY, getGroupByClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(HavingClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper()); |
| } |
| } |
| |
| @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); |
| } |
| |
| @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()); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(IndexExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @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); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(InputParameter expression) { |
| // No content assist can be provider for an input parameter |
| super.visit(expression); |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(KeyExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION); |
| } |
| |
| @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)); |
| } |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| super.visit(expression); |
| visitCollectionExpression(expression, LOCATE, getTripleEncapsulatedCollectionHelper()); |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| super.visit(expression); |
| visitAggregateFunction(expression); |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| super.visit(expression); |
| visitAggregateFunction(expression); |
| } |
| |
| @Override |
| public void visit(ModExpression expression) { |
| super.visit(expression); |
| visitCollectionExpression(expression, MOD, getDoubleEncapsulatedCollectionHelper()); |
| } |
| |
| @Override |
| public void visit(MultiplicationExpression expression) { |
| super.visit(expression); |
| visitArithmeticExpression(expression); |
| } |
| |
| @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); |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(NullExpression expression) { |
| // No content assist can be provider |
| super.visit(expression); |
| } |
| |
| @Override |
| public void visit(NullIfExpression expression) { |
| super.visit(expression); |
| visitCollectionExpression(expression, NULLIF, getDoubleEncapsulatedCollectionHelper()); |
| } |
| |
| @Override |
| public void visit(NumericLiteral expression) { |
| // No content assist can be provider for a numerical value |
| super.visit(expression); |
| } |
| |
| @Override |
| public void visit(ObjectExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(OnClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(OrderByClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, ORDER_BY, getOrderByClauseCollectionHelper()); |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(OrExpression expression) { |
| super.visit(expression); |
| visitLogicalExpression(expression); |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, SELECT, getSelectClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(SelectStatement expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitStatement(expression, getSelectClauseStatementHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, FROM, getFromClauseCollectionHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitCollectionExpression(expression, SELECT, getSimpleSelectClauseCollectionHelper()); |
| } |
| } |
| |
| @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()); |
| } |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| expression.accept(getVisitParentVisitor()); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(StateFieldPathExpression expression) { |
| super.visit(expression); |
| if (!isFollowingInvalidExpression(expression)) { |
| visitPathExpression(expression); |
| } |
| } |
| |
| @Override |
| public void visit(StringLiteral expression) { |
| // No content assist required |
| super.visit(expression); |
| } |
| |
| @Override |
| public void visit(SubExpression expression) { |
| super.visit(expression); |
| int position = queryPosition.getPosition(expression) - corrections.peek(); |
| |
| if (position == 1) { |
| addFunctionIdentifiers(expression); |
| } |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| super.visit(expression); |
| visitCollectionExpression(expression, SUBSTRING, getTripleEncapsulatedCollectionHelper()); |
| } |
| |
| @Override |
| public void visit(SubtractionExpression expression) { |
| super.visit(expression); |
| visitArithmeticExpression(expression); |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| super.visit(expression); |
| visitAggregateFunction(expression); |
| } |
| |
| @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(); |
| } |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(TypeExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(UnknownExpression expression) { |
| super.visit(expression); |
| visitInvalidExpression(expression); |
| } |
| |
| @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()); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(UpdateStatement expression) { |
| if (!isLocked(expression)) { |
| super.visit(expression); |
| visitStatement(expression, getUpdateClauseStatementHelper()); |
| } |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.ALL); |
| } |
| |
| @Override |
| public void visit(ValueExpression expression) { |
| super.visit(expression); |
| visitSingleEncapsulatedExpression(expression, IdentificationVariableType.LEFT_COLLECTION); |
| } |
| |
| @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); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @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 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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(Expression expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public int preExpressionLength(Expression expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(FROM); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(T expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(T expression) { |
| return expression.getFromClause(); |
| } |
| |
| @Override |
| public boolean hasClause(T expression) { |
| return expression.hasFromClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(T expression) { |
| return expression.hasSpaceAfterFrom(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(T expression) { |
| return visitor.isComplete(expression.getFromClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addCompositeIdentifier(GROUP_BY, -1); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(T expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(T expression) { |
| return expression.getGroupByClause(); |
| } |
| |
| @Override |
| public boolean hasClause(T expression) { |
| return expression.hasGroupByClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(T expression) { |
| return expression.hasSpaceAfterGroupBy(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(T expression) { |
| return visitor.isComplete(expression.getGroupByClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(HAVING); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(T expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(T expression) { |
| return expression.getHavingClause(); |
| } |
| |
| @Override |
| public boolean hasClause(T expression) { |
| return expression.hasHavingClause(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(T expression) { |
| return visitor.isComplete(expression.getHavingClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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(); |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(T expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(T expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| if ((index == 0) || hasComma) { |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(expression.getSelectItemQueryBNFId()); |
| } |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(T expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getSelectExpression()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(T expression, CollectionExpression collectionExpression, int index) { |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(T expression) { |
| return expression.hasSpaceAfterSelect(); |
| } |
| |
| @Override |
| public int maxCollectionSize(T expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(T expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(SELECT); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(AbstractSelectStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(AbstractSelectStatement expression) { |
| return expression.getSelectClause(); |
| } |
| |
| @Override |
| public boolean hasClause(AbstractSelectStatement expression) { |
| return expression.hasSelectClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(AbstractSelectStatement expression) { |
| return expression.hasSpaceAfterSelect(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(AbstractSelectStatement expression) { |
| return visitor.isComplete(expression.getSelectClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(WHERE); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(T expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(T expression) { |
| return expression.getWhereClause(); |
| } |
| |
| @Override |
| public boolean hasClause(T expression) { |
| return expression.hasWhereClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(T expression) { |
| return expression.hasSpaceAfterWhere(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(T expression) { |
| return visitor.isComplete(expression.getWhereClause()); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(AbstractSchemaName expression) { |
| appendable = !conditionalExpression; |
| } |
| |
| @Override |
| public void visit(AdditionExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(AllOrAnyExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(AndExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(ArithmeticFactor expression) { |
| if (expression.hasExpression()) { |
| expression.getExpression().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(BadExpression expression) { |
| appendable = true; |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(CaseExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasEnd(); |
| } |
| |
| @Override |
| public void visit(CoalesceExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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; |
| } |
| |
| @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(); |
| } |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| appendable = !conditionalExpression && |
| !expression.endsWithDot(); |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(ConstructorExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(DeleteClause expression) { |
| if (expression.hasRangeVariableDeclaration()) { |
| expression.getRangeVariableDeclaration().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(DeleteStatement expression) { |
| |
| if (expression.hasWhereClause()) { |
| expression.getWhereClause().accept(this); |
| } |
| else { |
| expression.getDeleteClause().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(DivisionExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void visit(EntityTypeLiteral expression) { |
| appendable = !conditionalExpression; |
| } |
| |
| @Override |
| public void visit(EntryExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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(); |
| } |
| |
| @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; |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(FunctionExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(GroupByClause expression) { |
| |
| if (expression.hasGroupByItems()) { |
| clauseOfItems = true; |
| expression.getGroupByItems().accept(this); |
| clauseOfItems = false; |
| } |
| } |
| |
| @Override |
| public void visit(HavingClause expression) { |
| if (expression.hasConditionalExpression()) { |
| conditionalExpression = true; |
| expression.getConditionalExpression().accept(this); |
| conditionalExpression = false; |
| } |
| } |
| |
| @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; |
| } |
| } |
| } |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(IndexExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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(); |
| } |
| |
| @Override |
| public void visit(InputParameter expression) { |
| appendable = !conditionalExpression; |
| } |
| |
| @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(); |
| } |
| } |
| |
| @Override |
| public void visit(KeyExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(KeywordExpression expression) { |
| // Nothing can be added right after NULL, TRUE, FALSE |
| appendable = !conditionalExpression && |
| (appendableType == AppendableType.LOGICAL || |
| appendableType == AppendableType.COMPLETE); |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(ModExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(MultiplicationExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @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; |
| } |
| } |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void visit(NullIfExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(NumericLiteral expression) { |
| appendable = !conditionalExpression; |
| } |
| |
| @Override |
| public void visit(ObjectExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(OnClause expression) { |
| if (expression.hasConditionalExpression()) { |
| conditionalExpression = true; |
| expression.getConditionalExpression().accept(this); |
| conditionalExpression = false; |
| } |
| } |
| |
| @Override |
| public void visit(OrderByClause expression) { |
| if (expression.hasOrderByItems()) { |
| clauseOfItems = true; |
| expression.getOrderByItems().accept(this); |
| clauseOfItems = false; |
| } |
| } |
| |
| @Override |
| public void visit(OrderByItem expression) { |
| appendable = expression.hasSpaceAfterExpression() && |
| expression.isDefault() || |
| expression.hasSpaceAfterOrdering() && |
| !expression.isNullsFirst() && !expression.isNullsLast(); |
| } |
| |
| @Override |
| public void visit(OrExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(ResultVariable expression) { |
| // The result variable is parsed without AS |
| appendable = !expression.hasAs() && |
| !expression.hasSpaceAfterAs() && |
| expression.hasResultVariable(); |
| } |
| |
| @Override |
| public void visit(SelectClause expression) { |
| if (expression.hasSelectExpression()) { |
| clauseOfItems = true; |
| expression.getSelectExpression().accept(this); |
| clauseOfItems = false; |
| } |
| } |
| |
| @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); |
| } |
| } |
| |
| @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; |
| } |
| } |
| |
| @Override |
| public void visit(SimpleSelectClause expression) { |
| |
| if (expression.hasSelectExpression()) { |
| clauseOfItems = true; |
| expression.getSelectExpression().accept(this); |
| clauseOfItems = true; |
| } |
| } |
| |
| @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); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @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; |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visit(StringLiteral expression) { |
| appendable = !conditionalExpression && |
| expression.hasCloseQuote(); |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(SubtractionExpression expression) { |
| if (expression.hasRightExpression()) { |
| expression.getRightExpression().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(TreatExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(TrimExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(TypeExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(UpdateClause expression) { |
| if (expression.hasUpdateItems()) { |
| expression.getUpdateItems().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(UpdateItem expression) { |
| expression.getNewValue().accept(this); |
| } |
| |
| @Override |
| public void visit(UpdateStatement expression) { |
| if (expression.hasWhereClause()) { |
| expression.getWhereClause().accept(this); |
| } |
| else { |
| expression.getUpdateClause().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(ValueExpression expression) { |
| appendable = !conditionalExpression && |
| expression.hasRightParenthesis(); |
| } |
| |
| @Override |
| public void visit(WhenClause expression) { |
| if (expression.hasWhenExpression()) { |
| conditionalExpression = true; |
| expression.getWhenExpression().accept(this); |
| conditionalExpression = false; |
| } |
| } |
| |
| @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 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; |
| } |
| |
| @Override |
| public void visit(CollectionExpression expression) { |
| this.expression = expression; |
| } |
| } |
| |
| // Made static final for performance reasons. |
| protected static final class CollectionMappingFilter implements Filter<IMapping> { |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(ConcatExpression expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(ConcatExpression expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(queryBNF(expression, index)); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(ConcatExpression expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getExpression()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(ConcatExpression expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(ConcatExpression expression) { |
| return expression.hasSpaceAfterIdentifier() || |
| expression.hasLeftParenthesis(); |
| } |
| |
| @Override |
| public int maxCollectionSize(ConcatExpression expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(ConcatExpression expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(AbstractConditionalClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConditionalExpression()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(ConstructorExpression expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(ConstructorExpression expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addIdentificationVariables(expression, IdentificationVariableType.ALL); |
| visitor.addFunctionIdentifiers(ConstructorItemBNF.ID); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(ConstructorExpression expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getConstructorItems()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(ConstructorExpression expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(ConstructorExpression expression) { |
| return expression.hasLeftParenthesis(); |
| } |
| |
| @Override |
| public int maxCollectionSize(ConstructorExpression expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(ConstructorExpression expression) { |
| if (expression.hasSpaceAfterNew()) { |
| return expression.getClassName().length() + SPACE_LENGTH; |
| } |
| return expression.getClassName().length(); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @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 { |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addAtTheEndOfChild(DeleteClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma, |
| boolean virtualSpace) { |
| } |
| |
| @Override |
| public void addIdentifier(DeleteClause expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(DeleteClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| if (index == 0) { |
| visitor.addEntities(); |
| } |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(DeleteClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getRangeVariableDeclaration()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(DeleteClause expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(DeleteClause expression) { |
| return expression.hasSpaceAfterFrom(); |
| } |
| |
| @Override |
| public int maxCollectionSize(DeleteClause expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(DeleteClause expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(DELETE_FROM); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(DeleteStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(DeleteStatement expression) { |
| return expression.getDeleteClause(); |
| } |
| |
| @Override |
| public WhereClauseDeleteStatementHelper getNextHelper() { |
| return visitor.getWhereClauseDeleteStatementHelper(); |
| } |
| |
| @Override |
| public boolean hasClause(DeleteStatement expression) { |
| return true; |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(DeleteStatement expression) { |
| return expression.hasSpaceAfterDeleteClause(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(DeleteStatement expression) { |
| return visitor.isComplete(expression.getDeleteClause()); |
| } |
| |
| @Override |
| 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; |
| |
| @Override |
| public boolean accept(Expression expression) { |
| try { |
| expression.accept(this); |
| return valid; |
| } |
| finally { |
| valid = false; |
| } |
| } |
| |
| @Override |
| protected void visit(Expression expression) { |
| valid = true; |
| } |
| |
| @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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(AbstractDoubleEncapsulatedExpression expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(expression.getParent().findQueryBNF(expression)); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(AbstractDoubleEncapsulatedExpression expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(queryBNF(expression, index)); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(AbstractDoubleEncapsulatedExpression expression) { |
| return expression.buildCollectionExpression(); |
| } |
| |
| @Override |
| public boolean canContinue(AbstractDoubleEncapsulatedExpression expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(AbstractDoubleEncapsulatedExpression expression) { |
| return expression.hasSpaceAfterIdentifier() || |
| expression.hasLeftParenthesis(); |
| } |
| |
| @Override |
| public int maxCollectionSize(AbstractDoubleEncapsulatedExpression expression) { |
| // Both MOD and NULLIF allows a fixed 2 encapsulated expressions |
| return 2; |
| } |
| |
| @Override |
| public int preExpressionLength(AbstractDoubleEncapsulatedExpression expression) { |
| return 0; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| protected void visit(Expression expression) { |
| if (!visited) { |
| visited = true; |
| expression.getParent().accept(this); |
| visited = false; |
| } |
| } |
| |
| @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. |
| * |
| */ |
| 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())); |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(AbstractSchemaName expression) { |
| |
| if (badExpression) { |
| correction = expression.getLength() - positionWithinInvalidExpression; |
| } |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, expression.getLength() - correction); |
| } |
| } |
| |
| @Override |
| public void visit(AdditionExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| public void visit(AllOrAnyExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(AndExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(BadExpression expression) { |
| badExpression = true; |
| expression.getExpression().accept(this); |
| badExpression = false; |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(CoalesceExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| |
| if (badExpression) { |
| correction = expression.getLength() - positionWithinInvalidExpression; |
| } |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| } |
| |
| @Override |
| public void visit(ComparisonExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(DivisionExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(EntityTypeLiteral expression) { |
| |
| if (badExpression) { |
| correction = expression.getLength() - positionWithinInvalidExpression; |
| } |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| } |
| |
| @Override |
| public void visit(EntryExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(ExistsExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| visitAbstractFromClause(expression); |
| } |
| |
| @Override |
| public void visit(FunctionExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(HavingClause expression) { |
| visitAbstractConditionalClause(expression); |
| } |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| |
| if (badExpression) { |
| correction = expression.getLength() - positionWithinInvalidExpression; |
| } |
| |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(IndexExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(InputParameter expression) { |
| |
| if (!badExpression) { |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(KeyExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| visitAbstractTripleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(ModExpression expression) { |
| visitAbstractDoubleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(MultiplicationExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(NullExpression expression) { |
| |
| if (!badExpression) { |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, 0); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(NullIfExpression expression) { |
| visitAbstractDoubleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(NumericLiteral expression) { |
| |
| if (!badExpression) { |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(ObjectExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(OnClause expression) { |
| visitAbstractConditionalClause(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(OrExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| visitAbstractFromClause(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(StateFieldPathExpression expression) { |
| |
| if (badExpression) { |
| correction = expression.getLength() - positionWithinInvalidExpression; |
| } |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, expression.getLength() - correction); |
| } |
| } |
| |
| @Override |
| public void visit(StringLiteral expression) { |
| |
| if (!badExpression) { |
| |
| if (invalidExpression == expression) { |
| queryPosition.setExpression(expression); |
| queryPosition.addPosition(expression, positionWithinInvalidExpression); |
| } |
| } |
| } |
| |
| @Override |
| public void visit(SubExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| visitAbstractTripleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(SubtractionExpression expression) { |
| visitCompoundExpression(expression); |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(TrimExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(TypeExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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 |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| public void visit(ValueExpression expression) { |
| visitAbstractSingleEncapsulatedExpression(expression); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public void visit(CaseExpression expression) { |
| valid = (pathExpression == expression.getElseExpression()); |
| } |
| |
| @Override |
| public void visit(CoalesceExpression expression) { |
| // TODO |
| valid = (pathExpression == expression.getExpression()); |
| } |
| |
| @Override |
| public void visit(CollectionMemberExpression expression) { |
| valid = (pathExpression == expression.getEntityExpression()); |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @Override |
| public void visit(ComparisonExpression expression) { |
| String identifier = expression.getComparisonOperator(); |
| valid = ((identifier == EQUAL) || (identifier == DIFFERENT)); |
| } |
| |
| @Override |
| public void visit(ConstructorExpression expression) { |
| valid = true; |
| } |
| |
| @Override |
| public void visit(FunctionExpression expression) { |
| valid = true; |
| } |
| |
| @Override |
| public void visit(InExpression expression) { |
| valid = (pathExpression != expression.getExpression()); |
| } |
| |
| @Override |
| public void visit(NullIfExpression expression) { |
| valid = (pathExpression == expression.getSecondExpression()); |
| } |
| |
| @Override |
| public void visit(StateFieldPathExpression expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @Override |
| public void visit(UpdateItem expression) { |
| valid = (pathExpression == expression.getNewValue()); |
| } |
| |
| @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<>(filter, buildMappingNameFilter(suffix)); |
| } |
| |
| protected Filter<IMapping> buildMappingNameFilter(final String suffix) { |
| return new Filter<IMapping>() { |
| @Override |
| public boolean accept(IMapping mapping) { |
| return mapping.getName().startsWith(suffix); |
| } |
| }; |
| } |
| |
| @Override |
| public Collection<IMapping> buildProposals() { |
| |
| IManagedType managedType = resolver.getManagedType(); |
| |
| if (managedType == null) { |
| return Collections.emptyList(); |
| } |
| |
| ArrayList<IMapping> mappings = new ArrayList<>(); |
| 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(); |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void visit(SimpleFromClause expression) { |
| expression.getParent().accept(this); |
| } |
| |
| @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; |
| } |
| |
| @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; |
| } |
| } |
| } |
| |
| @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); |
| } |
| |
| @Override |
| 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 */); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(AbstractFromClause expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(AbstractFromClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getDeclaration()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(AbstractFromClause expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return true; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(AbstractFromClause expression) { |
| return expression.hasSpaceAfterFrom(); |
| } |
| |
| @Override |
| public int maxCollectionSize(AbstractFromClause expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(AbstractFromClause expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addAtTheEndOfChild(GroupByClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma, |
| boolean virtualSpace) { |
| } |
| |
| @Override |
| public void addIdentifier(GroupByClause expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(GroupByClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| if ((index == 0) || hasComma) { |
| visitor.addFunctionIdentifiers(GroupByItemBNF.ID); |
| visitor.addIdentificationVariables(); |
| } |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(GroupByClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getGroupByItems()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(GroupByClause expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(GroupByClause expression) { |
| return expression.hasSpaceAfterGroupBy(); |
| } |
| |
| @Override |
| public int maxCollectionSize(GroupByClause expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(GroupByClause expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public OrderByClauseStatementHelper getNextHelper() { |
| return visitor.getOrderByClauseStatementHelper(); |
| } |
| |
| @Override |
| 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 { |
| |
| |
| @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 { |
| |
| |
| @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 { |
| |
| |
| @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 { |
| |
| |
| @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 { |
| |
| |
| @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<>(); |
| } |
| |
| /** |
| * 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(Locale.ROOT).startsWith(fragment)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void visit(FromClause expression) { |
| clause = FROM; |
| expression.getDeclaration().accept(this); |
| clause = null; |
| } |
| |
| @Override |
| public void visit(GroupByClause expression) { |
| clause = GROUP_BY; |
| expression.getGroupByItems().accept(this); |
| clause = null; |
| } |
| |
| @Override |
| public void visit(HavingClause expression) { |
| clause = HAVING; |
| expression.getConditionalExpression().accept(this); |
| clause = null; |
| } |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| complete = true; |
| } |
| |
| @Override |
| public void visit(IdentificationVariableDeclaration expression) { |
| if (insideCollection && !expression.hasJoins()) { |
| expression.getRangeVariableDeclaration().accept(this); |
| } |
| } |
| |
| @Override |
| public void visit(OrderByClause expression) { |
| clause = ORDER_BY; |
| expression.getOrderByItems().accept(this); |
| clause = null; |
| } |
| |
| @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(Locale.ROOT); |
| complete = isPossibleCompositeIdentifier(clause, fragment); |
| } |
| } |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void visit(BadExpression expression) { |
| this.expression = expression; |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public void addAtTheEndOfChild(IdentificationVariableDeclaration expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma, |
| boolean virtualSpace) { |
| } |
| |
| @Override |
| public void addIdentifier(IdentificationVariableDeclaration expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(IdentificationVariableDeclaration expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addJoinIdentifiers(); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(IdentificationVariableDeclaration expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getJoins()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(IdentificationVariableDeclaration expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(IdentificationVariableDeclaration expression) { |
| return expression.hasSpace(); |
| } |
| |
| @Override |
| public int maxCollectionSize(IdentificationVariableDeclaration expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(IdentificationVariableDeclaration expression) { |
| return expression.getRangeVariableDeclaration().getLength(); |
| } |
| |
| @Override |
| 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 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; |
| } |
| |
| @Override |
| public void visit(AbsExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(AvgFunction expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(BetweenExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(CoalesceExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(CollectionMemberDeclaration expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(CollectionValuedPathExpression expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(ConcatExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(CountFunction expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(EmptyCollectionComparisonExpression expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(FunctionExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @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(); |
| } |
| |
| @Override |
| public void visit(Join expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(JPQLExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(LengthExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(LocateExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(LowerExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(MaxFunction expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(MinFunction expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(ModExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(NullComparisonExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @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(); |
| } |
| |
| @Override |
| public void visit(SizeExpression expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(SqrtExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(SubstringExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(SumFunction expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(TreatExpression expression) { |
| filter = visitor.getMappingCollectionFilter(); |
| } |
| |
| @Override |
| public void visit(TrimExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @Override |
| public void visit(UpperExpression expression) { |
| filter = visitor.getMappingPropertyFilter(); |
| } |
| |
| @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(); |
| } |
| |
| @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; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| |
| @Override |
| public void addIdentifier(AbstractOrderByClause expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(AbstractOrderByClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| if ((index == 0) || hasComma) { |
| visitor.addIdentificationVariables(); |
| visitor.addResultVariables(); |
| } |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(AbstractOrderByClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getOrderByItems()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(AbstractOrderByClause expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(AbstractOrderByClause expression) { |
| return expression.hasSpaceAfterIdentifier(); |
| } |
| |
| @Override |
| public int maxCollectionSize(AbstractOrderByClause expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| public int preExpressionLength(AbstractOrderByClause expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addCompositeIdentifier(ORDER_BY, -1); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(SelectStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(SelectStatement expression) { |
| return expression.getOrderByClause(); |
| } |
| |
| @Override |
| public StatementHelper<SelectStatement> getNextHelper() { |
| return null; |
| } |
| |
| @Override |
| public boolean hasClause(SelectStatement expression) { |
| return expression.hasOrderByClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(SelectStatement expression) { |
| return false; |
| } |
| |
| @Override |
| public boolean isClauseComplete(SelectStatement expression) { |
| return visitor.isComplete(expression.getOrderByClause()); |
| } |
| |
| @Override |
| public boolean isRequired() { |
| return false; |
| } |
| } |
| |
| // Made static final for performance reasons. |
| protected static final class PropertyMappingFilter implements Filter<IMapping> { |
| |
| @Override |
| 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; |
| } |
| |
| @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; |
| } |
| |
| @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); |
| } |
| |
| @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); |
| } |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(SelectClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| super.addTheBeginningOfChild(expression, collectionExpression, index, hasComma); |
| |
| if (index == 0) { |
| visitor.addIdentifier(DISTINCT); |
| } |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public FromClauseStatementHelper getNextHelper() { |
| return visitor.getFromClauseStatementHelper(); |
| } |
| } |
| |
| // Made static for performance reasons. |
| protected static class SimpleFromClauseStatementHelper extends AbstractFromClauseStatementHelper<SimpleSelectStatement> { |
| |
| protected SimpleFromClauseStatementHelper(AbstractContentAssistVisitor visitor) { |
| super(visitor); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public StatementHelper<SimpleSelectStatement> getNextHelper() { |
| return null; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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 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; |
| |
| @Override |
| public void visit(IdentificationVariable expression) { |
| this.appendable = subExpression; |
| } |
| |
| @Override |
| public void visit(NullExpression expression) { |
| this.appendable = subExpression; |
| } |
| |
| @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; |
| } |
| |
| @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); |
| } |
| |
| @Override |
| 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); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void addIdentifier(AbstractTripleEncapsulatedExpression expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| visitor.addFunctionIdentifiers(expression); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(AbstractTripleEncapsulatedExpression expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addIdentificationVariables(); |
| visitor.addFunctionIdentifiers(queryBNF(expression, index)); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(AbstractTripleEncapsulatedExpression expression) { |
| return expression.buildCollectionExpression(); |
| } |
| |
| @Override |
| public boolean canContinue(AbstractTripleEncapsulatedExpression expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(AbstractTripleEncapsulatedExpression expression) { |
| return expression.hasSpaceAfterIdentifier() || |
| expression.hasLeftParenthesis(); |
| } |
| |
| @Override |
| public int maxCollectionSize(AbstractTripleEncapsulatedExpression expression) { |
| // Both LOCATE and SUBSTRING can allow up to 3 encapsulated expressions |
| return 3; |
| } |
| |
| @Override |
| public int preExpressionLength(AbstractTripleEncapsulatedExpression expression) { |
| return 0; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(UPDATE); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(UpdateStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(UpdateStatement expression) { |
| return expression.getUpdateClause(); |
| } |
| |
| @Override |
| public StatementHelper<UpdateStatement> getNextHelper() { |
| return visitor.getWhereClauseUpdateStatementHelper(); |
| } |
| |
| @Override |
| public boolean hasClause(UpdateStatement expression) { |
| return true; |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(UpdateStatement expression) { |
| return expression.hasSpaceAfterUpdateClause(); |
| } |
| |
| @Override |
| public boolean isClauseComplete(UpdateStatement expression) { |
| return visitor.isComplete(expression.getUpdateClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addAtTheEndOfChild(UpdateClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma, |
| boolean virtualSpace) { |
| |
| visitor.addAggregateIdentifiers(NewValueBNF.ID); |
| } |
| |
| @Override |
| public void addIdentifier(UpdateClause expression, String identifier) { |
| visitor.proposals.addIdentifier(identifier); |
| } |
| |
| @Override |
| public void addTheBeginningOfChild(UpdateClause expression, |
| CollectionExpression collectionExpression, |
| int index, |
| boolean hasComma) { |
| |
| visitor.addIdentificationVariables(); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(UpdateClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getUpdateItems()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| public boolean canContinue(UpdateClause expression, |
| CollectionExpression collectionExpression, |
| int index) { |
| |
| return false; |
| } |
| |
| @Override |
| public boolean hasDelimiterAfterIdentifier(UpdateClause expression) { |
| return expression.hasSpaceAfterUpdate(); |
| } |
| |
| @Override |
| public int maxCollectionSize(UpdateClause expression) { |
| return Integer.MAX_VALUE; |
| } |
| |
| @Override |
| 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(); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| protected void visit(Expression expression) { |
| expression.getParent().accept(visitor); |
| } |
| |
| @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); |
| } |
| |
| @Override |
| public CollectionExpression buildCollectionExpression(WhenClause expression) { |
| CollectionExpression collectionExpression = visitor.getCollectionExpression(expression.getWhenExpression()); |
| if (collectionExpression == null) { |
| collectionExpression = expression.buildWhenCollectionExpression(); |
| } |
| return collectionExpression; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(WHERE); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(DeleteStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(DeleteStatement expression) { |
| return expression.getWhereClause(); |
| } |
| |
| @Override |
| public StatementHelper<? extends DeleteStatement> getNextHelper() { |
| return null; |
| } |
| |
| @Override |
| public boolean hasClause(DeleteStatement expression) { |
| return expression.hasWhereClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(DeleteStatement expression) { |
| return false; |
| } |
| |
| @Override |
| public boolean isClauseComplete(DeleteStatement expression) { |
| return visitor.isComplete(expression.getWhereClause()); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public void addClauseProposals() { |
| visitor.addIdentifier(WHERE); |
| } |
| |
| @Override |
| public void addInternalClauseProposals(UpdateStatement expression) { |
| // Does not have internal clauses |
| } |
| |
| @Override |
| public Expression getClause(UpdateStatement expression) { |
| return expression.getWhereClause(); |
| } |
| |
| @Override |
| public StatementHelper<? extends UpdateStatement> getNextHelper() { |
| return null; |
| } |
| |
| @Override |
| public boolean hasClause(UpdateStatement expression) { |
| return expression.hasWhereClause(); |
| } |
| |
| @Override |
| public boolean hasSpaceAfterClause(UpdateStatement expression) { |
| return false; |
| } |
| |
| @Override |
| public boolean isClauseComplete(UpdateStatement expression) { |
| return visitor.isComplete(expression.getWhereClause()); |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| public void visit(BadExpression expression) { |
| withinInvalidExpression = true; |
| } |
| |
| @Override |
| public void visit(UnknownExpression expression) { |
| withinInvalidExpression = true; |
| } |
| } |
| } |