All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.jpa.jpql.parser.JPQLExpression Maven / Gradle / Ivy

There is a newer version: 5.0.0-B03
Show newest version
/*
 * 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.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * A JPQLExpression is the root of the parsed tree representation of a JPQL query. The
 * query is parsed based on what was registered in the {@link JPQLGrammar}'s {@link ExpressionRegistry}.
 * 

* A JPQL statement may be either a SELECT statement, an UPDATE statement, or a * DELETE FROM statement. * *

BNF: QL_statement ::= {@link SelectStatement select_statement} | * {@link UpdateStatement update_statement} | * {@link DeleteStatement delete_statement}
*

* It is possible to parse a portion of a JPQL query. The ID of the {@link JPQLQueryBNF} is used to * parse that portion and {@link #getQueryStatement()} then returns only the parsed tree representation * of that JPQL fragment. * * @version 2.5 * @since 2.3 * @author Pascal Filion */ @SuppressWarnings("nls") public final class JPQLExpression extends AbstractExpression { /** * The JPQL grammar that defines how to parse a JPQL query. */ private JPQLGrammar jpqlGrammar; /** * By default, this is {@link JPQLStatementBNF#ID} but it can be any other unique identifier of * a {@link JPQLQueryBNF} when a portion of a JPQL query needs to be parsed. */ private String queryBNFId; /** * The tree representation of the query. */ private AbstractExpression queryStatement; /** * Determines if the parsing system should be tolerant, meaning if it should try to parse invalid * or incomplete queries. */ private boolean tolerant; /** * If the expression could not be fully parsed, meaning some unknown text is trailing the query, * this will contain it. */ private AbstractExpression unknownEndingStatement; /** * Creates a new JPQLExpression, which is the root of the JPQL parsed tree. * * @param query The string representation of the JPQL query to parse * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query */ public JPQLExpression(CharSequence query, JPQLGrammar jpqlGrammar) { this(query, jpqlGrammar, false); } /** * Creates a new JPQLExpression, which is the root of the JPQL parsed tree. * * @param query The string representation of the JPQL query to parse * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try * to parse invalid or incomplete queries */ public JPQLExpression(CharSequence query, JPQLGrammar jpqlGrammar, boolean tolerant) { this(query, jpqlGrammar, JPQLStatementBNF.ID, tolerant); } /** * Creates a new JPQLExpression that will parse the given fragment of a JPQL query. * This means {@link #getQueryStatement()} will not return a query statement (select, delete or * update) but only the parsed tree representation of the fragment if the query BNF can pare it. * If the fragment of the JPQL query could not be parsed using the given {@link JPQLQueryBNF}, * then {@link #getUnknownEndingStatement()} will contain the non-parsable fragment. * * @param jpqlFragment A fragment of a JPQL query, which is a portion of a complete JPQL query * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query * @param queryBNFId The unique identifier of the {@link org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF JPQLQueryBNF} * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try * to parse invalid or incomplete queries * @since 2.4 */ public JPQLExpression(CharSequence jpqlFragment, JPQLGrammar jpqlGrammar, String queryBNFId, boolean tolerant) { this(jpqlGrammar, queryBNFId, tolerant); parse(new WordParser(jpqlFragment), tolerant); } /** * Creates a new JPQLExpression, which is the root of the JPQL parsed tree. * * @param jpqlGrammar The JPQL grammar that defines how to parse a JPQL query * @param tolerant Determines if the parsing system should be tolerant, meaning if it should try * to parse invalid or incomplete queries */ private JPQLExpression(JPQLGrammar jpqlGrammar, String queryBNFId, boolean tolerant) { super(null); this.queryBNFId = queryBNFId; this.tolerant = tolerant; this.jpqlGrammar = jpqlGrammar; } @Override public void accept(ExpressionVisitor visitor) { visitor.visit(this); } @Override public void acceptChildren(ExpressionVisitor visitor) { getQueryStatement().accept(visitor); getUnknownEndingStatement().accept(visitor); } @Override protected void addChildrenTo(Collection children) { children.add(getQueryStatement()); children.add(getUnknownEndingStatement()); } @Override protected void addOrderedChildrenTo(List children) { if (queryStatement != null) { children.add(queryStatement); } if (unknownEndingStatement != null) { children.add(unknownEndingStatement); } } /** * Creates a map of the position of the cursor within each {@link Expression} of the parsed tree. * * @param actualQuery The actual query is a string representation of the query that may contain * extra whitespace * @param position The position of the cursor in the actual query, which is used to retrieve the * deepest {@link Expression}. The position will be adjusted to fit into the beautified version * of the query * @return A new {@link QueryPosition} */ public QueryPosition buildPosition(String actualQuery, int position) { // Adjust the position by not counting extra whitespace position = ExpressionTools.repositionCursor(actualQuery, position, toActualText()); QueryPosition queryPosition = new QueryPosition(position); populatePosition(queryPosition, position); return queryPosition; } /** * Returns the deepest {@link Expression} for the given position. * * @param actualQuery The actual query is the text version of the query that may contain extra * whitespace and different formatting than the trim down version generated by the parsed tree * @param position The position in the actual query used to retrieve the {@link Expression} * @return The {@link Expression} located at the given position in the given query */ public Expression getExpression(String actualQuery, int position) { QueryPosition queryPosition = buildPosition(actualQuery, position); return queryPosition.getExpression(); } @Override public JPQLGrammar getGrammar() { return jpqlGrammar; } @Override public JPAVersion getJPAVersion() { return jpqlGrammar.getJPAVersion(); } @Override public JPQLQueryBNF getQueryBNF() { return getQueryBNF(queryBNFId); } /** * Returns the {@link Expression} representing the query, which is either a SELECT, a * DELETE or an UPDATE clause. * * @return The expression representing the Java Persistence query */ public Expression getQueryStatement() { if (queryStatement == null) { queryStatement = buildNullExpression(); } return queryStatement; } /** * Returns the {@link Expression} that may contain a portion of the query that could not be * parsed, this happens when the query is either incomplete or malformed. * * @return The expression used when the ending of the query is unknown or malformed */ public Expression getUnknownEndingStatement() { if (unknownEndingStatement == null) { unknownEndingStatement = buildNullExpression(); } return unknownEndingStatement; } /** * Determines whether a query was parsed. The query may be incomplete but it started with one of * the three clauses (SELECT, DELETE FROM, or UPDATE). * * @return true the query was parsed; false otherwise */ public boolean hasQueryStatement() { return queryStatement != null && !queryStatement.isNull(); } /** * Determines whether the query that got parsed had some malformed or unknown information. * * @return true if the query could not be parsed correctly * because it is either incomplete or malformed */ public boolean hasUnknownEndingStatement() { return unknownEndingStatement != null && !unknownEndingStatement.isNull(); } @Override protected boolean isTolerant() { return tolerant; } @Override protected void parse(WordParser wordParser, boolean tolerant) { // Skip leading whitespace wordParser.skipLeadingWhitespace(); // Parse the query, which can be invalid/incomplete or complete and valid // Make sure to use this statement if it's a JPQL fragment as well if (tolerant || (queryBNFId != JPQLStatementBNF.ID)) { // If the query BNF is not the "root" BNF, then we need to parse // it with a broader check when parsing if (queryBNFId == JPQLStatementBNF.ID) { queryStatement = parseUsingExpressionFactory(wordParser, queryBNFId, tolerant); } else { queryStatement = parse(wordParser, queryBNFId, tolerant); } int count = wordParser.skipLeadingWhitespace(); // The JPQL query is invalid or incomplete, the remaining will be added // to the unknown ending statement if ((queryStatement == null) || !wordParser.isTail()) { wordParser.moveBackward(count); unknownEndingStatement = buildUnknownExpression(wordParser.substring()); } // The JPQL query has some ending whitespace, keep one (used by content assist) else if (!wordParser.isTail() || (tolerant && (count > 0))) { unknownEndingStatement = buildUnknownExpression(" "); } // The JPQL query or fragment is invalid else if (queryStatement.isUnknown()) { unknownEndingStatement = buildUnknownExpression(queryStatement.toParsedText()); queryStatement = null; } } // Quickly parse the valid query else { switch (wordParser.character()) { case 'd': case 'D': queryStatement = new DeleteStatement(this); break; case 'u': case 'U': queryStatement = new UpdateStatement(this); break; case 's': case 'S': queryStatement = new SelectStatement(this); break; } if (queryStatement != null) { queryStatement.parse(wordParser, tolerant); } else { queryStatement = parse(wordParser, queryBNFId, tolerant); } } } @Override protected void toParsedText(StringBuilder writer, boolean actual) { if (queryStatement != null) { queryStatement.toParsedText(writer, actual); } if (unknownEndingStatement != null) { unknownEndingStatement.toParsedText(writer, actual); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy