blob: a6674587786756645f05394b5d98b5f06b06f712 [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.List;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* This expression handles parsing the identifier followed by an expression encapsulated within
* parenthesis.
*
* <div><b>BNF:</b> <code>expression ::= &lt;identifier&gt;(expression)</code></div>
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public abstract class AbstractEncapsulatedExpression extends AbstractExpression {
/**
* 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;
/**
* Special flag used to separate the identifier with the encapsulated expression when the left
* parenthesis is missing.
*/
private boolean hasSpaceAfterIdentifier;
/**
* The actual identifier found in the string representation of the JPQL query.
*/
private String identifier;
/**
* Creates a new <code>AbstractEncapsulatedExpression</code>.
*
* @param parent The parent of this expression
* @param identifier The JPQL identifier that starts this expression
*/
protected AbstractEncapsulatedExpression(AbstractExpression parent, String identifier) {
super(parent, identifier);
}
@Override
protected final void addOrderedChildrenTo(List<Expression> children) {
// Identifier
children.add(buildStringExpression(getText()));
// '('
if (hasLeftParenthesis) {
children.add(buildStringExpression(LEFT_PARENTHESIS));
}
else if (hasSpaceAfterIdentifier) {
children.add(buildStringExpression(SPACE));
}
addOrderedEncapsulatedExpressionTo(children);
// ')'
if (hasRightParenthesis) {
children.add(buildStringExpression(RIGHT_PARENTHESIS));
}
}
/**
* Adds the {@link Expression Expressions} representing the encapsulated {@link
* Expression}.
*
* @param children The list used to store the string representation of the encapsulated {@link
* Expression}
*/
protected abstract void addOrderedEncapsulatedExpressionTo(List<Expression> children);
protected boolean areLogicalIdentifiersSupported() {
return false;
}
/**
* Returns the actual identifier found in the string representation of the JPQL query, which has
* the actual case that was used.
*
* @return The JPQL identifier that was actually parsed
*/
public final String getActualIdentifier() {
return identifier;
}
/**
* Returns the JPQL identifier of this expression.
*
* @return The JPQL identifier
*/
public final String getIdentifier() {
return getText();
}
/**
* Determines whether something was parsed after the left parenthesis.
*
* @return <code>true</code> if something was parsed; <code>false</code> otherwise
*/
public abstract boolean hasEncapsulatedExpression();
/**
* 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 final 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 final boolean hasRightParenthesis() {
return hasRightParenthesis;
}
/**
* Determines whether a whitespace was parsed after the identifier rather than the left
* parenthesis. This can happen in incomplete query of this form: <b>ABS 4 + 5)</b>.
*
* @return <code>true</code> if a whitespace was parsed after the identifier; <code>false</code>
* otherwise
*/
public boolean hasSpaceAfterIdentifier() {
return hasSpaceAfterIdentifier;
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
if (wordParser.startsWith(RIGHT_PARENTHESIS) ||
word.equalsIgnoreCase(WHEN) ||
word.equalsIgnoreCase(SET) ||
word.equalsIgnoreCase(AS) ||
super.isParsingComplete(wordParser, word, expression)) {
return true;
}
if (areLogicalIdentifiersSupported()) {
return false;
}
// This check for compound functions, such as AND, OR, <, <=, =, >=, >, BETWEEN
return word.equalsIgnoreCase(AND) ||
word.equalsIgnoreCase(OR) ||
word.equalsIgnoreCase(BETWEEN) ||
word.equalsIgnoreCase(NOT_BETWEEN) ||
wordParser.startsWith(LOWER_THAN) ||
wordParser.startsWith(GREATER_THAN) ||
wordParser.startsWith(EQUAL);
}
@Override
protected void parse(WordParser wordParser, boolean tolerant) {
// Parse the identifier
this.identifier = wordParser.moveForward(getText());
int position = wordParser.position();
int count = wordParser.skipLeadingWhitespace();
int whitespaceAfterLeftParenthesis = 0;
// Parse '('
hasLeftParenthesis = wordParser.startsWith(LEFT_PARENTHESIS);
if (hasLeftParenthesis) {
wordParser.moveForward(1);
count = wordParser.skipLeadingWhitespace();
whitespaceAfterLeftParenthesis = count;
}
// Parse the expression
parseEncapsulatedExpression(wordParser, whitespaceAfterLeftParenthesis, tolerant);
if (hasEncapsulatedExpression()) {
// When having incomplete query of this form: ABS 4 + 5),
// a whitespace is required
hasSpaceAfterIdentifier = !hasLeftParenthesis && (count > 0);
count = wordParser.skipLeadingWhitespace();
}
// Parse ')'
if (shouldParseRightParenthesis(wordParser, tolerant)) {
hasRightParenthesis = wordParser.startsWith(RIGHT_PARENTHESIS);
if (hasRightParenthesis) {
wordParser.moveForward(1);
}
else {
// Neither '(' and ') were parsed, then it makes no sense
// the parsed expression is part of this expression
if (!hasLeftParenthesis && hasEncapsulatedExpression()) {
hasSpaceAfterIdentifier = false;
removeEncapsulatedExpression();
wordParser.setPosition(position);
}
else {
wordParser.moveBackward(count);
}
}
}
else if ((count > 0) && !hasEncapsulatedExpression()) {
wordParser.moveBackward(count);
}
}
/**
* Parses the encapsulated expression by starting at the current position, which is part of the
* given {@link WordParser}.
*
* @param wordParser The text to parse based on the current position of the cursor
* @param whitespaceCount The number of whitespace characters that were parsed after '('
* @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
* try to parse invalid or incomplete queries
*/
protected abstract void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant);
/**
* Removes the encapsulated {@link Expression} that was parsed, it should not be part of this one.
* This happens when the parsed information does not have both '(' and ')'.
*/
protected abstract void removeEncapsulatedExpression();
/**
* Determines whether the right parenthesis should be parsed or not by this expression. There is
* a possible case where this expression should have optional left and right parenthesis. If
* there is no left parenthesis, then it would most likely mean the right parenthesis does not
* belong to this expression.
*
* @param wordParser The text to parse based on the current position of the cursor
* @param tolerant Determines whether the parsing system should be tolerant, meaning if it should
* try to parse invalid or incomplete queries
* @return <code>true</code> if the right parenthesis should be owned by this expression if it
* is the next character to scan; <code>false</code> otherwise
*/
protected boolean shouldParseRightParenthesis(WordParser wordParser, boolean tolerant) {
return true;
}
@Override
protected final void toParsedText(StringBuilder writer, boolean actual) {
// Identifier
writer.append(actual ? identifier : getText());
// '('
if (hasLeftParenthesis) {
writer.append(LEFT_PARENTHESIS);
}
else if (hasSpaceAfterIdentifier) {
writer.append(SPACE);
}
// Encapsulated expression
toParsedTextEncapsulatedExpression(writer, actual);
// ')'
if (hasRightParenthesis) {
writer.append(RIGHT_PARENTHESIS);
}
}
/**
* Generates a string representation of the encapsulated {@link Expression}.
*
* @param writer The buffer used to append the encapsulated {@link Expression}'s string
* representation
* @param actual Determines whether to include any characters that are considered
* virtual, i.e. that was parsed when the query is incomplete and is needed for functionality
* like content assist
*/
protected abstract void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual);
}