| /* |
| * 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 ::= <identifier>(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<>(3); |
| children.add((AbstractExpression) getFirstExpression()); |
| children.add((AbstractExpression) getSecondExpression()); |
| |
| List<Boolean> commas = new ArrayList<>(2); |
| commas.add(hasComma); |
| commas.add(Boolean.FALSE); |
| |
| List<Boolean> spaces = new ArrayList<>(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); |
| } |
| } |
| } |