blob: f7dabe160d0ac7c95387cba3ec5cdce541a96889 [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 three expressions
* separated by a comma.
*
* <div><b>BNF:</b> <code>expression ::= &lt;identifier&gt;(first_expression, second_expression, third_expression)</code></div>
*
* @see LocateExpression
* @see SubstringExpression
*
* @version 2.5.1
* @since 2.3
* @author Pascal Filion
*/
public abstract class AbstractTripleEncapsulatedExpression 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 hasFirstComma;
/**
* Determines whether the comma separating the first and second expression was parsed.
*/
private boolean hasSecondComma;
/**
* Determines whether a whitespace is following the comma.
*/
private boolean hasSpaceAfterFirstComma;
/**
* Determines whether a whitespace is following the comma.
*/
private boolean hasSpaceAfterSecondComma;
/**
* Determines which child expression is been currently parsed.
*/
protected int parameterIndex;
/**
* The {@link Expression} that represents the second expression.
*/
private AbstractExpression secondExpression;
/**
* The {@link Expression} that represents the first expression.
*/
private AbstractExpression thirdExpression;
/**
* Creates a new <code>AbstractTripleEncapsulatedExpression</code>.
*
* @param parent The parent of this expression
* @param identifier The JPQL identifier that starts this expression
*/
protected AbstractTripleEncapsulatedExpression(AbstractExpression parent, String identifier) {
super(parent, identifier);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getFirstExpression().accept(visitor);
getSecondExpression().accept(visitor);
getThirdExpression().accept(visitor);
}
@Override
protected void addChildrenTo(Collection<Expression> children) {
children.add(getFirstExpression());
children.add(getSecondExpression());
children.add(getThirdExpression());
}
@Override
protected void addOrderedEncapsulatedExpressionTo(List<Expression> children) {
// Fist expression
if (firstExpression != null) {
children.add(firstExpression);
}
// ','
if (hasFirstComma) {
children.add(buildStringExpression(COMMA));
}
if (hasSpaceAfterFirstComma) {
children.add(buildStringExpression(SPACE));
}
// Second expression
if (secondExpression != null) {
children.add(secondExpression);
}
// ','
if (hasSecondComma) {
children.add(buildStringExpression(COMMA));
}
if (hasSpaceAfterSecondComma) {
children.add(buildStringExpression(SPACE));
}
// Third expression
if (thirdExpression != null) {
children.add(thirdExpression);
}
}
/**
* Creates a new {@link CollectionExpression} that will wrap the first, second and third
* expressions.
*
* @return The first, second and third expressions represented by a temporary collection
*/
public final CollectionExpression buildCollectionExpression() {
List<AbstractExpression> children = new ArrayList<>(3);
children.add((AbstractExpression) getFirstExpression());
children.add((AbstractExpression) getSecondExpression());
children.add((AbstractExpression) getThirdExpression());
List<Boolean> commas = new ArrayList<>(3);
commas.add(hasFirstComma);
commas.add(hasSecondComma);
commas.add(Boolean.FALSE);
List<Boolean> spaces = new ArrayList<>(3);
spaces.add(hasSpaceAfterFirstComma);
spaces.add(hasSpaceAfterSecondComma);
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(getParameterQueryBNFId(0));
}
if ((secondExpression != null) && secondExpression.isAncestor(expression)) {
return getQueryBNF(getParameterQueryBNFId(1));
}
if ((thirdExpression != null) && thirdExpression.isAncestor(expression)) {
return getQueryBNF(getParameterQueryBNFId(2));
}
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 unique identifier of the {@link JPQLQueryBNF} to be used to parse one of the
* encapsulated expression at the given position.
*
* @param index The position of the encapsulated {@link Expression} that needs to be parsed
* within the parenthesis, which starts at position 0
* @return The ID of the {@link JPQLQueryBNF} to be used to parse one of the encapsulated expression
*/
public abstract String getParameterQueryBNFId(int index);
/**
* 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;
}
/**
* Returns the {@link Expression} that represents the first expression.
*
* @return The expression that was parsed representing the first expression
*/
public final Expression getThirdExpression() {
if (thirdExpression == null) {
thirdExpression = buildNullExpression();
}
return thirdExpression;
}
@Override
public boolean hasEncapsulatedExpression() {
return hasFirstExpression() || hasFirstComma ||
hasSecondExpression() || hasSecondComma ||
hasThirdExpression();
}
/**
* 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 hasFirstComma() {
return hasFirstComma;
}
/**
* 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 comma was parsed after the second expression.
*
* @return <code>true</code> if a comma was parsed after the second expression; <code>false</code>
* otherwise
*/
public final boolean hasSecondComma() {
return hasSecondComma;
}
/**
* 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 first comma.
*
* @return <code>true</code> if there was a whitespace after the first comma; <code>false</code>
* otherwise
*/
public final boolean hasSpaceAfterFirstComma() {
return hasSpaceAfterFirstComma;
}
/**
* Determines whether a whitespace was parsed after the second comma.
*
* @return <code>true</code> if there was a whitespace after the second comma; <code>false</code>
* otherwise
*/
public final boolean hasSpaceAfterSecondComma() {
return hasSpaceAfterSecondComma;
}
/**
* Determines whether the third expression of the query was parsed.
*
* @return <code>true</code> if the third expression was parsed; <code>false</code> if it was not
* parsed
*/
public final boolean hasThirdExpression() {
return thirdExpression != null &&
!thirdExpression.isNull();
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
char character = wordParser.character();
// When parsing an invalid JPQL query (eg: LOCATE + ABS(e.name)) then "+ ABS(e.name)"
// should not be parsed as an invalid first expression
if ((parameterIndex == 0) &&
((character == '+') || (character == '-')) &&
!hasLeftParenthesis()) {
parameterIndex = -1;
return true;
}
return super.isParsingComplete(wordParser, word, expression);
}
/**
* Determines whether the third expression is an optional expression, which means a valid query
* can have it or not.
*
* @return <code>true</code> if the third expression can either be present or not in a valid
* query; <code>false</code> if it's mandatory
*/
protected abstract boolean isThirdExpressionOptional();
@Override
protected void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant) {
int count = 0;
// Parse the first expression
parameterIndex = 0;
firstExpression = parse(wordParser, getParameterQueryBNFId(0), tolerant);
if (firstExpression != null) {
count = wordParser.skipLeadingWhitespace();
}
// See comment in isParsingComplete()
else if (parameterIndex == -1) {
return;
}
// Parse ','
hasFirstComma = wordParser.startsWith(COMMA);
if (hasFirstComma) {
count = 0;
wordParser.moveForward(1);
hasSpaceAfterFirstComma = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the second expression
parameterIndex = 1;
secondExpression = parse(wordParser, getParameterQueryBNFId(1), tolerant);
if (!hasFirstComma) {
hasSpaceAfterFirstComma = (count > 0);
}
count = wordParser.skipLeadingWhitespace();
// Parse ','
hasSecondComma = wordParser.startsWith(COMMA);
if (hasSecondComma) {
count = 0;
wordParser.moveForward(1);
hasSpaceAfterSecondComma = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the third expression
parameterIndex = 2;
thirdExpression = parse(wordParser, getParameterQueryBNFId(2), tolerant);
if (!hasSecondComma && (!isThirdExpressionOptional() || (thirdExpression != null))) {
hasSpaceAfterSecondComma = (count > 0);
}
}
@Override
protected void removeEncapsulatedExpression() {
hasFirstComma = false;
hasSecondComma = false;
firstExpression = null;
thirdExpression = null;
secondExpression = null;
hasSpaceAfterFirstComma = false;
hasSpaceAfterSecondComma = false;
}
@Override
protected final void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) {
// First expression
if (firstExpression != null) {
firstExpression.toParsedText(writer, actual);
}
// ','
if (hasFirstComma) {
writer.append(COMMA);
}
if (hasSpaceAfterFirstComma) {
writer.append(SPACE);
}
// Second expression
if (secondExpression != null) {
secondExpression.toParsedText(writer, actual);
}
// ','
if (hasSecondComma) {
writer.append(COMMA);
}
if (hasSpaceAfterSecondComma) {
writer.append(SPACE);
}
// Third expression
if (thirdExpression != null) {
thirdExpression.toParsedText(writer, actual);
}
}
}