| /* |
| * 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 ::= <identifier>(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); |
| } |
| } |
| } |