/******************************************************************************* * Copyright (c) 2006, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation * ******************************************************************************/ package org.eclipse.persistence.jpa.jpql.parser; import java.util.List; import org.eclipse.persistence.jpa.jpql.WordParser; /** * This expression handles parsing the identifier followed by an expression encapsulated within * parenthesis. * * <div><b>BNF:</b> <code>expression ::= <identifier>(expression)</code><p></div> * * @version 2.5 * @since 2.3 * @author Pascal Filion */ public abstract class AbstractEncapsulatedExpression extends AbstractExpression { /** * Flag used to determine if the closing parenthesis is present in the query. */ private boolean hasLeftParenthesis; /** * Flag used to determine if the opening parenthesis is present in the query. */ private boolean hasRightParenthesis; /** * Special flag used to separate the identifier with the encapsulated expression when the left * parenthesis is missing. */ private boolean hasSpaceAfterIdentifier; /** * The actual identifier found in the string representation of the JPQL query. */ private String identifier; /** * Creates a new <code>AbstractEncapsulatedExpression</code>. * * @param parent The parent of this expression * @param identifier The JPQL identifier that starts this expression */ protected AbstractEncapsulatedExpression(AbstractExpression parent, String identifier) { super(parent, identifier); } /** * {@inheritDoc} */ @Override protected final void addOrderedChildrenTo(List<Expression> children) { // Identifier children.add(buildStringExpression(getText())); // '(' if (hasLeftParenthesis) { children.add(buildStringExpression(LEFT_PARENTHESIS)); } else if (hasSpaceAfterIdentifier) { children.add(buildStringExpression(SPACE)); } addOrderedEncapsulatedExpressionTo(children); // ')' if (hasRightParenthesis) { children.add(buildStringExpression(RIGHT_PARENTHESIS)); } } /** * Adds the {@link Expression Expressions} representing the encapsulated {@link * Expression}. * * @param children The list used to store the string representation of the encapsulated {@link * Expression} */ protected abstract void addOrderedEncapsulatedExpressionTo(List<Expression> children); protected boolean areLogicalIdentifiersSupported() { return false; } /** * Returns the actual identifier found in the string representation of the JPQL query, which has * the actual case that was used. * * @return The JPQL identifier that was actually parsed */ public final String getActualIdentifier() { return identifier; } /** * Returns the JPQL identifier of this expression. * * @return The JPQL identifier */ public final String getIdentifier() { return getText(); } /** * Determines whether something was parsed after the left parenthesis. * * @return <code>true</code> if something was parsed; <code>false</code> otherwise */ public abstract boolean hasEncapsulatedExpression(); /** * Determines whether the open parenthesis was parsed or not. * * @return <code>true</code> if the open parenthesis was present in the string version of the * query; <code>false</code> otherwise */ public final boolean hasLeftParenthesis() { return hasLeftParenthesis; } /** * Determines whether the close parenthesis was parsed or not. * * @return <code>true</code> if the close parenthesis was present in the string version of the * query; <code>false</code> otherwise */ public final boolean hasRightParenthesis() { return hasRightParenthesis; } /** * Determines whether a whitespace was parsed after the identifier rather than the left * parenthesis. This can happen in incomplete query of this form: <b>ABS 4 + 5)</b>. * * @return <code>true</code> if a whitespace was parsed after the identifier; <code>false</code> * otherwise */ public boolean hasSpaceAfterIdentifier() { return hasSpaceAfterIdentifier; } /** * {@inheritDoc} */ @Override protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) { if (wordParser.startsWith(RIGHT_PARENTHESIS) || word.equalsIgnoreCase(WHEN) || word.equalsIgnoreCase(SET) || word.equalsIgnoreCase(AS) || super.isParsingComplete(wordParser, word, expression)) { return true; } if (areLogicalIdentifiersSupported()) { return false; } // This check for compound functions, such as AND, OR, <, <=, =, >=, >, BETWEEN return word.equalsIgnoreCase(AND) || word.equalsIgnoreCase(OR) || word.equalsIgnoreCase(BETWEEN) || word.equalsIgnoreCase(NOT_BETWEEN) || wordParser.startsWith(LOWER_THAN) || wordParser.startsWith(GREATER_THAN) || wordParser.startsWith(EQUAL); } /** * {@inheritDoc} */ @Override protected void parse(WordParser wordParser, boolean tolerant) { // Parse the identifier this.identifier = wordParser.moveForward(getText()); int position = wordParser.position(); int count = wordParser.skipLeadingWhitespace(); int whitespaceAfterLeftParenthesis = 0; // Parse '(' hasLeftParenthesis = wordParser.startsWith(LEFT_PARENTHESIS); if (hasLeftParenthesis) { wordParser.moveForward(1); count = wordParser.skipLeadingWhitespace(); whitespaceAfterLeftParenthesis = count; } // Parse the expression parseEncapsulatedExpression(wordParser, whitespaceAfterLeftParenthesis, tolerant); if (hasEncapsulatedExpression()) { // When having incomplete query of this form: ABS 4 + 5), // a whitespace is required hasSpaceAfterIdentifier = !hasLeftParenthesis && (count > 0); count = wordParser.skipLeadingWhitespace(); } // Parse ')' if (shouldParseRightParenthesis(wordParser, tolerant)) { hasRightParenthesis = wordParser.startsWith(RIGHT_PARENTHESIS); if (hasRightParenthesis) { wordParser.moveForward(1); } else { // Neither '(' and ') were parsed, then it makes no sense // the parsed expression is part of this expression if (!hasLeftParenthesis && hasEncapsulatedExpression()) { hasSpaceAfterIdentifier = false; removeEncapsulatedExpression(); wordParser.setPosition(position); } else { wordParser.moveBackward(count); } } } else if ((count > 0) && !hasEncapsulatedExpression()) { wordParser.moveBackward(count); } } /** * Parses the encapsulated expression by starting at the current position, which is part of the * given {@link WordParser}. * * @param wordParser The text to parse based on the current position of the cursor * @param whitespaceCount The number of whitespace characters that were parsed after '(' * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should * try to parse invalid or incomplete queries */ protected abstract void parseEncapsulatedExpression(WordParser wordParser, int whitespaceCount, boolean tolerant); /** * Removes the encapsulated {@link Expression} that was parsed, it should not be part of this one. * This happens when the parsed information does not have both '(' and ')'. */ protected abstract void removeEncapsulatedExpression(); /** * Determines whether the right parenthesis should be parsed or not by this expression. There is * a possible case where this expression should have optional left and right parenthesis. If * there is no left parenthesis, then it would most likely mean the right parenthesis does not * belong to this expression. * * @param wordParser The text to parse based on the current position of the cursor * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should * try to parse invalid or incomplete queries * @return <code>true</code> if the right parenthesis should be owned by this expression if it * is the next character to scan; <code>false</code> otherwise */ protected boolean shouldParseRightParenthesis(WordParser wordParser, boolean tolerant) { return true; } /** * {@inheritDoc} */ @Override protected final void toParsedText(StringBuilder writer, boolean actual) { // Identifier writer.append(actual ? identifier : getText()); // '(' if (hasLeftParenthesis) { writer.append(LEFT_PARENTHESIS); } else if (hasSpaceAfterIdentifier) { writer.append(SPACE); } // Encapsulated expression toParsedTextEncapsulatedExpression(writer, actual); // ')' if (hasRightParenthesis) { writer.append(RIGHT_PARENTHESIS); } } /** * Generates a string representation of the encapsulated {@link Expression}. * * @param writer The buffer used to append the encapsulated {@link Expression}'s string * representation * @param actual Determines whether to include any characters that are considered * virtual, i.e. that was parsed when the query is incomplete and is needed for functionality * like content assist */ protected abstract void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual); }