blob: f3c00cc4841fc3785f53f7b5d5885a16cd45bc06 [file] [log] [blame]
/*
* Copyright (c) 2006, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation
//
package org.eclipse.persistence.jpa.jpql.tools;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.DOT;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.ALL;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.AS;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.AS_OF;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.CONNECT_BY;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.EXCEPT;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.FROM;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.INTERSECT;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NOT_EQUAL;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NULLS_FIRST;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.NULLS_LAST;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.ORDER_SIBLINGS_BY;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.REGEXP;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.SCN;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.SELECT;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.START_WITH;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.TABLE;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.TIMESTAMP;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.UNION;
import static org.eclipse.persistence.jpa.jpql.parser.Expression.WHERE;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.EclipseLinkVersion;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractFromClause;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.AsOfClause;
import org.eclipse.persistence.jpa.jpql.parser.CastExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
import org.eclipse.persistence.jpa.jpql.parser.DefaultEclipseLinkJPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.ExtractExpression;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem.Ordering;
import org.eclipse.persistence.jpa.jpql.parser.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.PatternValueBNF;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.ScalarExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.StartWithClause;
import org.eclipse.persistence.jpa.jpql.parser.TableExpression;
import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.UnionClause;
import org.eclipse.persistence.jpa.jpql.tools.ContentAssistProposals.ClassType;
import org.eclipse.persistence.jpa.jpql.tools.resolver.Declaration;
/**
* This extension over the default content assist visitor adds the additional support EclipseLink
* provides.
* <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.1
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings("unused") // unused used for the import statement: see bug 330740
public class EclipseLinkContentAssistVisitor extends AbstractContentAssistVisitor
implements EclipseLinkExpressionVisitor {
/**
* Creates a new <code>EclipseLinkContentAssistVisitor</code>.
*
* @param queryContext The context used to query information about the query
* @exception NullPointerException The {@link JPQLQueryContext} cannot be <code>null</code>
*/
public EclipseLinkContentAssistVisitor(JPQLQueryContext queryContext) {
super(queryContext);
}
@Override
protected AcceptableTypeVisitor buildAcceptableTypeVisitor() {
return new AcceptableTypeVisitor();
}
@Override
protected AppendableExpressionVisitor buildAppendableExpressionVisitor() {
return new AppendableExpressionVisitor(this);
}
@Override
protected EndingQueryPositionBuilder buildEndingQueryPositionBuilder() {
return new EndingQueryPositionBuilder(this);
}
@Override
protected FollowingClausesVisitor buildFollowingClausesVisitor() {
return new FollowingClausesVisitor();
}
@Override
protected FromClauseCollectionHelper buildFromClauseCollectionHelper() {
return new FromClauseCollectionHelper(this);
}
@Override
protected FromClauseStatementHelper buildFromClauseStatementHelper() {
return new FromClauseStatementHelper(this);
}
@Override
protected GroupByClauseCollectionHelper buildGroupByClauseCollectionHelper() {
return new GroupByClauseCollectionHelper(this);
}
@Override
protected IncompleteCollectionExpressionVisitor buildIncompleteCollectionExpressionVisitor() {
return new IncompleteCollectionExpressionVisitor();
}
@Override
protected OrderByClauseStatementHelper buildOrderByClauseStatementHelper() {
return new OrderByClauseStatementHelper(this);
}
@Override
protected SimpleFromClauseStatementHelper buildSimpleFromClauseStatementHelper() {
return new SimpleFromClauseStatementHelper(this);
}
protected TableExpressionVisitor buildTableExpressionVisitor() {
return new TableExpressionVisitor();
}
protected UnionClauseStatementHelper buildUnionClauseStatementHelper() {
return new UnionClauseStatementHelper();
}
/**
* Returns the enum constant of the EclipseLink version specified in the {@link JPQLQueryContext}.
*
* @return The EclipseLink version specified or the default version (i.e. the version of the
* current release)
* @since 2.5
*/
protected EclipseLinkVersion getEcliseLinkVersion() {
return EclipseLinkVersion.value(queryContext.getProviderVersion());
}
@Override
protected JPQLGrammar getLatestGrammar() {
return DefaultEclipseLinkJPQLGrammar.instance();
}
protected TableExpressionVisitor getTableExpressionVisitor() {
TableExpressionVisitor visitor = getHelper(TableExpressionVisitor.class);
if (visitor == null) {
visitor = buildTableExpressionVisitor();
registerHelper(TableExpressionVisitor.class, visitor);
}
return visitor;
}
protected String getTableName(String variableName) {
Declaration declaration = queryContext.getDeclaration(variableName);
Expression baseExpression = (declaration != null) ? declaration.getBaseExpression() : null;
if ((baseExpression != null) && isTableExpression(baseExpression)) {
return queryContext.literal(baseExpression, LiteralType.STRING_LITERAL);
}
return null;
}
protected UnionClauseStatementHelper getUnionClauseStatementHelper() {
UnionClauseStatementHelper helper = getHelper(UnionClauseStatementHelper.class);
if (helper == null) {
helper = buildUnionClauseStatementHelper();
registerHelper(UnionClauseStatementHelper.class, helper);
}
return helper;
}
@Override
protected void initialize() {
super.initialize();
identifierFilters.put(REGEXP, VALID_IDENTIFIER_FILTER);
identifierFilters.put(NOT_EQUAL, VALID_IDENTIFIER_FILTER);
}
@Override
protected boolean isJoinFetchIdentifiable() {
EclipseLinkVersion version = EclipseLinkVersion.value(queryContext.getProviderVersion());
return version.isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_4);
}
protected boolean isTableExpression(Expression expression) {
TableExpressionVisitor visitor = getTableExpressionVisitor();
try {
visitor.expression = expression;
expression.accept(visitor);
return visitor.valid;
}
finally {
visitor.valid = false;
visitor.expression = null;
}
}
@Override
public void visit(AsOfClause expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
// Within "AS OF"
if (isPositionWithin(position, AS_OF)) {
proposals.addIdentifier(AS_OF);
}
// After "AS OF"
else if (expression.hasSpaceAfterIdentifier()) {
int length = AS_OF.length() + SPACE_LENGTH;
// Right after "AS OF "
if (position == length) {
addIdentifier(SCN);
addIdentifier(TIMESTAMP);
if (!expression.hasScn() &&
!expression.hasTimestamp()) {
addIdentificationVariables();
addFunctionIdentifiers(ScalarExpressionBNF.ID);
}
}
// After "AS OF SCN" or "AS OF TIMESTAMP"
else if (expression.hasScn() ||
expression.hasSpaceAfterIdentifier()) {
// SCN
if (expression.hasScn() && isPositionWithin(position, length, SCN)) {
proposals.addIdentifier(SCN);
proposals.addIdentifier(TIMESTAMP);
}
// TIMESTAMP
else if (expression.hasTimestamp() && isPositionWithin(position, length, TIMESTAMP)) {
proposals.addIdentifier(SCN);
proposals.addIdentifier(TIMESTAMP);
}
else {
if (expression.hasScn()) {
length += SCN.length();
}
else if (expression.hasTimestamp()) {
length += TIMESTAMP.length();
}
// After "AS OF SCN " or "AS OF TIMESTAMP "
if (expression.hasSpaceAfterCategory()) {
addIdentificationVariables();
addFunctionIdentifiers(ScalarExpressionBNF.ID);
}
}
}
}
}
@Override
public void visit(CastExpression expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
String identifier = expression.getIdentifier();
// Within CAST
if (isPositionWithin(position, identifier)) {
addIdentifier(identifier);
addIdentificationVariables();
addFunctionIdentifiers(expression.getParent().findQueryBNF(expression));
}
// After "CAST("
else if (expression.hasLeftParenthesis()) {
int length = identifier.length() + 1 /* '(' */;
// Right after "CAST("
if (position == length) {
addIdentificationVariables();
addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
}
else if (expression.hasExpression()) {
Expression scalarExpression = expression.getExpression();
if (isComplete(scalarExpression)) {
length += scalarExpression.getLength();
if (expression.hasSpaceAfterExpression()) {
length++;
// Right before "AS" or database type
if (position == length) {
addAggregateIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
proposals.addIdentifier(AS);
}
// Within "AS"
else if (isPositionWithin(position, length, AS)) {
proposals.addIdentifier(AS);
}
}
}
}
}
}
@Override
public void visit(ConnectByClause expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
// Within "CONNECT BY"
if (isPositionWithin(position, CONNECT_BY)) {
proposals.addIdentifier(CONNECT_BY);
}
// After "CONNECT BY"
else if (expression.hasSpaceAfterConnectBy()) {
int length = CONNECT_BY.length() + SPACE_LENGTH;
// Right after "CONNECT BY "
if (position == length) {
addIdentificationVariables();
addFunctionIdentifiers(CollectionValuedPathExpressionBNF.ID);
}
}
}
@Override
public void visit(DatabaseType expression) {
super.visit(expression);
// Nothing to do, this is database specific
}
@Override
public void visit(ExtractExpression expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
String identifier = expression.getIdentifier();
// Within "EXTRACT"
if (isPositionWithin(position, identifier)) {
proposals.addIdentifier(identifier);
addFunctionIdentifiers(expression);
}
// After "EXTRACT("
else if (expression.hasLeftParenthesis()) {
int length = identifier.length() + 1 /* '(' */;
// Right after "EXTRACT("
if (position == length) {
// Nothing to do, unless we show basic date parts
}
if (expression.hasDatePart()) {
String datePart = expression.getDatePart();
// Within "<date part>"
if (isPositionWithin(position, length, datePart)) {
// Nothing to do, unless we show basic date parts
}
length += datePart.length();
// After "<date part> "
if (expression.hasSpaceAfterDatePart()) {
length++;
// Right before "FROM"
if (position == length) {
addIdentifier(FROM);
// Only add the scalar expression's functions if it is not specified
// or the FROM identifier is not present
if (!expression.hasExpression() || !expression.hasFrom()) {
addIdentificationVariables();
addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
}
}
}
}
if (expression.hasFrom()) {
// Within "FROM"
if (isPositionWithin(position, length, FROM)) {
proposals.addIdentifier(FROM);
// Only add the scalar expression's functions if it is not specified
if (!expression.hasExpression()) {
addIdentificationVariables();
addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
}
}
length += 4 /* FROM */;
if (expression.hasSpaceAfterFrom()) {
length++;
}
// Right after "FROM "
if (position == length) {
addIdentificationVariables();
addFunctionIdentifiers(expression.getEncapsulatedExpressionQueryBNFId());
}
}
}
}
@Override
public void visit(HierarchicalQueryClause expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
// At the beginning of the clause
if (position == 0) {
addIdentifier(START_WITH);
if (!expression.hasStartWithClause()) {
addIdentifier(CONNECT_BY);
}
}
else {
int length = 0;
// After the start with clause
if (expression.hasStartWithClause()) {
length += expression.getStartWithClause().getLength();
// Right after the start with clause
if (hasVirtualSpace() && (position == length + SPACE_LENGTH)) {
addIdentifier(CONNECT_BY);
}
// After the start with clause
else if (expression.hasSpaceAfterStartWithClause()) {
length++;
// Right after the start with clause
if (position == length) {
addIdentifier(CONNECT_BY);
}
}
}
length += expression.getConnectByClause().getLength();
// Right after the connect by clause
if (hasVirtualSpace() && (position == length + SPACE_LENGTH)) {
addIdentifier(ORDER_SIBLINGS_BY);
}
// After the connect by clause
else if (expression.hasSpaceAfterConnectByClause()) {
length++;
if (position == length) {
addIdentifier(ORDER_SIBLINGS_BY);
}
}
}
}
@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();
if (expression.hasSpaceAfterExpression()) {
length++;
// Right after the order by item
if (position == length) {
// Only add "NULLS FIRST" and "NULLS LAST" if the ordering is not specified
if (expression.getOrdering() == Ordering.DEFAULT) {
proposals.addIdentifier(NULLS_FIRST);
proposals.addIdentifier(NULLS_LAST);
}
}
else {
length += expression.getActualOrdering().length();
if (position > length) {
if (expression.hasSpaceAfterOrdering()) {
length += SPACE_LENGTH;
// Right before "NULLS FIRST" or "NULLS LAST"
if (position == length) {
proposals.addIdentifier(NULLS_FIRST);
proposals.addIdentifier(NULLS_LAST);
}
else {
String nullOrdering = expression.getActualNullOrdering();
// Within "NULLS FIRST" or "NULLS LAST"
if (isPositionWithin(position, length, nullOrdering)) {
proposals.addIdentifier(NULLS_FIRST);
proposals.addIdentifier(NULLS_LAST);
}
}
}
}
}
}
}
}
@Override
public void visit(OrderSiblingsByClause expression) {
if (!isLocked(expression)) {
super.visit(expression);
visitCollectionExpression(expression, ORDER_SIBLINGS_BY, getOrderByClauseCollectionHelper());
}
}
@Override
public void visit(RegexpExpression expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
int length = 0;
if (expression.hasStringExpression()) {
length += expression.getStringExpression().getLength();
if (expression.hasSpaceAfterStringExpression()) {
length += SPACE_LENGTH;
}
}
// Within "REGEXP"
if (isPositionWithin(position, length, REGEXP)) {
proposals.addIdentifier(REGEXP);
}
// After "REGEXP"
else {
length += 6 /* REGEXP */;
// After "REGEXP "
if (expression.hasSpaceAfterIdentifier()) {
length += SPACE_LENGTH;
// Right after "REGEXP "
addIdentificationVariables();
addFunctionIdentifiers(PatternValueBNF.ID);
}
}
}
@Override
public void visit(StartWithClause expression) {
if (!isLocked(expression)) {
super.visit(expression);
visitCollectionExpression(expression, expression.getIdentifier(), getConditionalClauseCollectionHelper());
}
}
@Override
public void visit(TableExpression expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression);
// Within "TABLE"
if (isPositionWithin(position, TABLE)) {
proposals.addIdentifier(TABLE);
}
// After '('
else if (expression.hasLeftParenthesis()) {
int length = TABLE.length() + SPACE_LENGTH;
// Right after '('
if (position == length) {
proposals.setTableNamePrefix(ExpressionTools.EMPTY_STRING);
}
else {
Expression nameExpression = expression.getExpression();
String tableName = queryContext.literal(nameExpression, LiteralType.STRING_LITERAL);
if (tableName.length() == 0) {
tableName = queryContext.literal(nameExpression, LiteralType.IDENTIFICATION_VARIABLE);
}
int tableNameLength = tableName.length();
// Within the string literal representing the table name
if ((position > length) && (position <= length + tableNameLength)) {
String prefix = tableName.substring(0, position - length);
prefix = ExpressionTools.unquote(prefix);
proposals.setTableNamePrefix(prefix);
}
}
}
}
@Override
public void visit(TableVariableDeclaration expression) {
super.visit(expression);
TableExpression tableExpression = expression.getTableExpression();
int position = queryPosition.getPosition(expression) - corrections.peek();
int length = tableExpression.getLength();
// After "TABLE()"
if (expression.hasSpaceAfterTableExpression()) {
length += SPACE_LENGTH;
// Right after "TABLE() "
if (isPositionWithin(position, length, AS) &&
isComplete(tableExpression)) {
addIdentifier(AS);
}
}
}
@Override
public void visit(UnionClause expression) {
super.visit(expression);
int position = queryPosition.getPosition(expression) - corrections.peek();
String identifier = expression.getIdentifier();
// Within <identifier>
if (isPositionWithin(position, identifier)) {
proposals.addIdentifier(EXCEPT);
proposals.addIdentifier(INTERSECT);
proposals.addIdentifier(UNION);
}
// After "<identifier> "
else if (expression.hasSpaceAfterIdentifier()) {
int length = identifier.length() + SPACE_LENGTH;
// Right after "<identifier> "
if (position == length) {
proposals.addIdentifier(ALL);
if (!expression.hasAll()) {
addIdentifier(SELECT);
}
}
// Within "ALL"
else if (isPositionWithin(position, length, ALL)) {
addIdentifier(ALL);
}
else {
if ((position == length) && !expression.hasAll()) {
proposals.addIdentifier(SELECT);
}
else {
if (expression.hasAll()) {
length += 3 /* ALL */;
}
// After "ALL "
if (expression.hasSpaceAfterAll()) {
length += SPACE_LENGTH;
// Right after "ALL "
if (position == length) {
proposals.addIdentifier(SELECT);
}
}
}
}
}
}
@Override
protected void visitThirdPartyPathExpression(AbstractPathExpression expression,
String variableName) {
// Check to see if a column name can be resolved
int position = queryPosition.getPosition(expression);
String text = expression.toActualText();
int dotIndex = text.indexOf(DOT);
int secondDotIndex = (dotIndex > -1) ? text.indexOf(DOT, dotIndex + 1) : -1;
// The cursor position is after the first dot and either there is no second dot or the
// position is before the second dot, which means a table name and column names could
// potentially be resolved
if ((secondDotIndex == -1) || (position < secondDotIndex)) {
String tableName = getTableName(variableName);
if (tableName != ExpressionTools.EMPTY_STRING) {
tableName = ExpressionTools.unquote(tableName);
proposals.setTableName(tableName, text.substring(dotIndex + 1, position));
}
}
}
// Made static final for performance reasons.
protected static final class AcceptableTypeVisitor extends AbstractContentAssistVisitor.AcceptableTypeVisitor {
}
// Made static final for performance reasons.
protected static final class AppendableExpressionVisitor extends AbstractContentAssistVisitor.AppendableExpressionVisitor
implements EclipseLinkExpressionVisitor {
AppendableExpressionVisitor(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public void visit(AsOfClause expression) {
if (expression.hasExpression()) {
expression.getExpression().accept(this);
}
}
@Override
public void visit(CastExpression expression) {
appendable = !conditionalExpression &&
expression.hasRightParenthesis();
}
@Override
public void visit(ConnectByClause expression) {
if (expression.hasExpression()) {
expression.getExpression().accept(this);
}
}
@Override
public void visit(DatabaseType expression) {
// Always complete since it's a single word
}
@Override
public void visit(ExtractExpression expression) {
appendable = !conditionalExpression &&
expression.hasRightParenthesis();
}
@Override
public void visit(HierarchicalQueryClause expression) {
if (expression.hasOrderSiblingsByClause()) {
expression.getOrderSiblingsByClause().accept(this);
}
else if (expression.hasConnectByClause()) {
expression.getConnectByClause().accept(this);
}
else if (expression.hasStartWithClause()) {
expression.getStartWithClause().accept(this);
}
}
@Override
public void visit(OrderSiblingsByClause expression) {
if (expression.hasOrderByItems()) {
clauseOfItems = true;
expression.getOrderByItems().accept(this);
clauseOfItems = false;
}
}
@Override
public void visit(RegexpExpression expression) {
if (expression.hasPatternValue()) {
expression.getPatternValue().accept(this);
}
}
@Override
public void visit(StartWithClause expression) {
if (expression.hasConditionalExpression()) {
conditionalExpression = true;
expression.getConditionalExpression().accept(this);
conditionalExpression = false;
}
}
@Override
public void visit(TableExpression expression) {
appendable = !conditionalExpression &&
expression.hasRightParenthesis();
}
@Override
public void visit(TableVariableDeclaration expression) {
if (expression.hasIdentificationVariable()) {
expression.getIdentificationVariable().accept(this);
}
}
@Override
public void visit(UnionClause expression) {
if (expression.hasQuery()) {
expression.getQuery().accept(this);
}
}
}
// Made static final for performance reasons.
protected static final class EndingQueryPositionBuilder extends AbstractContentAssistVisitor.EndingQueryPositionBuilder
implements EclipseLinkExpressionVisitor {
protected EndingQueryPositionBuilder(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public void visit(AsOfClause expression) {
if (badExpression) {
return;
}
if (expression.hasExpression()) {
expression.getExpression().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(CastExpression expression) {
if (badExpression) {
return;
}
if (expression.hasScalarExpression() &&
!expression.hasAs() &&
!expression.hasDatabaseType() &&
!expression.hasRightParenthesis()) {
expression.getExpression().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(ConnectByClause expression) {
if (badExpression) {
return;
}
if (expression.hasExpression()) {
expression.getExpression().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(DatabaseType expression) {
visitAbstractDoubleEncapsulatedExpression(expression);
}
@Override
public void visit(ExtractExpression expression) {
visitAbstractSingleEncapsulatedExpression(expression);
}
@Override
public void visit(HierarchicalQueryClause expression) {
if (badExpression) {
return;
}
if (expression.hasOrderSiblingsByClause()) {
expression.getOrderSiblingsByClause().accept(this);
}
else if (expression.hasConnectByClause()) {
expression.getConnectByClause().accept(this);
if (expression.hasSpaceAfterConnectByClause()) {
virtualSpace = true;
}
}
else if (expression.hasStartWithClause()) {
expression.getStartWithClause().accept(this);
if (expression.hasSpaceAfterStartWithClause()) {
virtualSpace = true;
}
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(OrderSiblingsByClause expression) {
if (badExpression) {
return;
}
if (expression.hasOrderByItems()) {
expression.getOrderByItems().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(RegexpExpression expression) {
if (badExpression) {
return;
}
if (expression.hasPatternValue()) {
expression.getPatternValue().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(StartWithClause expression) {
visitAbstractConditionalClause(expression);
}
@Override
public void visit(TableExpression expression) {
visitAbstractSingleEncapsulatedExpression(expression);
}
@Override
public void visit(TableVariableDeclaration expression) {
if (badExpression) {
return;
}
if (expression.hasIdentificationVariable()) {
expression.getIdentificationVariable().accept(this);
}
else if (!expression.hasAs()) {
expression.getTableExpression().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
@Override
public void visit(UnionClause expression) {
if (badExpression) {
return;
}
if (expression.hasQuery()) {
expression.getQuery().accept(this);
}
if (queryPosition.getExpression() == null) {
queryPosition.setExpression(expression);
}
queryPosition.addPosition(expression, expression.getLength() - correction);
}
}
// Made static final for performance reasons.
/**
* This visitor adds support for the additional clauses provided by EclipseLink, such as the
*/
protected static final class FollowingClausesVisitor extends AbstractContentAssistVisitor.FollowingClausesVisitor {
protected boolean hasAsOfClause;
protected boolean hasConnectByClause;
protected boolean hasOrderSiblingsByClause;
protected boolean hasStartWithClause;
protected boolean introspect;
@Override
public void dispose() {
super.dispose();
hasAsOfClause = false;
hasConnectByClause = false;
hasStartWithClause = false;
hasOrderSiblingsByClause = false;
}
@Override
protected boolean hasFromClause(AbstractSelectStatement expression) {
introspect = true;
expression.getFromClause().accept(this);
introspect = false;
if (afterIdentifier == SELECT) {
if (beforeIdentifier == START_WITH) {
return expression.hasFromClause();
}
if (beforeIdentifier == CONNECT_BY) {
return expression.hasFromClause() ||
hasStartWithClause;
}
if (beforeIdentifier == ORDER_SIBLINGS_BY) {
return expression.hasFromClause() ||
hasStartWithClause ||
hasConnectByClause;
}
if (beforeIdentifier == AS_OF) {
return expression.hasFromClause() ||
hasStartWithClause ||
hasConnectByClause ||
hasOrderSiblingsByClause;
}
if (beforeIdentifier == WHERE) {
return expression.hasFromClause() ||
hasStartWithClause ||
hasConnectByClause ||
hasOrderSiblingsByClause ||
hasAsOfClause;
}
}
else if (afterIdentifier == FROM) {
if (beforeIdentifier == CONNECT_BY) {
return hasStartWithClause;
}
if (beforeIdentifier == ORDER_SIBLINGS_BY) {
return hasStartWithClause ||
hasConnectByClause;
}
if (beforeIdentifier == AS_OF) {
return hasStartWithClause ||
hasConnectByClause ||
hasOrderSiblingsByClause;
}
if (beforeIdentifier == WHERE) {
return hasStartWithClause ||
hasConnectByClause ||
hasOrderSiblingsByClause ||
hasAsOfClause;
}
}
else if (afterIdentifier == START_WITH) {
if (beforeIdentifier == ORDER_SIBLINGS_BY) {
return hasConnectByClause;
}
if (beforeIdentifier == AS_OF) {
return hasConnectByClause ||
hasOrderSiblingsByClause;
}
if (beforeIdentifier == WHERE) {
return hasConnectByClause ||
hasOrderSiblingsByClause ||
hasAsOfClause;
}
}
else if (afterIdentifier == CONNECT_BY) {
if (beforeIdentifier == AS_OF) {
return hasOrderSiblingsByClause;
}
if (beforeIdentifier == WHERE) {
return hasOrderSiblingsByClause ||
hasAsOfClause;
}
}
else if (afterIdentifier == ORDER_SIBLINGS_BY) {
if (beforeIdentifier == WHERE) {
return hasAsOfClause;
}
}
return false;
}
@Override
public void visit(FromClause expression) {
if (!introspect) {
super.visit(expression);
}
else {
hasAsOfClause = expression.hasAsOfClause();
if (expression.hasHierarchicalQueryClause()) {
expression.getHierarchicalQueryClause().accept(this);
}
}
}
public void visit(HierarchicalQueryClause expression) {
if (!introspect) {
super.visit(expression);
}
else {
hasConnectByClause = expression.hasConnectByClause();
hasStartWithClause = expression.hasStartWithClause();
hasOrderSiblingsByClause = expression.hasOrderSiblingsByClause();
}
}
@Override
public void visit(SimpleFromClause expression) {
if (!introspect) {
super.visit(expression);
}
else {
hasAsOfClause = expression.hasAsOfClause();
if (expression.hasHierarchicalQueryClause()) {
expression.getHierarchicalQueryClause().accept(this);
}
}
}
}
protected class FromClauseCollectionHelper extends AbstractContentAssistVisitor.FromClauseCollectionHelper {
protected FromClauseCollectionHelper(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public void addAtTheEndOfChild(AbstractFromClause expression,
CollectionExpression collectionExpression,
int index,
boolean hasComma,
boolean virtualSpace) {
super.addAtTheEndOfChild(expression, collectionExpression, index, hasComma, virtualSpace);
boolean end = (index + 1 == collectionExpression.childrenSize());
// At the end of a range variable declaration, the following clauses can be added
// Example: "SELECT e FROM Employee e |"
// Example: "SELECT e FROM Employee e, Address a |"
// Example: "SELECT e FROM Employee e |, Address a " <- Not valid to add the clauses
if (((index == 0) || hasComma) && end && virtualSpace) {
if (isComplete(collectionExpression.getChild(0))) {
EclipseLinkContentAssistVisitor.this.addIdentifier(START_WITH);
if (!hasClausesDefinedBetween(expression, FROM, CONNECT_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, ORDER_SIBLINGS_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, AS_OF)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(AS_OF);
}
}
}
// Special case to handle a range variable declaration that can also
// be either the beginning of the following clauses
// Example: "SELECT e FROM Employee o o|" <- Valid
// Example: "SELECT e FROM Employee o, Address a o|" <- Valid
// Example: "SELECT e FROM Employee o|" <- Not valid
else if ((index > 0) && end && !hasComma) {
addCompositeIdentifier(START_WITH, 4 /* START - 1 */);
if (!hasClausesDefinedBetween(expression, FROM, CONNECT_BY)) {
addCompositeIdentifier(CONNECT_BY, 6 /* CONNECT - 1 */);
}
if (!hasClausesDefinedBetween(expression, FROM, ORDER_SIBLINGS_BY)) {
addCompositeIdentifier(ORDER_SIBLINGS_BY, 4 /* ORDER - 1 */);
}
// AS OF clause
if (!hasClausesDefinedBetween(expression, FROM, AS_OF)) {
addCompositeIdentifier(AS_OF, 1 /* AS - 1 */);
}
}
}
@Override
public void addTheBeginningOfChild(AbstractFromClause expression,
CollectionExpression collectionExpression,
int index,
boolean hasComma) {
super.addTheBeginningOfChild(expression, collectionExpression, index, hasComma);
// 1. At the beginning of the FROM declaration, fully qualified class names 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) {
proposals.setClassNamePrefix(word, ClassType.INSTANTIABLE);
}
// The identifier for a TABLE declaration can only be added after
// the first declaration, as long as there is a comma before it
// "SELECT e FROM Employee e, |" <- 'TABLE' is a valid proposal
// "SELECT e FROM Employee e, T|" <- 'TABLE' is a valid proposal
// "SELECT e FROM Employee e T|" <- 'TABLE' is NOT a valid proposal
if ((index > 0) && hasComma) {
EclipseLinkContentAssistVisitor.this.addIdentifier(TABLE);
}
// Add the "internal" clauses of the FROM clause defined by EclipseLink
// only if it ends the FROM clause expression
if (collectionExpression != null) {
boolean end = (index + 1 == collectionExpression.childrenSize());
if (end && !hasComma) {
EclipseLinkContentAssistVisitor.this.addIdentifier(START_WITH);
if (!hasClausesDefinedBetween(expression, FROM, CONNECT_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, ORDER_SIBLINGS_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, AS_OF)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(AS_OF);
}
}
}
}
}
protected class FromClauseStatementHelper extends AbstractContentAssistVisitor.FromClauseStatementHelper {
protected FromClauseStatementHelper(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public void addInternalClauseProposals(SelectStatement expression) {
super.addInternalClauseProposals(expression);
if (!hasClausesDefinedBetween(expression, FROM, WHERE)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(START_WITH);
}
if (!hasClausesDefinedBetween(expression, START_WITH, ORDER_SIBLINGS_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, CONNECT_BY, AS_OF)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(ORDER_SIBLINGS_BY);
}
if (!hasClausesDefinedBetween(expression, ORDER_SIBLINGS_BY, WHERE)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(AS_OF);
}
}
}
/**
* This subclass adds support for EclipseLink specific support.
*/
protected class IncompleteCollectionExpressionVisitor extends AbstractContentAssistVisitor.IncompleteCollectionExpressionVisitor {
@Override
protected List<String> compositeIdentifiersAfter(String clause) {
// Add support for hierarchical query and AS OF clauses
if ((clause == FROM) && getEcliseLinkVersion().isNewerThanOrEqual(EclipseLinkVersion.VERSION_2_5)) {
List<String> identifiers = super.compositeIdentifiersAfter(clause);
identifiers.add(START_WITH);
identifiers.add(CONNECT_BY);
identifiers.add(ORDER_SIBLINGS_BY);
identifiers.add(AS_OF);
return identifiers;
}
return super.compositeIdentifiersAfter(clause);
}
}
protected class OrderByClauseStatementHelper extends AbstractContentAssistVisitor.OrderByClauseStatementHelper {
protected OrderByClauseStatementHelper(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public UnionClauseStatementHelper getNextHelper() {
return getUnionClauseStatementHelper();
}
@Override
public boolean hasSpaceAfterClause(SelectStatement expression) {
return expression.hasSpaceBeforeUnion();
}
}
protected class SimpleFromClauseStatementHelper extends AbstractContentAssistVisitor.SimpleFromClauseStatementHelper {
protected SimpleFromClauseStatementHelper(AbstractContentAssistVisitor visitor) {
super(visitor);
}
@Override
public void addInternalClauseProposals(SimpleSelectStatement expression) {
super.addInternalClauseProposals(expression);
EclipseLinkContentAssistVisitor.this.addIdentifier(START_WITH);
if (!hasClausesDefinedBetween(expression, FROM, CONNECT_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, ORDER_SIBLINGS_BY)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(CONNECT_BY);
}
if (!hasClausesDefinedBetween(expression, FROM, AS_OF)) {
EclipseLinkContentAssistVisitor.this.addIdentifier(AS_OF);
}
}
}
protected class TableExpressionVisitor extends AbstractEclipseLinkExpressionVisitor {
/**
* The {@link Expression} being visited.
*/
protected Expression expression;
/**
* <code>true</code> if the {@link Expression} being visited is a {@link TableExpression}.
*/
protected boolean valid;
@Override
public void visit(TableExpression expression) {
valid = (this.expression == expression);
}
}
protected class UnionClauseStatementHelper implements StatementHelper<SelectStatement> {
@Override
public void addClauseProposals() {
addIdentifier(EXCEPT);
addIdentifier(INTERSECT);
addIdentifier(UNION);
}
@Override
public void addInternalClauseProposals(SelectStatement expression) {
}
@Override
public Expression getClause(SelectStatement expression) {
return expression.getUnionClauses();
}
@Override
public StatementHelper<? extends SelectStatement> getNextHelper() {
return null;
}
@Override
public boolean hasClause(SelectStatement expression) {
return expression.hasUnionClauses();
}
@Override
public boolean hasSpaceAfterClause(SelectStatement expression) {
return false;
}
@Override
public boolean isClauseComplete(SelectStatement expression) {
return isComplete(expression.getUnionClauses());
}
@Override
public boolean isRequired() {
return false;
}
}
}