| /* |
| * 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.Collection; |
| import java.util.List; |
| import org.eclipse.persistence.jpa.jpql.WordParser; |
| |
| /** |
| * A <code>CollectionExpression</code> wraps many expression which they are separated by spaces |
| * and/or commas. |
| * |
| * <div><b>BNF:</b> <code>expression ::= child_item {, child_item }*</code></div> |
| * <br> |
| * or |
| * <br> |
| * <div><b>BNF:</b> <code>expression ::= child_item { child_item }*</code><p></p></div> |
| * |
| * @version 2.5 |
| * @since 2.3 |
| * @author Pascal Filion |
| */ |
| @SuppressWarnings("nls") |
| public final class CollectionExpression extends AbstractExpression { |
| |
| /** |
| * The {@link Expression Expressions} that forms a collection within another expression. |
| */ |
| private List<AbstractExpression> children; |
| |
| /** |
| * The list of flags used to determine where to separate two child {@link Expression Expressions} |
| * with a comma or with a space only. |
| */ |
| private List<Boolean> commas; |
| |
| /** |
| * Flag used to determine when a space is required after a comma. |
| */ |
| private List<Boolean> spaces; |
| |
| /** |
| * Creates a new <code>CollectionExpression</code>. |
| * |
| * @param parent The parent of this expression |
| * @param children The list of children that are regrouped together |
| * @param spaces The list of flags used to determine when to add a space after an {@link Expression} |
| * @param commas The list of flags used to determine when to add a comma after an {@link Expression} |
| */ |
| public CollectionExpression(AbstractExpression parent, |
| List<AbstractExpression> children, |
| List<Boolean> commas, |
| List<Boolean> spaces) { |
| |
| this(parent, children, commas, spaces, false); |
| } |
| |
| /** |
| * Creates a new <code>CollectionExpression</code>. |
| * |
| * @param parent The parent of this expression |
| * @param children The list of children that are regrouped together |
| * @param commas The list of flags used to determine when to add a comma after an {@link Expression} |
| * @param spaces The list of flags used to determine when to add a space after an {@link Expression} |
| * @param temporary Flag used to determine if this expression is temporarily used, which means |
| * the children will not be parented to this object |
| */ |
| public CollectionExpression(AbstractExpression parent, |
| List<AbstractExpression> children, |
| List<Boolean> commas, |
| List<Boolean> spaces, |
| boolean temporary) { |
| super(parent); |
| |
| this.children = children; |
| this.commas = commas; |
| this.spaces = spaces; |
| |
| if (!temporary) { |
| updateBackpointers(); |
| } |
| } |
| |
| @Override |
| public void accept(ExpressionVisitor visitor) { |
| visitor.visit(this); |
| } |
| |
| /** |
| * Visits the child {@link Expression} at the given position by the given {@link ExpressionVisitor |
| * visitor}. |
| * |
| * @param index The index of the child to visit |
| * @param visitor The {@link ExpressionVisitor} to visit a specific child |
| * @since 2.4 |
| */ |
| public void accept(int index, ExpressionVisitor visitor) { |
| getChild(index).accept(visitor); |
| } |
| |
| @Override |
| public void acceptChildren(ExpressionVisitor visitor) { |
| for (Expression child : children()) { |
| child.accept(visitor); |
| } |
| } |
| |
| @Override |
| protected void addChildrenTo(Collection<Expression> children) { |
| |
| // Make sure all children are non null |
| for (int index = 0, childCount = this.children.size(); index < childCount; index++) { |
| getChildInternal(index); |
| } |
| |
| children.addAll(this.children); |
| } |
| |
| @Override |
| protected void addOrderedChildrenTo(List<Expression> children) { |
| |
| children(); |
| |
| for (int index = 0, count = this.children.size(); index < count; index++) { |
| Expression expression = getChild(index); |
| children.add(expression); |
| |
| // Write ',' |
| if (hasComma(index)) { |
| children.add(buildStringExpression(COMMA)); |
| } |
| |
| // Write whitespace |
| if (hasSpace(index)) { |
| children.add(buildStringExpression(SPACE)); |
| } |
| } |
| } |
| |
| /** |
| * Returns the count of child {@link Expression expressions}. |
| * |
| * @return The total count of {@link Expression expressions} aggregated with spaces and/or commas |
| */ |
| public int childrenSize() { |
| children(); |
| return children.size(); |
| } |
| |
| /** |
| * Determines whether this {@link CollectionExpression} ends with a comma, which means the last |
| * {@link Expression} is a "<code>null</code>" expression. |
| * |
| * @return <code>true</code> if the string representation of this {@link CollectionExpression} |
| * ends with a comma (the ending space is not checked) |
| */ |
| public boolean endsWithComma() { |
| |
| children(); |
| |
| if (children.get(children.size() - 1).isNull()) { |
| return commas.get(commas.size() - 2); |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Determines whether this {@link CollectionExpression} ends with a space, which means the last |
| * {@link Expression} is a "<code>null</code>" expression. |
| * |
| * @return <code>true</code> if the string representation of this {@link CollectionExpression} |
| * ends with a space (the ending comma is not checked) |
| */ |
| public boolean endsWithSpace() { |
| |
| children(); |
| |
| if (children.get(children.size() - 1).isNull()) { |
| return spaces.get(spaces.size() - 2); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public JPQLQueryBNF findQueryBNF(Expression expression) { |
| return getParent().findQueryBNF(expression); |
| } |
| |
| /** |
| * Retrieves the child {@link Expression} at the given position. |
| * |
| * @param index The position of the child {@link Expression} to retrieve |
| * @return The child {@link Expression} at the given position |
| */ |
| public Expression getChild(int index) { |
| return getChildInternal(index); |
| } |
| |
| private AbstractExpression getChildInternal(int index) { |
| |
| AbstractExpression child = children.get(index); |
| |
| if (child == null) { |
| child = buildNullExpression(); |
| children.set(index, child); |
| } |
| |
| return child; |
| } |
| |
| @Override |
| public JPQLQueryBNF getQueryBNF() { |
| return getParent().getQueryBNF(); |
| } |
| |
| /** |
| * Determines whether a comma was parsed at the given position. The index is the position of the |
| * comma that is following the child at the same position. |
| * |
| * @param index The index of the child {@link Expression} to verify if there is a comma following it |
| * @return <code>true</code> if a comma is following the child {@link Expression} at the given |
| * index; <code>false</code> otherwise |
| */ |
| public boolean hasComma(int index) { |
| children(); |
| return (index < commas.size()) && commas.get(index); |
| } |
| |
| /** |
| * Determines whether a space was parsed at the given position. The index is the position of the |
| * space that is following the child at the same position, which is after a comma, if one was |
| * also parsed at that location. |
| * |
| * @param index The index of the child {@link Expression} to verify if there is a space following |
| * it, which could be after a comma, if one was parsed |
| * @return <code>true</code> if a space is following the child {@link Expression} at the given |
| * index; <code>false</code> otherwise |
| */ |
| public boolean hasSpace(int index) { |
| children(); |
| return (index < spaces.size()) && (spaces.get(index) || hasComma(index) && (index + 1 < children.size() ? !getChildInternal(index + 1).isNull() : false)); |
| } |
| |
| /** |
| * Retrieves the index of the given <code>Expression</code>. |
| * |
| * @param expression The <code>Expression</code> that might be a child of this expression |
| * @return The index in the collection of the given <code>Expression</code> or -1 if it is not a child |
| */ |
| public int indexOf(Expression expression) { |
| children(); |
| return children.indexOf(expression); |
| } |
| |
| @Override |
| protected void parse(WordParser wordParser, boolean tolerant) { |
| throw new IllegalAccessError("This method shouln't be invoked, text=" + wordParser); |
| } |
| |
| /** |
| * Returns a string representation of this {@link Expression} and its children. The expression |
| * should contain whitespace even if the beautified version would not have any. For instance, |
| * "SELECT e " should be returned where {@link Expression#toParsedText()} would return "SELECT e". |
| * |
| * @param endIndex The index used to determine when to create the string representation, which |
| * is exclusive |
| * @return The string representation of this {@link Expression} |
| */ |
| public String toActualText(int endIndex) { |
| StringBuilder writer = new StringBuilder(); |
| toParsedText(writer, endIndex, true); |
| return writer.toString(); |
| } |
| |
| /** |
| * Generates a string representation of this {@link CollectionExpression}. |
| * |
| * @param endIndex The index used to determine when to create the string representation, which |
| * is exclusive |
| * @return The string representation of this {@link Expression} |
| */ |
| public String toParsedText(int endIndex) { |
| StringBuilder writer = new StringBuilder(); |
| toParsedText(writer, endIndex, false); |
| return writer.toString(); |
| } |
| |
| @Override |
| protected void toParsedText(StringBuilder writer, boolean actual) { |
| toParsedText(writer, childrenSize(), actual); |
| } |
| |
| private void toParsedText(StringBuilder writer, int endIndex, boolean actual) { |
| |
| for (int index = 0, count = children.size(); index < endIndex; index++) { |
| |
| AbstractExpression expression = children.get(index); |
| |
| // Write the child expression |
| if (expression != null) { |
| expression.toParsedText(writer, actual); |
| } |
| |
| // Write ',' |
| if (commas.get(index)) { |
| writer.append(COMMA); |
| |
| // If there is a space, then add it |
| if (spaces.get(index)) { |
| writer.append(SPACE); |
| } |
| // Otherwise check if the next expression is not null, if it's null, |
| // then a space will not be added |
| else if (index + 1 < count) { |
| AbstractExpression nextExpression = children.get(index + 1); |
| |
| if ((nextExpression != null) && !nextExpression.isNull()) { |
| writer.append(SPACE); |
| } |
| } |
| } |
| // Write ' ' |
| else if (spaces.get(index)) { |
| writer.append(SPACE); |
| } |
| } |
| } |
| |
| private void updateBackpointers() { |
| for (AbstractExpression child : children) { |
| if (child != null) { |
| child.setParent(this); |
| } |
| } |
| } |
| } |