blob: 705ea77d864b44a08350b541b2e0b7a5ba4b6789 [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.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* This {@link Expression} takes care of parsing an expression that encapsulates two expressions
* separated by a comma.
*
* <div><b>BNF:</b> <code>expression ::= &lt;identifier&gt;(first_expression, second_expression)</code></div>
*
* @see ConcatExpression
* @see ModExpression
* @see NullIfExpression
*
* @version 2.5.1
* @since 2.3
* @author Pascal Filion
*/
public abstract class AbstractDoubleEncapsulatedExpression extends AbstractEncapsulatedExpression {
/**
* The {@link Expression} that represents the first expression.
*/
private AbstractExpression firstExpression;
/**
* Determines whether the comma separating the first and second expression was parsed.
*/
private boolean hasComma;
/**
* Determines whether a whitespace is following the comma.
*/
private boolean hasSpaceAfterComma;
/**
* The {@link Expression} that represents the second expression.
*/
private AbstractExpression secondExpression;
/**
* Creates a new <code>AbstractDoubleEncapsulatedExpression</code>.
*
* @param parent The parent of this expression
* @param identifier The JPQL identifier that starts this expression
*/
protected AbstractDoubleEncapsulatedExpression(AbstractExpression parent, String identifier) {
super(parent, identifier);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getFirstExpression().accept(visitor);
getSecondExpression().accept(visitor);
}
@Override
protected void addChildrenTo(Collection<Expression> children) {
children.add(getFirstExpression());
children.add(getSecondExpression());
}
@Override
protected void addOrderedEncapsulatedExpressionTo(List<Expression> children) {
// Fist expression
if (firstExpression != null) {
children.add(firstExpression);
}
// ','
if (hasComma) {
children.add(buildStringExpression(COMMA));
}
if (hasSpaceAfterComma) {
children.add(buildStringExpression(SPACE));
}
// Second expression
if (secondExpression != null) {
children.add(secondExpression);
}
}
/**
* Creates a new {@link CollectionExpression} that will wrap the first and second expressions.
*
* @return The first and second expressions wrapped by a temporary collection
*/
public final CollectionExpression buildCollectionExpression() {
List<AbstractExpression> children = new ArrayList<AbstractExpression>(3);
children.add((AbstractExpression) getFirstExpression());
children.add((AbstractExpression) getSecondExpression());
List<Boolean> commas = new ArrayList<Boolean>(2);
commas.add(hasComma);
commas.add(Boolean.FALSE);
List<Boolean> spaces = new ArrayList<Boolean>(2);
spaces.add(hasSpaceAfterComma);
spaces.add(Boolean.FALSE);
return new CollectionExpression(this, children, commas, spaces, true);
}
@Override
public JPQLQueryBNF findQueryBNF(Expression expression) {
if ((firstExpression != null) && firstExpression.isAncestor(expression)) {
return getQueryBNF(parameterExpressionBNF(0));
}
if ((secondExpression != null) && secondExpression.isAncestor(expression)) {
return getQueryBNF(parameterExpressionBNF(1));
}
return super.findQueryBNF(expression);
}
/**
* Returns the {@link Expression} that represents the first expression.
*
* @return The expression that was parsed representing the first expression
*/
public final Expression getFirstExpression() {
if (firstExpression == null) {
firstExpression = buildNullExpression();
}
return firstExpression;
}
/**
* Returns the {@link Expression} that represents the second expression.
*
* @return The expression that was parsed representing the second expression
*/
public final Expression getSecondExpression() {
if (secondExpression == null) {
secondExpression = buildNullExpression();
}
return secondExpression;
}
/**
* Determines whether the comma was parsed after the first expression.
*
* @return <code>true</code> if a comma was parsed after the first expression; <code>false</code>
* otherwise
*/
public final boolean hasComma() {
return hasComma;
}
@Override
public boolean hasEncapsulatedExpression() {
return hasFirstExpression() || hasComma || hasSecondExpression();
}
/**
* Determines whether the first expression of the query was parsed.
*
* @return <code>true</code> if the first expression was parsed; <code>false</code> if it was
* not parsed
*/
public final boolean hasFirstExpression() {
return firstExpression != null &&
!firstExpression.isNull();
}
/**
* Determines whether the second expression of the query was parsed.
*
* @return <code>true</code> if the second expression was parsed; <code>false</code> if it was
* not parsed
*/
public final boolean hasSecondExpression() {
return secondExpression != null &&
!secondExpression.isNull();
}
/**
* Determines whether a whitespace was parsed after the comma.
*
* @return <code>true</code> if there was a whitespace after the comma; <code>false</code>
* otherwise
*/
public final boolean hasSpaceAfterComma() {
return hasSpaceAfterComma;
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
char character = wordParser.character();
return character == COMMA ||
character == RIGHT_PARENTHESIS ||
word.equalsIgnoreCase(AND) ||
word.equalsIgnoreCase(OR) ||
super.isParsingComplete(wordParser, word, expression);
}
/**
* Determines whether the second expression is optional or not.
*
* @return <code>true</code> if the second expression is optional; <code>false</code> if it is
* required
*/
protected boolean isSecondExpressionOptional() {
return false;
}
/**
* Returns the BNF to be used to parse one of the encapsulated expression.
*
* @param index The position of the encapsulated {@link Expression} that needs to be parsed
* within the parenthesis, which starts at position 0
* @return The BNF to be used to parse one of the encapsulated expression
*/
public abstract String parameterExpressionBNF(int index);
@Override
protected void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant) {
int count = 0;
// Parse the first expression
firstExpression = parse(wordParser, parameterExpressionBNF(0), tolerant);
if (firstExpression != null) {
count = wordParser.skipLeadingWhitespace();
}
// Parse ','
hasComma = wordParser.startsWith(COMMA);
if (hasComma) {
count = 0;
wordParser.moveForward(1);
hasSpaceAfterComma = wordParser.skipLeadingWhitespace() > 0;
}
else if (hasFirstExpression()) {
hasSpaceAfterComma = (count > 0);
}
// Parse the second expression
secondExpression = parse(wordParser, parameterExpressionBNF(1), tolerant);
if (secondExpression == null) {
if (!hasComma && isSecondExpressionOptional()) {
hasSpaceAfterComma = false;
}
wordParser.moveBackward(count);
}
}
@Override
protected void removeEncapsulatedExpression() {
hasComma = false;
firstExpression = null;
secondExpression = null;
hasSpaceAfterComma = false;
}
@Override
protected void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) {
// First expression
if (firstExpression != null) {
firstExpression.toParsedText(writer, actual);
}
// ','
if (hasComma) {
writer.append(COMMA);
}
if (hasSpaceAfterComma) {
writer.append(SPACE);
}
// Second expression
if (secondExpression != null) {
secondExpression.toParsedText(writer, actual);
}
}
}