| /* |
| * 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.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedList; |
| import java.util.List; |
| import org.eclipse.persistence.jpa.jpql.ExpressionTools; |
| import org.eclipse.persistence.jpa.jpql.JPAVersion; |
| import org.eclipse.persistence.jpa.jpql.WordParser; |
| import org.eclipse.persistence.jpa.jpql.WordParser.WordType; |
| import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable; |
| import org.eclipse.persistence.jpa.jpql.utility.iterable.SnapshotCloneListIterable; |
| |
| /** |
| * This is the abstract definition of all the parts used to create the tree hierarchy representing |
| * the parsed JPQL query. |
| * |
| * @see ExpressionFactory |
| * @see JPQLGrammar |
| * |
| * @version 2.6 |
| * @since 2.3 |
| * @author Pascal Filion |
| */ |
| @SuppressWarnings("nls") |
| public abstract class AbstractExpression implements Expression { |
| |
| /** |
| * The string representation of this {@link AbstractExpression} (which includes its children). |
| * The string includes characters that are considered virtual, i.e. that was parsed when the |
| * query is incomplete and is needed for functionality like content assist. |
| * |
| * @see #toActualText() |
| */ |
| private String actualText; |
| |
| /** |
| * The children of this {@link AbstractExpression}. |
| * |
| * @see #children() |
| */ |
| private List<Expression> children; |
| |
| /** |
| * The position of this {@link AbstractExpression} in relation to its parent hierarchy by |
| * calculating the length of the string representation of what comes before. |
| */ |
| private int offset; |
| |
| /** |
| * The string representation of this {@link AbstractExpression}. |
| * |
| * @see #orderedChildren() |
| */ |
| private List<Expression> orderedChildren; |
| |
| /** |
| * The parent of this {@link AbstractExpression} or <code>null</code> if this object is {@link |
| * JPQLExpression} - the root of the parsed tree hierarchy. |
| */ |
| private AbstractExpression parent; |
| |
| /** |
| * The string representation of this {@link AbstractExpression} (which includes its children). |
| * The string does not include characters that are considered virtual, i.e. that was parsed when |
| * the query is incomplete. |
| * |
| * @see #toParsedText() |
| */ |
| private String parsedText; |
| |
| /** |
| * This attribute can be used to store the {@link AbstractExpression}'s JPQL identifier or a literal. |
| */ |
| private String text; |
| |
| /** |
| * The constant for ','. |
| */ |
| public static final char COMMA = ','; |
| |
| /** |
| * The constant for '.'. |
| */ |
| public static final char DOT = '.'; |
| |
| /** |
| * The constant for '"'. |
| */ |
| public static final char DOUBLE_QUOTE = '\"'; |
| |
| /** |
| * The constant for '{'. |
| */ |
| public static final char LEFT_CURLY_BRACKET = '{'; |
| |
| /** |
| * The constant for '('. |
| */ |
| public static final char LEFT_PARENTHESIS = '('; |
| |
| /** |
| * The constant for a character that is not defined. |
| */ |
| public static final char NOT_DEFINED = '\0'; |
| |
| /** |
| * The constant for '}'. |
| */ |
| public static final char RIGHT_CURLY_BRACKET = '}'; |
| |
| /** |
| * The constant for ')'. |
| */ |
| public static final char RIGHT_PARENTHESIS = ')'; |
| |
| /** |
| * The constant for '''. |
| */ |
| public static final char SINGLE_QUOTE = '\''; |
| |
| /** |
| * The constant for ' '. |
| */ |
| public static final char SPACE = ' '; |
| |
| /** |
| * The constant for '_'. |
| */ |
| public static final char UNDERSCORE = '_'; |
| |
| /** |
| * Creates a new <code>AbstractExpression</code>. |
| * |
| * @param parent The parent of this expression |
| */ |
| protected AbstractExpression(AbstractExpression parent) { |
| this(parent, ExpressionTools.EMPTY_STRING); |
| } |
| |
| /** |
| * Creates a new <code>AbstractExpression</code>. |
| * |
| * @param parent The parent of this expression |
| * @param text The text to be stored in this expression, <code>null</code> cannot be passed |
| */ |
| protected AbstractExpression(AbstractExpression parent, String text) { |
| super(); |
| this.offset = -1; |
| this.text = text; |
| this.parent = parent; |
| } |
| |
| /** |
| * The given {@link ExpressionVisitor} needs to visit this class but it is defined by a third- |
| * party provider. This method will programmatically invoke the <b>visit</b> method defined on |
| * the visitor. The method signature should be: |
| * |
| * <div><code>{public|protected|private} void visit(ThirdPartyExpression expression)</code></div> |
| * <p> |
| * or |
| * |
| * <div><code>{public|protected|private} void visit(Expression expression)</code></div> |
| * <p> |
| * <b>Note:</b> The package protected visibility (default) should be used with care, if the code |
| * is running inside OSGi, then the method will not be accessible, even through reflection. |
| * |
| * @param visitor The {@link ExpressionVisitor} to visit this {@link Expression} programmatically |
| * @return <code>true</code> if the call was successfully executed; <code>false</code> otherwise |
| * @since 2.4 |
| */ |
| protected boolean acceptUnknownVisitor(ExpressionVisitor visitor) { |
| try { |
| try { |
| acceptUnknownVisitor(visitor, visitor.getClass(), getClass()); |
| } |
| catch (NoSuchMethodException e) { |
| // Try with Expression as the parameter type |
| acceptUnknownVisitor(visitor, visitor.getClass(), Expression.class); |
| } |
| return true; |
| } |
| catch (NoSuchMethodException e) { |
| // Ignore, just do nothing |
| return false; |
| } |
| catch (IllegalAccessException e) { |
| // Ignore, just do nothing |
| return false; |
| } |
| catch (InvocationTargetException e) { |
| Throwable cause = e.getCause(); |
| RuntimeException actual; |
| if (cause instanceof RuntimeException) { |
| actual = (RuntimeException) cause; |
| } |
| else { |
| actual = new RuntimeException(cause); |
| } |
| throw actual; |
| } |
| } |
| |
| /** |
| * The given {@link ExpressionVisitor} needs to visit this class but it is defined by a third- |
| * party provider. This method will programmatically invoke the <b>visit</b> method defined on |
| * the visitor. The method signature should be: |
| * |
| * <div><code>{public|protected|private} void visit(ThirdPartyExpression expression)</code></div> |
| * <br> |
| * or |
| * |
| * <div><code>{public|protected|private} void visit(Expression expression)</code></div> |
| * |
| * @param visitor The {@link ExpressionVisitor} to visit this {@link Expression} programmatically |
| * @param type The type found in the hierarchy of the given {@link ExpressionVisitor} that will |
| * be used to retrieve the visit method |
| * @param parameterType The parameter type of the visit method |
| * @see #acceptUnknownVisitor(ExpressionVisitor) |
| * @since 2.4 |
| */ |
| protected void acceptUnknownVisitor(ExpressionVisitor visitor, |
| Class<?> type, |
| Class<?> parameterType) throws NoSuchMethodException, |
| IllegalAccessException, |
| InvocationTargetException{ |
| |
| try { |
| Method visitMethod = type.getDeclaredMethod("visit", parameterType); |
| visitMethod.setAccessible(true); |
| visitMethod.invoke(visitor, this); |
| } |
| catch (NoSuchMethodException e) { |
| type = type.getSuperclass(); |
| if (type == Object.class) { |
| throw e; |
| } |
| else { |
| acceptUnknownVisitor(visitor, type, parameterType); |
| } |
| } |
| } |
| |
| /** |
| * Adds the children of this {@link AbstractExpression} to the given collection. |
| * |
| * @param children The collection used to store the children |
| */ |
| protected void addChildrenTo(Collection<Expression> children) { |
| } |
| |
| /** |
| * Adds the children of this {@link AbstractExpression} to the given list. |
| * |
| * @param children The list used to store the string representation of this {@link AbstractExpression} |
| */ |
| protected void addOrderedChildrenTo(List<Expression> children) { |
| } |
| |
| /** |
| * No factories were found to create an {@link Expression} with the content of {@link WordParser}, |
| * this method will retrieve the fallback {@link ExpressionFactory} defined in the given {@link |
| * JPQLQueryBNF BNF}. |
| * |
| * @param wordParser The text to parse based on the current position of the cursor |
| * @param word The word that was retrieved from the given text, which is the first word in the text |
| * @param queryBNF The {@link JPQLQueryBNF} used to determine how to parse from the current |
| * position of the cursor within the JPQL query |
| * @param expression The {@link Expression} that has just been parsed or <code>null</code> |
| * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should |
| * try to parse invalid or incomplete queries |
| * @return The {@link Expression} representing the given sub-query |
| */ |
| protected final AbstractExpression buildExpressionFromFallingBack(WordParser wordParser, |
| String word, |
| JPQLQueryBNF queryBNF, |
| AbstractExpression expression, |
| boolean tolerant) { |
| |
| ExpressionFactory factory = findFallBackExpressionFactory(queryBNF); |
| |
| if (factory == null) { |
| return null; |
| } |
| |
| // When parsing an invalid or incomplete query, it is possible two literals would be parsed |
| // but in some cases, a CollectionExpression should not be created and the parsing should |
| // actually stop here. Example: BETWEEN 10 20, when parsing 20, it should not be parsed as |
| // part of the lower bound expression |
| if (tolerant && |
| (factory.getId() == LiteralExpressionFactory.ID) && |
| shouldSkipLiteral(expression)) { |
| |
| return null; |
| } |
| |
| return factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant); |
| } |
| |
| /** |
| * Creates a new <code>null</code>-{@link Expression} parented with this one. |
| * |
| * @return A new <code>null</code> version of an {@link Expression} |
| */ |
| protected final AbstractExpression buildNullExpression() { |
| return new NullExpression(this); |
| } |
| |
| /** |
| * Creates a new {@link Expression} wrapping the given character value. |
| * |
| * @param value The character to wrap as a {@link Expression} |
| * @return The {@link Expression} representation of the given identifier where the owning |
| * {@link Expression} is this one |
| */ |
| protected final Expression buildStringExpression(char value) { |
| return buildStringExpression(String.valueOf(value)); |
| } |
| |
| /** |
| * Creates a new {@link Expression} wrapping the given string value. |
| * |
| * @param value The string to wrap as a <code>Expression</code> |
| * @return The {@link Expression} representation of the given identifier where the owning |
| * {@link Expression} is this one |
| */ |
| protected final Expression buildStringExpression(String value) { |
| return new DefaultStringExpression(this, value); |
| } |
| |
| /** |
| * Creates an {@link Expression} that contains a malformed expression. |
| * |
| * @param text The text causing the expression to be malformed |
| * @return A new {@link Expression} where {@link #toActualText()} returns the given text |
| */ |
| protected final AbstractExpression buildUnknownExpression(String text) { |
| return new UnknownExpression(this, text); |
| } |
| |
| /** |
| * Calculates the position of the given {@link Expression} by calculating the length of what is before. |
| * |
| * @param expression The {@link Expression} for which its position within the parsed tree needs |
| * to be determined |
| * @param length The current cursor position within the JPQL query while digging into the tree |
| * until the search reaches the expression |
| * @return The length of the string representation for what is coming before the given {@link Expression} |
| * @since 2.4 |
| */ |
| protected final int calculatePosition(Expression expression, int length) { |
| |
| Expression parent = expression.getParent(); |
| |
| // Reach the root |
| if (parent == null) { |
| return length; |
| } |
| |
| // Traverse the child expression until the expression |
| for (Expression childExpression : parent.orderedChildren()) { |
| |
| // Continue to calculate the position by going up the hierarchy |
| if (childExpression == expression) { |
| return calculatePosition(parent, length); |
| } |
| |
| length += childExpression.getLength(); |
| } |
| |
| // It should never reach this |
| throw new RuntimeException("The position of the Expression could not be calculated: " + expression); |
| } |
| |
| @Override |
| public final ListIterable<Expression> children() { |
| if (children == null) { |
| children = new LinkedList<>(); |
| addChildrenTo(children); |
| } |
| return new SnapshotCloneListIterable<>(children); |
| } |
| |
| /** |
| * Retrieve the {@link ExpressionFactory} from the given {@link JPQLQueryBNF} by following the |
| * path of fallback {@link JPQLQueryBNF JPQLQueryBNFs} and then returns the {@link ExpressionFactory} |
| * from the leaf {@link JPQLQueryBNF}. |
| * |
| * @param queryBNF The {@link JPQLQueryBNF} for which its associated fallback {@link ExpressionFactory} |
| * will be searched |
| * @return Either the fallback {@link ExpressionFactory} linked to the given {@link JPQLQueryBNF} |
| * or <code>null</code> if none was declared |
| */ |
| protected final ExpressionFactory findFallBackExpressionFactory(JPQLQueryBNF queryBNF) { |
| |
| String fallBackBNFId = queryBNF.getFallbackBNFId(); |
| |
| // No fall back BNF is defined, then nothing can be done |
| if (fallBackBNFId == null) { |
| return null; |
| } |
| |
| JPQLQueryBNF fallBackQueryBNF = getQueryBNF(fallBackBNFId); |
| |
| // Traverse the fall back BNF because it has its own fall back BNF |
| if (fallBackQueryBNF != queryBNF && |
| fallBackQueryBNF.getFallbackBNFId() != null) { |
| |
| return findFallBackExpressionFactory(fallBackQueryBNF); |
| } |
| |
| // Retrieve the factory associated with the fall back BNF |
| return getExpressionRegistry().getExpressionFactory(fallBackQueryBNF.getFallbackExpressionFactoryId()); |
| } |
| |
| @Override |
| public JPQLQueryBNF findQueryBNF(Expression expression) { |
| return getQueryBNF(); |
| } |
| |
| /** |
| * Retrieves the registered {@link ExpressionFactory} that was registered for the given unique |
| * identifier. |
| * |
| * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory} to retrieve |
| * @return The {@link ExpressionFactory} mapped with the given unique identifier |
| * @see ExpressionRegistry#getExpressionFactory(String) |
| */ |
| protected final ExpressionFactory getExpressionFactory(String expressionFactoryId) { |
| return getExpressionRegistry().getExpressionFactory(expressionFactoryId); |
| } |
| |
| /** |
| * Returns the registry containing the {@link JPQLQueryBNF JPQLQueryBNFs} and the {@link |
| * org.eclipse.persistence.jpa.jpql.parser.ExpressionFactory ExpressionFactories} that are used |
| * to properly parse a JPQL query. |
| * |
| * @return The registry containing the information related to the JPQL grammar |
| */ |
| protected final ExpressionRegistry getExpressionRegistry() { |
| return getGrammar().getExpressionRegistry(); |
| } |
| |
| @Override |
| public JPQLGrammar getGrammar() { |
| return getRoot().getGrammar(); |
| } |
| |
| /** |
| * Retrieves the JPA version in which the identifier was first introduced. |
| * |
| * @return The version in which the identifier was introduced |
| */ |
| public JPAVersion getIdentifierVersion(String identifier) { |
| return getRoot().getIdentifierVersion(identifier); |
| } |
| |
| /** |
| * Returns the version of the Java Persistence to support. |
| * |
| * @return The JPA version supported by the grammar |
| * @see JPQLGrammar |
| */ |
| protected JPAVersion getJPAVersion() { |
| return getRoot().getJPAVersion(); |
| } |
| |
| @Override |
| public final int getLength() { |
| return toActualText().length(); |
| } |
| |
| @Override |
| public final int getOffset() { |
| if (offset == -1) { |
| offset = calculatePosition(this, 0); |
| } |
| return offset; |
| } |
| |
| @Override |
| public final AbstractExpression getParent() { |
| return parent; |
| } |
| |
| /** |
| * Retrieves the BNF object that was registered for the given unique identifier. |
| * |
| * @param queryBNFID The unique identifier of the {@link JPQLQueryBNF} to retrieve |
| * @return The {@link JPQLQueryBNF} representing a section of the grammar |
| */ |
| public JPQLQueryBNF getQueryBNF(String queryBNFID) { |
| return getExpressionRegistry().getQueryBNF(queryBNFID); |
| } |
| |
| @Override |
| public final JPQLExpression getRoot() { |
| return (parent == null) ? (JPQLExpression) this : parent.getRoot(); |
| } |
| |
| /** |
| * Returns the encapsulated text of this {@link AbstractExpression}, which can be used in various |
| * ways, it can be a keyword, a literal, etc. |
| * |
| * @return Either the JPQL identifier for this {@link AbstractExpression}, the literal it |
| * encapsulates or an empty string |
| */ |
| protected String getText() { |
| return text; |
| } |
| |
| /** |
| * Determines whether the given {@link JPQLQueryBNF} handles aggregate expressions. |
| * |
| * @param queryBNF The {@link JPQLQueryBNF} used to determine if the parsing should handle |
| * aggregate expressions |
| * @return <code>true</code> if the given BNF handles aggregate expressions; <code>false</code> |
| * otherwise |
| */ |
| protected boolean handleAggregate(JPQLQueryBNF queryBNF) { |
| return queryBNF.handleAggregate(); |
| } |
| |
| /** |
| * Determines whether the given {@link JPQLQueryBNF} handles a collection of sub-expressions that |
| * are separated by commas. |
| * |
| * @param queryBNF The {@link JPQLQueryBNF} used to determine if the parsing should handle |
| * collection of sub-expressions |
| * @return <code>true</code> if the sub-expression to parse might have several sub-expressions |
| * separated by commas; <code>false</code> otherwise |
| */ |
| protected boolean handleCollection(JPQLQueryBNF queryBNF) { |
| return queryBNF.handleCollection(); |
| } |
| |
| @Override |
| public boolean isAncestor(Expression expression) { |
| |
| if (expression == this) { |
| return true; |
| } |
| |
| if (expression == null) { |
| return false; |
| } |
| |
| return isAncestor(expression.getParent()); |
| } |
| |
| /** |
| * Determines if the given word is a JPQL identifier. The check is case insensitive. |
| * |
| * @param word The word to test if it is a JPQL identifier |
| * @return <code>true</code> if the word is an identifier, <code>false</code> otherwise |
| * @see ExpressionRegistry#isIdentifier(String) |
| */ |
| protected final boolean isIdentifier(String word) { |
| return getExpressionRegistry().isIdentifier(word); |
| } |
| |
| /** |
| * Determines whether this expression is a <code>null</code> {@link Expression} or any other subclass. |
| * |
| * @return <code>false</code> by default |
| */ |
| protected boolean isNull() { |
| return false; |
| } |
| |
| /** |
| * Determines whether the parsing is complete based on what is left in the given text. The text |
| * is never empty. |
| * |
| * @param wordParser The text to parse based on the current position of the cursor |
| * @param word The word that was retrieved from the given text, which is the first word in the text |
| * @param expression The {@link Expression} that has already been parsed |
| * @return <code>true</code> if the text no longer can't be parsed by the current expression; |
| * <code>false</code> if more can be parsed |
| */ |
| protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) { |
| // TODO: MAYBE MOVE THIS TO THE JPQL GRAMMAR SO GENERIC JPA DOES |
| // NOT HAVE KNOWLEDGE OF ECLIPSELINK SPECIFIC FUNCTIONS |
| return word.equalsIgnoreCase(FROM) || |
| word.equalsIgnoreCase(WHERE) || |
| word.equalsIgnoreCase(HAVING) || |
| wordParser.startsWithIdentifier(GROUP_BY) || |
| wordParser.startsWithIdentifier(ORDER_BY) || |
| wordParser.startsWithIdentifier(AS_OF) || |
| wordParser.startsWithIdentifier(START_WITH) || |
| wordParser.startsWithIdentifier(CONNECT_BY) || |
| wordParser.startsWithIdentifier(ORDER_SIBLINGS_BY) || |
| word.equalsIgnoreCase(UNION) || |
| word.equalsIgnoreCase(INTERSECT) || |
| word.equalsIgnoreCase(EXCEPT); |
| } |
| |
| /** |
| * Determines if the parser is in tolerant mode or is in fast mode. When the tolerant is turned |
| * on, it means the parser will attempt to parse incomplete or invalid queries. |
| * |
| * @return <code>true</code> if the parsing system should parse invalid or incomplete queries; |
| * <code>false</code> when the query is well-formed and valid |
| */ |
| protected boolean isTolerant() { |
| return getRoot().isTolerant(); |
| } |
| |
| /** |
| * Determines whether this expression is an unknown {@link Expression} or any other subclass. |
| * |
| * @return <code>false</code> by default |
| */ |
| protected boolean isUnknown() { |
| return false; |
| } |
| |
| /** |
| * Determines whether this {@link AbstractExpression} is virtual, meaning it's not part of the |
| * query but is required for proper navigability. |
| * |
| * @return <code>true</code> if this {@link AbstractExpression} was virtually created to fully |
| * qualify path expression; <code>false</code> if it was parsed |
| */ |
| protected boolean isVirtual() { |
| return false; |
| } |
| |
| @Override |
| public final ListIterable<Expression> orderedChildren() { |
| if (orderedChildren == null) { |
| orderedChildren = new LinkedList<>(); |
| addOrderedChildrenTo(orderedChildren); |
| } |
| return new SnapshotCloneListIterable<>(orderedChildren); |
| } |
| |
| /** |
| * Parses the query 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 tolerant Determines whether the parsing system should be tolerant, meaning if it should |
| * try to parse invalid or incomplete queries |
| */ |
| protected abstract void parse(WordParser wordParser, boolean tolerant); |
| |
| /** |
| * Parses the given text by using the specified BNF. |
| * |
| * @param wordParser The text to parse based on the current position of the cursor |
| * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that is used to determine |
| * how to parse the text at the current cursor position within the JPQL query |
| * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should |
| * try to parse invalid or incomplete queries |
| * @return The {@link Expression} representing the given sub-query |
| */ |
| @SuppressWarnings("null") |
| protected AbstractExpression parse(WordParser wordParser, String queryBNFId, boolean tolerant) { |
| |
| // Quick check so we don't create some objects for no reasons |
| if (tolerant && wordParser.isTail()) { |
| return null; |
| } |
| |
| // |
| // NOTE: This method could look better but for performance reason, it is a single method, |
| // which reduces the number of objects created and methods called. |
| // |
| |
| JPQLQueryBNF queryBNF = getQueryBNF(queryBNFId); |
| |
| int count = 0; |
| boolean beginning = !tolerant; |
| char character = wordParser.character(); |
| |
| AbstractExpression child; |
| AbstractExpression expression = null; |
| |
| Info rootInfo = null; |
| Info currentInfo = null; |
| |
| // Parse the string until the position of the cursor is at |
| // the end of the string or until the parsing is complete |
| while (!wordParser.isTail()) { |
| |
| child = null; |
| |
| // |
| // Step 1 |
| // |
| // Right away create a SubExpression and parse the encapsulated expression |
| if (character == LEFT_PARENTHESIS) { |
| |
| // If the JPQLQueryBNF handles parsing the sub-expression, then delegate the parsing |
| // to its fallback ExpressionFactory |
| if (queryBNF.handleSubExpression()) { |
| expression = buildExpressionFromFallingBack( |
| wordParser, |
| ExpressionTools.EMPTY_STRING, |
| queryBNF, |
| expression, |
| tolerant |
| ); |
| } |
| else { |
| expression = new SubExpression(this, queryBNF); |
| expression.parse(wordParser, tolerant); |
| |
| // Make sure this is not the root and if the parent handles parsing the sub- |
| // expression, then the Expression needs to be returned without further parsing |
| if ((parent != null) && parent.getQueryBNF().handleSubExpression()) { |
| return expression; |
| } |
| } |
| |
| // Something has been parsed, which means it's not the beginning anymore |
| beginning = false; |
| |
| // Continue to the next character/word |
| count = wordParser.skipLeadingWhitespace(); |
| character = wordParser.character(); |
| |
| // Store the SubExpression |
| currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo)); |
| currentInfo.expression = expression; |
| currentInfo.space = count > 0; |
| } |
| |
| // Retrieve the next word, including any arithmetic symbols |
| String word = wordParser.word(); |
| |
| // A word was parsed, attempt to parse it using first the factory, then the fallback factory |
| if (word.length() > 0) { |
| |
| // Nothing more to parse |
| if (!tolerant && !beginning && isParsingComplete(wordParser, word, expression) || |
| tolerant && isParsingComplete(wordParser, word, expression)) { |
| |
| break; |
| } |
| |
| // |
| // Step 2 |
| // |
| // Parse using the ExpressionFactory that is mapped with a JPQL identifier (word) |
| if (shouldParseWithFactoryFirst() && |
| (wordParser.getWordType() == WordType.WORD)) { |
| |
| ExpressionFactory factory = queryBNF.getExpressionFactory(word); |
| |
| if (factory != null) { |
| child = factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant); |
| |
| if (child != null) { |
| |
| // The new expression is a child of the previous expression, |
| // remove it from the collection since it's already parented |
| if ((expression != null) && child.isAncestor(expression)) { |
| if (currentInfo == rootInfo) { |
| rootInfo = null; |
| currentInfo = null; |
| } |
| else if (currentInfo != null) { |
| currentInfo = currentInfo.previous; |
| } |
| } |
| |
| // Something has been parsed, which means it's not the beginning anymore |
| beginning = false; |
| |
| // Continue with the next character/word |
| count = wordParser.skipLeadingWhitespace(); |
| character = wordParser.character(); |
| |
| // The new expression becomes the previous expression |
| expression = child; |
| } |
| } |
| } |
| |
| // |
| // Step 3 |
| // |
| // No factories could be used, use the fall back ExpressionFactory |
| if (child == null) { |
| child = buildExpressionFromFallingBack(wordParser, word, queryBNF, expression, tolerant); |
| |
| if (child != null) { |
| |
| // The new expression is a child of the previous expression, |
| // remove it from the collection since it's already parented |
| if ((expression != null) && child.isAncestor(expression)) { |
| if (currentInfo == rootInfo) { |
| rootInfo = null; |
| currentInfo = null; |
| } |
| else if (currentInfo != null) { |
| currentInfo = currentInfo.previous; |
| } |
| } |
| |
| // Something has been parsed, which means it's not the beginning anymore |
| beginning = false; |
| |
| // Continue with the next character/word |
| count = wordParser.skipLeadingWhitespace(); |
| character = wordParser.character(); |
| |
| // The new expression becomes the previous expression |
| expression = child; |
| } |
| } |
| |
| // |
| // Step 4 |
| // |
| // If nothing was parsed, then attempt to parse the fragment by retrieving the factory |
| // directory from the JPQL grammar and not from the one registered with the current BNF |
| if (tolerant && (child == null)) { |
| |
| ExpressionRegistry expressionRegistry = getExpressionRegistry(); |
| |
| if (expressionRegistry.getIdentifierRole(word) != IdentifierRole.AGGREGATE) { |
| |
| ExpressionFactory factory = expressionRegistry.expressionFactoryForIdentifier(word); |
| |
| if (factory != null) { |
| child = factory.buildExpression(this, wordParser, word, queryBNF, expression, tolerant); |
| |
| if (child != null) { |
| child = new BadExpression(this, child); |
| |
| // The new expression is a child of the previous expression, |
| // remove it from the collection since it's already parented |
| if ((expression != null) && child.isAncestor(expression)) { |
| if (currentInfo == rootInfo) { |
| rootInfo = null; |
| currentInfo = null; |
| } |
| else if (currentInfo != null) { |
| currentInfo = currentInfo.previous; |
| } |
| } |
| |
| // Something has been parsed, which means it's not the beginning anymore |
| beginning = false; |
| |
| // Continue with the next character/word |
| count = wordParser.skipLeadingWhitespace(); |
| character = wordParser.character(); |
| |
| // The new expression becomes the previous expression |
| expression = child; |
| } |
| } |
| } |
| } |
| } |
| |
| // Nothing could be parsed, break here so the parent can continue parsing. |
| // Example: AVG() and we're parsing what's inside the parenthesis (nothing). |
| // But if it's (,), then we have to create a collection of "null" expressions |
| // separated by a comma |
| if ((child == null) && (character != COMMA)) { |
| break; |
| } |
| |
| // Store the child but skip a very special case, which happens when parsing |
| // two subqueries in a collection expression. Example: (SELECT ... ), (SELECT ... ) |
| if ((expression == null) || (child != null)) { |
| currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo)); |
| currentInfo.expression = child; |
| currentInfo.space = count > 1; |
| } |
| |
| // Nothing else to parse |
| if (wordParser.isTail()) { |
| break; |
| } |
| |
| // ',' |
| if (character == COMMA) { |
| |
| // The current expression does not handle collection, then stop the |
| // parsing here so the parent can continue to parse |
| if (!handleCollection(queryBNF) || tolerant && isParsingComplete(wordParser, word, expression)) { |
| break; |
| } |
| |
| // Skip the comma |
| wordParser.moveForward(1); |
| currentInfo.comma = true; |
| |
| // Remove leading whitespace |
| count = wordParser.skipLeadingWhitespace(); |
| currentInfo.space = count > 0; |
| |
| character = wordParser.character(); |
| expression = null; |
| |
| // Special case: ((), (), ()) |
| if (character == LEFT_PARENTHESIS) { |
| continue; |
| } |
| |
| // No more text, the query ends with a comma |
| word = wordParser.word(); |
| boolean stopParsing = tolerant && (word.length() == 0 || isParsingComplete(wordParser, word, null)); |
| |
| if (wordParser.isTail() || stopParsing) { |
| |
| // Make sure the space is not re-added at the end of the query |
| count = 0; |
| |
| // Add a null Expression since the expression ends with a comma |
| currentInfo = (rootInfo == null) ? (rootInfo = new Info()) : (currentInfo.next = new Info(currentInfo)); |
| |
| // Nothing else to parse |
| if (stopParsing) { |
| break; |
| } |
| } |
| |
| // Nothing more to parse |
| if (character == RIGHT_PARENTHESIS) { |
| break; |
| } |
| } |
| else { |
| |
| // Continue parsing the collection expression |
| if (character != RIGHT_PARENTHESIS && |
| handleAggregate(queryBNF)) { |
| |
| currentInfo.space = count > 0; |
| } |
| // Nothing more to parse |
| else { |
| break; |
| } |
| } |
| } |
| |
| if (count > 0) { |
| currentInfo.space = currentInfo.comma; |
| if (!currentInfo.comma) { |
| wordParser.moveBackward(count); |
| } |
| } |
| |
| // Nothing was parsed |
| if (currentInfo == null) { |
| return null; |
| } |
| |
| // Simply return the single expression |
| if (currentInfo == rootInfo && |
| !currentInfo.comma && |
| !currentInfo.space) { |
| |
| return currentInfo.expression; |
| } |
| |
| // Return a collection of expressions |
| return new CollectionExpression( |
| this, |
| rootInfo.buildChildren(), |
| rootInfo.buildCommas(), |
| rootInfo.buildSpaces() |
| ); |
| } |
| |
| /** |
| * Right away parses the text by retrieving the {@link ExpressionFactory} for the first word that |
| * is extracted from {@link WordParser} at the current location. |
| * |
| * @param wordParser The text to parse based on the current position of the cursor |
| * @param queryBNFId The unique identifier of the {@link JPQLQueryBNF} that is used to determine |
| * how to parse the text at the current cursor position within the JPQL query |
| * @param tolerant Determines whether the parsing system should be tolerant, meaning if it should |
| * try to parse invalid or incomplete queries |
| * @return The {@link Expression} representing the given sub-query |
| */ |
| protected AbstractExpression parseUsingExpressionFactory(WordParser wordParser, |
| String queryBNFId, |
| boolean tolerant) { |
| |
| String word = wordParser.word(); |
| JPQLQueryBNF queryBNF = getQueryBNF(queryBNFId); |
| ExpressionFactory factory = queryBNF.getExpressionFactory(word); |
| |
| if (factory == null) { |
| return null; |
| } |
| |
| return factory.buildExpression(this, wordParser, word, queryBNF, null, tolerant); |
| } |
| |
| @Override |
| public void populatePosition(QueryPosition queryPosition, int position) { |
| |
| queryPosition.addPosition(this, position); |
| |
| // The position is at the beginning of this expression |
| if (position == 0) { |
| queryPosition.setExpression(this); |
| } |
| else { |
| // Traverse the children in order to find where the cursor is located |
| for (Expression expression : orderedChildren()) { |
| |
| String expressionText = expression.toParsedText(); |
| |
| // The position is in the Expression, traverse it |
| if (position <= expressionText.length()) { |
| expression.populatePosition(queryPosition, position); |
| return; |
| } |
| |
| // Continue with the next child by adjusting the position |
| position -= expressionText.length(); |
| } |
| |
| throw new IllegalStateException("A problem was encountered while calculating the position."); |
| } |
| } |
| |
| /** |
| * Rebuilds the actual parsed text if it has been cached. |
| */ |
| protected final void rebuildActualText() { |
| if (actualText != null) { |
| toActualText(); |
| } |
| } |
| |
| /** |
| * Rebuilds the parsed parsed text if it has been cached. |
| */ |
| protected final void rebuildParsedText() { |
| if (parsedText != null) { |
| toParsedText(); |
| } |
| } |
| |
| /** |
| * Re-parents this {@link Expression} to be a child of the given {@link Expression}. |
| * |
| * @param parent The new parent of this object |
| */ |
| protected final void setParent(AbstractExpression parent) { |
| this.parent = parent; |
| } |
| |
| /** |
| * Sets the text of this {@link Expression}. |
| * |
| * @param text The immutable text wrapped by this {@link Expression}, which cannot be <code>null</code> |
| */ |
| protected final void setText(String text) { |
| this.text = text; |
| } |
| |
| /** |
| * Determines whether the parsing of the query should be performed using the {@link ExpressionFactory |
| * factories} first or it should automatically fallback to the fallback factory. |
| * |
| * @return <code>true</code> is returned by default so the factories are used before falling back |
| */ |
| protected boolean shouldParseWithFactoryFirst() { |
| return true; |
| } |
| |
| /** |
| * When parsing an invalid or incomplete query, it is possible two literals would be parsed but |
| * in some cases, a CollectionExpression should not be created and the parsing should actually |
| * stop here. Example: BETWEEN 10 20, when parsing 20, it should not be parsed as part of the |
| * lower bound expression. |
| * |
| * @param expression The {@link Expression} that has just been parsed or <code>null</code> |
| * @return <code>true</code> |
| */ |
| protected boolean shouldSkipLiteral(AbstractExpression expression) { |
| return (expression != null); |
| } |
| |
| @Override |
| public String toActualText() { |
| if (actualText == null) { |
| StringBuilder writer = new StringBuilder(); |
| toParsedText(writer, true); |
| actualText = writer.toString(); |
| } |
| return actualText; |
| } |
| |
| @Override |
| public String toParsedText() { |
| if (parsedText == null) { |
| StringBuilder writer = new StringBuilder(); |
| toParsedText(writer, false); |
| parsedText = writer.toString(); |
| } |
| return parsedText; |
| } |
| |
| /** |
| * Generates a string representation of this {@link Expression}, including its children, |
| * if it has any. |
| * |
| * @param writer The buffer used to append this {@link Expression}'s string representation |
| * @param actual Determines whether the string representation should represent what was parsed, |
| * i.e. include any "virtual" whitespace (such as ending whitespace) and the actual case of the |
| * JPQL identifiers |
| */ |
| protected abstract void toParsedText(StringBuilder writer, boolean actual); |
| |
| @Override |
| public final String toString() { |
| // toString() should only be called during debugging, thus the cached parsed text |
| // should always be recreated in order to reflect the current state while debugging |
| parsedText = null; |
| return toParsedText(); |
| } |
| |
| /** |
| * Rather than creating three lists when performing a single parsing operation ({@link |
| * AbstractExpression#parse(WordParser, String, boolean) parse(WordParser, String, boolean)}), |
| * this class will act as a simple chained list and postponing the creation of those three lists |
| * only when needed (which is when the parsed expression needs to be a collection of expressions). |
| */ |
| private static class Info { |
| |
| /** |
| * Flag indicating a comma follows the parsed {@link #expression}. |
| */ |
| boolean comma; |
| |
| /** |
| * The parsed {@link Expression}, which is the first one that was parsed if {@link #next} is |
| * <code>null</code>, meaning this <code>Info</code> object is the root of the chained list. |
| */ |
| AbstractExpression expression; |
| |
| /** |
| * When more than one expression needs to be parsed, it will be chained. |
| */ |
| Info next; |
| |
| /** |
| * The parent within the chain of this one. |
| */ |
| Info previous; |
| |
| /** |
| * Flag indicating a whitespace follows the parsed {@link #expression}. |
| */ |
| boolean space; |
| |
| /** |
| * Creates a new <code>Info</code>. |
| */ |
| Info() { |
| super(); |
| } |
| |
| /** |
| * Creates a new <code>Info</code>. |
| * |
| * @param previous The parent within the chain of this one |
| */ |
| Info(Info previous) { |
| super(); |
| this.previous = previous; |
| } |
| |
| private void addChild(ArrayList<AbstractExpression> children) { |
| children.add(expression); |
| if (next != null) { |
| next.addChild(children); |
| } |
| } |
| |
| private void addComma(ArrayList<Boolean> children) { |
| children.add(comma); |
| if (next != null) { |
| next.addComma(children); |
| } |
| } |
| |
| private void addSpace(ArrayList<Boolean> children) { |
| children.add(space); |
| if (next != null) { |
| next.addSpace(children); |
| } |
| } |
| |
| List<AbstractExpression> buildChildren() { |
| ArrayList<AbstractExpression> children = new ArrayList<>(); |
| addChild(children); |
| return children; |
| } |
| |
| List<Boolean> buildCommas() { |
| ArrayList<Boolean> children = new ArrayList<>(); |
| addComma(children); |
| return children; |
| } |
| |
| List<Boolean> buildSpaces() { |
| ArrayList<Boolean> children = new ArrayList<>(); |
| addSpace(children); |
| return children; |
| } |
| } |
| } |