org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2006, 2018 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.parser;
import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* An identification variable declared by a collection member declaration ranges over values of a
* collection obtained by navigation using a path expression. Such a path expression represents a
* navigation involving the association-fields of an entity abstract schema type. Because a path
* expression can be based on another path expression, the navigation can use the association-fields
* of related entities. An identification variable of a collection member declaration is declared
* using a special operator, the reserved identifier IN. The argument to the IN
* operator is a collection-valued path expression. The path expression evaluates to a collection
* type specified as a result of navigation to a collection-valued association-field of an entity
* abstract schema type.
*
* BNF: collection_member_declaration ::= IN(collection_valued_path_expression) [AS] identification_variable
* or
* BNF: derived_collection_member_declaration ::= IN superquery_identification_variable.{single_valued_object_field.}*collection_valued_field
*
* Example: SELECT t FROM Player p, IN (p.teams) AS t
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public final class CollectionMemberDeclaration extends AbstractExpression {
/**
* The actual AS identifier found in the string representation of the JPQL query.
*/
private String asIdentifier;
/**
* The {@link Expression} representing the collection member, which is declared by an
* identification variable.
*/
private AbstractExpression collectionValuedPathExpression;
/**
* Flag used to determine if the closing parenthesis is present in the query.
*/
private boolean hasLeftParenthesis;
/**
* Flag used to determine if the opening parenthesis is present in the query.
*/
private boolean hasRightParenthesis;
/**
* Determines whether a whitespace was parsed after AS.
*/
private boolean hasSpaceAfterAs;
/**
* Determines whether a whitespace was parsed after IN.
*/
private boolean hasSpaceAfterIn;
/**
* Determines whether a whitespace was parsed after the right parenthesis.
*/
private boolean hasSpaceAfterRightParenthesis;
/**
* The {@link Expression} representing the identification variable, which maps the collection-
* valued path expression.
*/
private AbstractExpression identificationVariable;
/**
* The actual IN identifier found in the string representation of the JPQL query.
*/
private String inIdentifier;
/**
* Creates a new CollectionMemberDeclaration
.
*
* @param parent The parent of this expression
*/
public CollectionMemberDeclaration(AbstractExpression parent) {
super(parent, IN);
}
/**
* {@inheritDoc}
*/
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
/**
* {@inheritDoc}
*/
public void acceptChildren(ExpressionVisitor visitor) {
getCollectionValuedPathExpression().accept(visitor);
getIdentificationVariable().accept(visitor);
}
/**
* {@inheritDoc}
*/
@Override
protected void addChildrenTo(Collection children) {
children.add(getCollectionValuedPathExpression());
children.add(getIdentificationVariable());
}
/**
* {@inheritDoc}
*/
@Override
protected void addOrderedChildrenTo(List children) {
// 'IN'
children.add(buildStringExpression(IN));
if (hasSpaceAfterIn) {
children.add(buildStringExpression(SPACE));
}
// '('
if (hasLeftParenthesis) {
children.add(buildStringExpression(LEFT_PARENTHESIS));
}
// Collection-valued path expression
if (collectionValuedPathExpression != null) {
children.add(collectionValuedPathExpression);
}
// ')'
if (hasRightParenthesis) {
children.add(buildStringExpression(RIGHT_PARENTHESIS));
}
if (hasSpaceAfterRightParenthesis) {
children.add(buildStringExpression(SPACE));
}
// AS
if (asIdentifier != null) {
children.add(buildStringExpression(AS));
}
if (hasSpaceAfterAs) {
children.add(buildStringExpression(SPACE));
}
// Identification variable
if (identificationVariable != null) {
children.add(identificationVariable);
}
}
/**
* {@inheritDoc}
*/
@Override
public JPQLQueryBNF findQueryBNF(Expression expression) {
if ((collectionValuedPathExpression != null) && collectionValuedPathExpression.isAncestor(expression)) {
return getQueryBNF(CollectionValuedPathExpressionBNF.ID);
}
if ((identificationVariable != null) && identificationVariable.isAncestor(expression)) {
return getQueryBNF(IdentificationVariableBNF.ID);
}
return super.findQueryBNF(expression);
}
/**
* Returns the actual AS identifier found in the string representation of the JPQL query,
* which has the actual case that was used.
*
* @return The AS identifier that was actually parsed
*/
public String getActualAsIdentifier() {
return asIdentifier;
}
/**
* Returns the actual IN identifier found in the string representation of the JPQL query,
* which has the actual case that was used.
*
* @return The IN identifier that was actually parsed
*/
public String getActualInIdentifier() {
return inIdentifier;
}
/**
* Returns the {@link Expression} representing the collection member, which is declared by an
* identification variable.
*
* @return The expression representing the collection-valued path expression
*/
public Expression getCollectionValuedPathExpression() {
if (collectionValuedPathExpression == null) {
collectionValuedPathExpression = buildNullExpression();
}
return collectionValuedPathExpression;
}
/**
* Returns the {@link Expression} representing the identification variable, which maps the
* collection-valued path expression.
*
* @return The expression representing the identification variable
*/
public Expression getIdentificationVariable() {
if (identificationVariable == null) {
identificationVariable = buildNullExpression();
}
return identificationVariable;
}
/**
* {@inheritDoc}
*/
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(CollectionMemberDeclarationBNF.ID);
}
/**
* Determines whether the identifier AS was parsed.
*
* @return true
if the identifier AS was parsed; false
otherwise
*/
public boolean hasAs() {
return asIdentifier != null;
}
/**
* Determines whether the collection-valued path expression was parsed.
*
* @return true
if the query has the collection-valued path expression;
* false
otherwise
*/
public boolean hasCollectionValuedPathExpression() {
return collectionValuedPathExpression != null &&
!collectionValuedPathExpression.isNull();
}
/**
* Determines whether the identification variable was parsed.
*
* @return true
if the query has the identification variable; false
* otherwise
*/
public boolean hasIdentificationVariable() {
return identificationVariable != null &&
!identificationVariable.isNull() &&
!identificationVariable.isVirtual();
}
/**
* Determines whether the open parenthesis was parsed or not.
*
* @return true
if the open parenthesis was present in the string version of the
* query; false
otherwise
*/
public boolean hasLeftParenthesis() {
return hasLeftParenthesis;
}
/**
* Determines whether the close parenthesis was parsed or not.
*
* @return true
if the close parenthesis was present in the string version of the
* query; false
otherwise
*/
public boolean hasRightParenthesis() {
return hasRightParenthesis;
}
/**
* Determines whether a whitespace was found after AS.
*
* @return true
if there was a whitespace after AS; false
* otherwise
*/
public boolean hasSpaceAfterAs() {
return hasSpaceAfterAs;
}
/**
* Determines whether a whitespace was found after IN.
*
* @return true
if there was a whitespace after IN; false
* otherwise
*/
public boolean hasSpaceAfterIn() {
return hasSpaceAfterIn;
}
/**
* Determines whether a whitespace was found after the close parenthesis.
*
* @return true
if there was a whitespace after the right parenthesis;
* false
otherwise
*/
public boolean hasSpaceAfterRightParenthesis() {
return hasSpaceAfterRightParenthesis;
}
/**
* Determines whether this collection member declaration is used as a derived collection-valued
* path expression.
*
* @return true
if this collection member declaration is used as this form:
* "IN collection_valued_path_expression
" in a subquery; false
* if it's used as this form: IN(collection_valued_path_expression)
* [AS] identification_variable
" in a top-level or subquery queries
* @since 2.4
*/
public boolean isDerived() {
return !hasLeftParenthesis &&
!hasRightParenthesis &&
!hasSpaceAfterRightParenthesis &&
asIdentifier == null &&
!hasIdentificationVariable();
}
/**
* {@inheritDoc}
*/
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
return wordParser.character() == RIGHT_PARENTHESIS ||
word.equalsIgnoreCase(AS) ||
super.isParsingComplete(wordParser, word, expression);
}
/**
* {@inheritDoc}
*/
@Override
protected void parse(WordParser wordParser, boolean tolerant) {
// Parse 'IN'
inIdentifier = wordParser.moveForward(IN);
int count = wordParser.skipLeadingWhitespace();
// Parse '('
hasLeftParenthesis = wordParser.startsWith(LEFT_PARENTHESIS);
if (hasLeftParenthesis) {
wordParser.moveForward(1);
count = wordParser.skipLeadingWhitespace();
}
// For JPA 2.0 a derived collection member declaration
else {
hasSpaceAfterIn = (count > 0);
count = 0;
}
// Parse the collection-valued path expression
collectionValuedPathExpression = parse(
wordParser,
CollectionValuedPathExpressionBNF.ID,
tolerant
);
if (collectionValuedPathExpression != null) {
count = wordParser.skipLeadingWhitespace();
}
// Parse ')'
hasRightParenthesis = wordParser.startsWith(RIGHT_PARENTHESIS);
if (hasRightParenthesis) {
wordParser.moveForward(1);
count = wordParser.skipLeadingWhitespace();
hasSpaceAfterRightParenthesis = count > 0;
// Special case for derived collection-valued path expression
if (!hasLeftParenthesis &&
!wordParser.startsWithIdentifier(AS) &&
(wordParser.isTail() || isParsingComplete(wordParser, wordParser.word(), null))) {
hasRightParenthesis = false;
hasSpaceAfterRightParenthesis = false;
wordParser.moveBackward(count + 1);
return;
}
}
else {
hasSpaceAfterRightParenthesis = (count > 0);
}
// Parse 'AS'
if (wordParser.startsWithIdentifier(AS)) {
asIdentifier = wordParser.moveForward(AS);
hasSpaceAfterAs = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the identification variable
boolean parseVariable = true;
if (!tolerant &&
!hasLeftParenthesis &&
!hasRightParenthesis &&
hasSpaceAfterRightParenthesis &&
asIdentifier == null &&
isParsingComplete(wordParser, wordParser.word(), null)) {
parseVariable = false;
}
if (parseVariable) {
identificationVariable = parse(wordParser, IdentificationVariableBNF.ID, tolerant);
}
// Revert back some options because what was parsed is a derived collection member declaration
if (!hasLeftParenthesis &&
!hasRightParenthesis &&
hasSpaceAfterRightParenthesis &&
asIdentifier == null &&
identificationVariable == null) {
hasSpaceAfterRightParenthesis = false;
wordParser.moveBackward(count);
}
}
/**
* {@inheritDoc}
*/
@Override
protected void toParsedText(StringBuilder writer, boolean actual) {
// 'IN'
writer.append(actual ? inIdentifier : getText());
// '('
if (hasLeftParenthesis) {
writer.append(LEFT_PARENTHESIS);
}
else if (hasSpaceAfterIn) {
writer.append(SPACE);
}
// Collection-valued path expression
if (collectionValuedPathExpression != null) {
collectionValuedPathExpression.toParsedText(writer, actual);
}
// ')'
if (hasRightParenthesis) {
writer.append(RIGHT_PARENTHESIS);
}
if (hasSpaceAfterRightParenthesis) {
writer.append(SPACE);
}
// 'AS'
if (asIdentifier != null) {
writer.append(actual ? asIdentifier : AS);
}
if (hasSpaceAfterAs) {
writer.append(SPACE);
}
// Identification variable
if (identificationVariable != null) {
identificationVariable.toParsedText(writer, actual);
}
}
/**
* Creates a string representation of this expression up and excluding the AS identifier.
*
* @return The string representation of a section of this expression
*/
public String toParsedTextUntilAs() {
StringBuilder writer = new StringBuilder();
// 'IN'
writer.append(getText());
// '('
if (hasLeftParenthesis) {
writer.append(LEFT_PARENTHESIS);
}
else if (hasSpaceAfterIn) {
writer.append(SPACE);
}
// Collection-valued path expression
if (collectionValuedPathExpression != null) {
collectionValuedPathExpression.toParsedText(writer, false);
}
// ')'
if (hasRightParenthesis) {
writer.append(RIGHT_PARENTHESIS);
}
if (hasSpaceAfterRightParenthesis) {
writer.append(SPACE);
}
return writer.toString();
}
}