blob: e48ccdbd0153e503c5c985d93d33e23c00ed45ac [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.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 <b>IN</b>. The argument to the <b>IN</b>
* 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.
*
* <div><b>BNF:</b> <code>collection_member_declaration ::= IN(collection_valued_path_expression) [AS] identification_variable</code><p></p></div>
* or
* <div><b>BNF:</b> <code>derived_collection_member_declaration ::= IN superquery_identification_variable.{single_valued_object_field.}*collection_valued_field</code><p></p></div>
* <p>
* Example: <code><b>SELECT</b> t <b>FROM</b> Player p, <b>IN</b> (p.teams) AS t</code>
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public final class CollectionMemberDeclaration extends AbstractExpression {
/**
* The actual <b>AS</b> 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 <b>AS</b>.
*/
private boolean hasSpaceAfterAs;
/**
* Determines whether a whitespace was parsed after <b>IN</b>.
*/
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 <b>IN</b> identifier found in the string representation of the JPQL query.
*/
private String inIdentifier;
/**
* Creates a new <code>CollectionMemberDeclaration</code>.
*
* @param parent The parent of this expression
*/
public CollectionMemberDeclaration(AbstractExpression parent) {
super(parent, IN);
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getCollectionValuedPathExpression().accept(visitor);
getIdentificationVariable().accept(visitor);
}
@Override
protected void addChildrenTo(Collection<Expression> children) {
children.add(getCollectionValuedPathExpression());
children.add(getIdentificationVariable());
}
@Override
protected void addOrderedChildrenTo(List<Expression> 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);
}
}
@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 <b>AS</b> identifier found in the string representation of the JPQL query,
* which has the actual case that was used.
*
* @return The <b>AS</b> identifier that was actually parsed
*/
public String getActualAsIdentifier() {
return asIdentifier;
}
/**
* Returns the actual <b>IN</b> identifier found in the string representation of the JPQL query,
* which has the actual case that was used.
*
* @return The <b>IN</b> 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;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(CollectionMemberDeclarationBNF.ID);
}
/**
* Determines whether the identifier <b>AS</b> was parsed.
*
* @return <code>true</code> if the identifier <b>AS</b> was parsed; <code>false</code> otherwise
*/
public boolean hasAs() {
return asIdentifier != null;
}
/**
* Determines whether the collection-valued path expression was parsed.
*
* @return <code>true</code> if the query has the collection-valued path expression;
* <code>false</code> otherwise
*/
public boolean hasCollectionValuedPathExpression() {
return collectionValuedPathExpression != null &&
!collectionValuedPathExpression.isNull();
}
/**
* Determines whether the identification variable was parsed.
*
* @return <code>true</code> if the query has the identification variable; <code>false</code>
* otherwise
*/
public boolean hasIdentificationVariable() {
return identificationVariable != null &&
!identificationVariable.isNull() &&
!identificationVariable.isVirtual();
}
/**
* Determines whether the open parenthesis was parsed or not.
*
* @return <code>true</code> if the open parenthesis was present in the string version of the
* query; <code>false</code> otherwise
*/
public boolean hasLeftParenthesis() {
return hasLeftParenthesis;
}
/**
* Determines whether the close parenthesis was parsed or not.
*
* @return <code>true</code> if the close parenthesis was present in the string version of the
* query; <code>false</code> otherwise
*/
public boolean hasRightParenthesis() {
return hasRightParenthesis;
}
/**
* Determines whether a whitespace was found after <b>AS</b>.
*
* @return <code>true</code> if there was a whitespace after <b>AS</b>; <code>false</code>
* otherwise
*/
public boolean hasSpaceAfterAs() {
return hasSpaceAfterAs;
}
/**
* Determines whether a whitespace was found after <b>IN</b>.
*
* @return <code>true</code> if there was a whitespace after <b>IN</b>; <code>false</code>
* otherwise
*/
public boolean hasSpaceAfterIn() {
return hasSpaceAfterIn;
}
/**
* Determines whether a whitespace was found after the close parenthesis.
*
* @return <code>true</code> if there was a whitespace after the right parenthesis;
* <code>false</code> otherwise
*/
public boolean hasSpaceAfterRightParenthesis() {
return hasSpaceAfterRightParenthesis;
}
/**
* Determines whether this collection member declaration is used as a derived collection-valued
* path expression.
*
* @return <code>true</code> if this collection member declaration is used as this form:
* "<code><b>IN</b> collection_valued_path_expression</code>" in a subquery; <code>false</code>
* if it's used as this form: <code><b>IN</b>(collection_valued_path_expression)
* [<b>AS</b>] identification_variable</code>" in a top-level or subquery queries
* @since 2.4
*/
public boolean isDerived() {
return !hasLeftParenthesis &&
!hasRightParenthesis &&
!hasSpaceAfterRightParenthesis &&
asIdentifier == null &&
!hasIdentificationVariable();
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
return wordParser.character() == RIGHT_PARENTHESIS ||
word.equalsIgnoreCase(AS) ||
super.isParsingComplete(wordParser, word, expression);
}
@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);
}
}
@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 <b>AS</b> 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();
}
}