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

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

There is a newer version: 5.0.0-B03
Show newest version
/*
 * Copyright (c) 2006, 2024 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
//
//     09/02/2019-3.0 Alexandre Jacob
//        - 527415: Fix code when locale is tr, az or lt
package org.eclipse.persistence.jpa.jpql.parser;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.persistence.jpa.jpql.utility.filter.Filter;
import org.eclipse.persistence.jpa.jpql.utility.filter.NullFilter;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ArrayIterable;

/**
 * 

This defines a single Backus-Naur Form (BNF) of the JPQL grammar. The Java Persistence functional * specifications are:

* * *

Provisional API: This interface is part of an interim API that is still under development and * expected to change significantly before reaching stability. It is available at this early stage * to solicit feedback from pioneering adopters on the understanding that any code that uses this * API will almost certainly be broken (repeatedly) as the API evolves.

* * @version 2.5.1 * @since 2.3 * @author Pascal Filion */ @SuppressWarnings("nls") public abstract class JPQLQueryBNF { /** * Caches the {@link ExpressionFactory} mapped by their the JPQL identifier registered with those * {@link ExpressionFactory}. */ private Map cachedExpressionFactories; /** * Caches the collection of unique identifiers of the {@link ExpressionFactory} registered with * this BNF rule. */ private String[] cachedExpressionFactoryIds; /** * The list of JPQL identifiers that are known by this BNF rule and its children, they are * retrieved by scanning the list of {@link ExpressionFactory} registered with this BNF rule * and its children. */ private String[] cachedIdentifiers; /** * Caches the children of this BNF rule (which actually includes this one as well). */ private JPQLQueryBNF[] childQueryBNFs; /** * The children BNF of this one. */ private List children; /** * Determines whether this BNF has child BNFs registered only to properly parse a query or if the * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF}, * it registers a series of children BNFs but they shouldn't be used to determine if they are * part of that BNF since the comparator identifiers are. */ private boolean compound; /** * The list of unique identifiers for the {@link ExpressionFactory} that are registered with this BNF rule. */ private List expressionFactoryIds; /** * The {@link ExpressionRegistry} with which this {@link JPQLQueryBNF} was registered. */ private ExpressionRegistry expressionRegistry; /** * The unique identifier of the {@link JPQLQueryBNF} to use in the last resort. */ private String fallbackBNFId; /** * The unique identifier of the {@link ExpressionFactory} to use when no other factories can be * used automatically. */ private String fallbackExpressionFactoryId; /** * Caches the property since any BNF rule is static. */ private Boolean handleAggregate; /** * Caches the property since any BNF rule is static. */ private Boolean handleCollection; /** * Determines whether this BNF handles parsing a nested array or not. */ private boolean handleNestedArray; /** * This flag can be used to determine if this BNF handles parsing a sub-expression, i.e. an * expression encapsulated by parenthesis. See {@link #setHandleSubExpression(boolean)} for more * details. */ private boolean handleSubExpression; /** * The unique identifier of this BNF rule. */ private String id; /** * Caches the children of this BNF rule (which actually includes this one as well) but do not * includes BNF rules that are used for compounding a rule. */ private JPQLQueryBNF[] nonCompoundChildren; /** * Only keep one instance of the {@link Filter} used when initializing {@link #nonCompoundChildren} JPQLQueryBNF. * This should help with performance since the filter won't be created for each JPQLQueryBNF. */ private static final Filter nonCompoundFilter = buildNonCompoundFilter(); /** * Creates a new JPQLQueryBNF. * * @param id The unique identifier of this BNF rule * @exception NullPointerException The given unique identifier cannot be null */ @SuppressWarnings("this-escape") protected JPQLQueryBNF(String id) { super(); initialize(id); } private static Filter buildNonCompoundFilter() { return new Filter<>() { @Override public boolean accept(JPQLQueryBNF queryBNF) { return !queryBNF.isCompound(); } }; } /** * Adds to the given set the child BNF rules and requests them to add their children as long as * they have not been traversed already. * * @param queryBNFs The set to add the child BNF rules * @param filter The {@link Filter} determines if the children of a given BNF should be added */ private void addChildren(Set queryBNFs, Filter filter) { // null children means no child JPQLQueryBNF was registered if (children != null) { for (String id : children) { JPQLQueryBNF queryBNF = expressionRegistry.getQueryBNF(id); if (queryBNFs.add(queryBNF) && filter.accept(queryBNF)) { queryBNF.addChildren(queryBNFs, filter); } } } } private JPQLQueryBNF[] buildChildren(Filter filter) { Set queryBNFs = new HashSet<>(); queryBNFs.add(this); addChildren(queryBNFs, filter); JPQLQueryBNF[] children = new JPQLQueryBNF[queryBNFs.size()]; queryBNFs.toArray(children); return children; } private void calculateExpressionFactories() { synchronized (this) { if (cachedExpressionFactories == null) { Map factories = new HashMap<>(); // Caches the ExpressionFactory mapped by all the JPQL identifiers that // are registered for that ExpressionFactory for (String expressionFactoryId : getExpressionFactoryIdsImp()) { ExpressionFactory expressionFactory = expressionRegistry.getExpressionFactory(expressionFactoryId); for (String identifier : expressionFactory.identifiers()) { factories.put(identifier, expressionFactory); } } cachedIdentifiers = new String[factories.size()]; factories.keySet().toArray(cachedIdentifiers); cachedExpressionFactories = factories; } } } private void calculateExpressionFactoryIds(Set queryBNFs, Set factoryIds) { if (expressionFactoryIds != null) { factoryIds.addAll(expressionFactoryIds); } for (JPQLQueryBNF queryBNF : getChildren()) { if ((queryBNF != this) && (queryBNFs.add(queryBNF))) { queryBNF.calculateExpressionFactoryIds(queryBNFs, factoryIds); } } } private boolean calculateHandleAggregate(Set queryBNFs) { if (handleAggregate != null) { return handleAggregate; } for (JPQLQueryBNF queryBNF : getChildren()) { if ((queryBNF != this) && queryBNFs.add(queryBNF)) { boolean result = queryBNF.calculateHandleAggregate(queryBNFs); if (result) { return true; } } } return false; } private boolean calculateHandleCollection(Set queryBNFs) { if (handleCollection != null) { return handleCollection; } for (JPQLQueryBNF queryBNF : getChildren()) { if ((queryBNF != this) && queryBNFs.add(queryBNF)) { boolean result = queryBNF.calculateHandleCollection(queryBNFs); if (result) { return true; } } } return false; } /** * Returns the set of all the query BNFs that are part of this BNF. The set always include this * BNF as well. * * @return The children BNFs describing this BNF rule */ public Iterable children() { return new ArrayIterable<>(getChildren()); } private JPQLQueryBNF[] getChildren() { // No need to synchronize if the list of children was calculated if (childQueryBNFs != null) { return childQueryBNFs; } // Synchronize to make sure only one thread populates the list of children synchronized (this) { if (childQueryBNFs == null) { childQueryBNFs = buildChildren(NullFilter.instance()); } } return childQueryBNFs; } /** * Retrieves the {@link ExpressionFactory} that is associated with the given identifier, if the * given string is indeed a JPQL identifier. * * @param identifier The JPQL identifier (in theory) that is used to retrieve the factory * responsible to parse a portion of the query starting with that identifier * @return The {@link ExpressionFactory} responsible to parse a portion of the query starting * with the given identifier; null if nothing was registered for it */ public ExpressionFactory getExpressionFactory(String identifier) { // No need to synchronize if the map was calculated if (cachedExpressionFactories != null) { return cachedExpressionFactories.get(identifier.toUpperCase(Locale.ROOT)); } // Synchronize to make sure only one thread populates the list of JPQL identifiers calculateExpressionFactories(); return cachedExpressionFactories.get(identifier.toUpperCase(Locale.ROOT)); } /** * Returns the unique identifiers of the {@link ExpressionFactory} handled by this BNF rule, * which includes those from the children as well. * * @return The list of unique identifiers of the {@link ExpressionFactory} registered with this * BNF rule and with its children */ public Iterable getExpressionFactoryIds() { return new ArrayIterable<>(getExpressionFactoryIdsImp()); } private String[] getExpressionFactoryIdsImp() { // No need to synchronize if the list of cached ExpressionFactory was calculated if (cachedExpressionFactoryIds != null) { return cachedExpressionFactoryIds; } // Synchronize to make sure only one thread populates the list of ExpressionFactory IDs synchronized (this) { if (cachedExpressionFactoryIds == null) { Set queryBNFs = new HashSet<>(); Set factoryIds = new HashSet<>(); calculateExpressionFactoryIds(queryBNFs, factoryIds); cachedExpressionFactoryIds = new String[factoryIds.size()]; factoryIds.toArray(cachedExpressionFactoryIds); } } return cachedExpressionFactoryIds; } /** * 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 */ public ExpressionRegistry getExpressionRegistry() { return expressionRegistry; } /** * When parsing the query and no {@link JPQLQueryBNF JPQLQueryBNFs} can help to parse the query, * then it will fall back on this one. * * @return The unique identifier of the {@link JPQLQueryBNF} to use in the last resort */ public String getFallbackBNFId() { return fallbackBNFId; } /** * Returns the unique identifier of the {@link ExpressionFactory} to use when the fall back BNF * ID is not null. This will be used to parse a portion of the query when the * registered {@link ExpressionFactory expression factories} cannot parse it. *

* Note: This method is only called if {@link #getFallbackBNFId()} does not return null. * * @return The unique identifier of the {@link ExpressionFactory} */ public String getFallbackExpressionFactoryId() { return fallbackExpressionFactoryId; } /** * Returns the unique identifier of this {@link JPQLQueryBNF}. * * @return The identifier used to register this {@link JPQLQueryBNF} with {@link AbstractExpression} */ public String getId() { return id; } /** * Retrieves the JPQL identifiers that are supported by this BNF rule. The JPQL identifiers are * retrieved by scanning the {@link ExpressionFactory} registered with this BNF rule and the * child BNF rules. * * @return The list of JPQL identifiers that are supported by this BNF */ public Iterable getIdentifiers() { // No need to synchronize if the cached JPQL identifiers was calculated // Note: it could be possible cachedIdentifiers was instantiated but the list of JPQL // identifiers has not been copied from cachedExpressionFactoryIds yet. // cachedExpressionFactoryIds is set at the end of the method, insuring proper initialization if (cachedExpressionFactoryIds != null) { return new ArrayIterable<>(cachedIdentifiers); } // Synchronize to make sure only one thread populates the list of JPQL identifiers calculateExpressionFactories(); return new ArrayIterable<>(cachedIdentifiers); } /** * Determines whether the {@link Expression} handles a collection of sub-expressions that * are aggregated by logical or arithmetic operators. * * @return true if the sub-expression to parse might have several logical and/or * arithmetic expressions; false otherwise */ public boolean handleAggregate() { // No need to synchronize if the property was calculated if (handleAggregate != null) { return handleAggregate; } // Synchronize to make sure only one thread calculates it synchronized (this) { if (handleAggregate == null) { Set children = new HashSet<>(); handleAggregate = calculateHandleAggregate(children); } } return handleAggregate; } /** * Determines whether the Expression handles a collection of sub-expressions that * are separated by commas. * * @return true if the sub-expression to parse might have several sub-expressions * separated by commas; false otherwise */ public boolean handleCollection() { // No need to synchronize if the property was calculated if (handleCollection != null) { return handleCollection; } // Synchronize to make sure only one thread calculates it synchronized (this) { if (handleCollection == null) { Set children = new HashSet<>(); handleCollection = calculateHandleCollection(children); } } return handleCollection; } /** * @since 2.5 */ public boolean handlesNestedArray() { return handleNestedArray; } /** * Determines whether this BNF handles parsing a sub-expression, i.e. parsing an expression * encapsulated by parenthesis. See {@link #setHandleSubExpression(boolean)} for more details. * * @return true if this BNF handles parsing a sub-expression; false otherwise */ public boolean handleSubExpression() { return handleSubExpression; } /** * Determines whether the BNF with the given ID is part of this BNF or not. * * @param queryBNFId The unique identifier of the BNF rule to check if it's a child of this one * @return true if the BNF with the given ID is * @since 2.5.1 */ public boolean hasChild(String queryBNFId) { for (JPQLQueryBNF child : getChildren()) { if (child.getId().equals(queryBNFId)) { return true; } } return false; } /** * Determines if this query BNF support the given word, which can be an identifier. * * @param word A word that could be a JPQL identifier or anything else * @return true if the given word is a JPQL identifier and it is supported by this * BNF; false otherwise */ public boolean hasIdentifier(String word) { // No need to synchronize if the map of cached ExpressionFactory was calculated if (cachedExpressionFactories != null) { return cachedExpressionFactories.containsKey(word); } // Synchronize to make sure only one thread calculates it calculateExpressionFactories(); return cachedExpressionFactories.containsKey(word); } /** * Initializes this BNF by registering child {@link JPQLQueryBNF JPQLQueryBNFs} and {@link * ExpressionFactory ExpressionFactories}. */ protected void initialize() { } /** * Initializes this JPQLQueryBNF. * * @param id The unique identifier of this BNF rule * @exception NullPointerException The given unique identifier cannot be null */ @SuppressWarnings("this-escape") private void initialize(String id) { if (id == null) { throw new NullPointerException("The unique identifier of this JPQLQueryBNF cannot be null"); } this.id = id; initialize(); } /** * Determines whether this BNF has child BNFs registered only to properly parse a query or if the * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF}, * it registers a series of children BNFs but they shouldn't be used to determine if they are * part of that BNF since the comparator identifiers are. * * @return false by default */ public boolean isCompound() { return compound; } /** * Returns the set of all the query BNFs that are part of this BNF. The set always include this * BNF as well. * * @return The children BNFs describing this BNF rule. The set excludes BNF rules that are used * to complete a BNF, such as the BNF rules defined for BETWEEN since they are required * to properly parse the query */ public Iterable nonCompoundChildren() { // No need to synchronize if the list of child BNFs (non-compound) was calculated if (nonCompoundChildren != null) { return new ArrayIterable<>(nonCompoundChildren); } // Synchronize to make sure only one thread populates the list of child BNFs (non-compound) synchronized (this) { if (nonCompoundChildren == null) { nonCompoundChildren = buildChildren(nonCompoundFilter); } } return new ArrayIterable<>(nonCompoundChildren); } /** * Registers the unique identifier of the BNF rule as a child of this BNF rule. * * @param queryBNFId The unique identifier of the BNF rule to add as a child * @exception NullPointerException The queryBNFId cannot be null */ protected final void registerChild(String queryBNFId) { if (queryBNFId == null) { throw new NullPointerException("The queryBNFId cannot be null"); } if (children == null) { children = new LinkedList<>(); } children.add(queryBNFId); } /** * Registers a unique identifier of the {@link ExpressionFactory} to register with this BNF rule. * * @param expressionFactoryId The unique identifier of the {@link ExpressionFactory} * @exception NullPointerException The expressionFactoryId cannot be null */ protected final void registerExpressionFactory(String expressionFactoryId) { if (expressionFactoryId == null) { throw new NullPointerException("The expressionFactoryId cannot be null"); } if (expressionFactoryIds == null) { expressionFactoryIds = new LinkedList<>(); } expressionFactoryIds.add(expressionFactoryId); } /** * Determines whether this BNF has child BNFs registered only to properly parse a query or if the * child BNFs are part of the BNF. An example if a compound BNF is {@link BetweenExpressionBNF}, * it registers a series of children BNFs but they shouldn't be used to determine if they are * part of that BNF since the comparator identifiers are. * * @param compound true if this BNF represents a compound BNF and its children are * not part of this BNF but only to support compound expression; false otherwise */ public void setCompound(boolean compound) { this.compound = compound; } /** * Sets the backpointer to the {@link ExpressionRegistry} with which this {@link JPQLQueryBNF} * was registered. * * @param expressionRegistry The registry for a given JPQL grammar */ final void setExpressionRegistry(ExpressionRegistry expressionRegistry) { this.expressionRegistry = expressionRegistry; } /** * When parsing the query and no {@link JPQLQueryBNF JPQLQueryBNFs} can help to parse the query, * then it will fall back on the given one. * * @param fallbackBNFId The unique identifier of the {@link JPQLQueryBNF} to use in the last resort */ public void setFallbackBNFId(String fallbackBNFId) { this.fallbackBNFId = fallbackBNFId; } /** * Sets the unique identifier of the {@link ExpressionFactory} to use when the fall back BNF * ID is not null. This will be used to parse a portion of the query when the * registered {@link ExpressionFactory expression factories} cannot parse it. *

* Note: This method is only called if {@link #getFallbackBNFId()} does not return null. * * @param fallbackExpressionFactoryId The unique identifier of the {@link ExpressionFactory} */ public void setFallbackExpressionFactoryId(String fallbackExpressionFactoryId) { this.fallbackExpressionFactoryId = fallbackExpressionFactoryId; } /** * Sets whether the {@link Expression} handles a collection of sub-expressions that are * aggregated by logical or arithmetic operators. * * @param handleAggregate true if the sub-expression to parse might have several * logical and/or arithmetic expressions; false otherwise */ public void setHandleAggregate(boolean handleAggregate) { this.handleAggregate = handleAggregate; } /** * Sets whether the Expression handles a collection of sub-expressions that are * separated by commas. * * @param handleCollection true if the sub-expression to parse might have several * sub-expressions separated by commas; false otherwise */ public void setHandleCollection(boolean handleCollection) { this.handleCollection = handleCollection; } /** * Sets whether this BNF supports nested array or not. A nested array is a sub-expression with * its child being a collection expression: (item_1, item_2, ..., item_n). * * @param handleNestedArray true if the expression represented by this BNF can be * a nested array; false otherwise * @since 2.5 */ public void setHandleNestedArray(boolean handleNestedArray) { this.handleNestedArray = handleNestedArray; } /** * Sets whether this BNF handles parsing a sub-expression, i.e. parsing an expression * encapsulated by parenthesis. Which in fact would be handled by the fallback {@link * ExpressionFactory}. The default behavior is to not handle it. *

* A good example for using this option is when an {@link Expression} cannot use any {@link * ExpressionFactory} for creating a child object, parsing will use the fallback {@link * ExpressionFactory}, if one was specified. So when this is set to true, the * fallback {@link ExpressionFactory} will be immediately invoked. *

* Let's say we want to parse "SELECT e FROM (SELECT a FROM Address a) e", {@link FromClause} * cannot use a factory for parsing the entity name (that's what usually the FROM * clause has) so it uses the fallback factory to create {@link IdentificationVariableDeclaration}. * Then IdentificationVariableDeclaration also cannot use any factory to create its * child object so it uses the fallback factory to create {@link RangeVariableDeclaration}. * By changing the status of for handling the sub-expression for the BNFs for those objects, then * a subquery can be created by RangeVariableDeclaration. * *

FromClause
     *  |- IdentificationVariableDeclaration
     *       |- RangeVariableDeclaration
     *            |- SubExpression(subquery)
* * In order to get this working, the following would have to be done into the grammar: * *
 public class MyJPQLGrammar extends AbstractJPQLGrammar {
     *   @Override
     *   protected void initializeBNFs() {
     *      setHandleSubExpression(InternalFromClauseBNF.ID,                true);
     *      setHandleSubExpression(InternalSimpleFromClauseBNF.ID,          true);
     *      setHandleSubExpression(IdentificationVariableDeclarationBNF.ID, true);
     *      setHandleSubExpression(RangeVariableDeclarationBNF.ID,          true);
     *   }
     * }
* * @param handleSubExpression true to let the creation of a sub-expression be * created by the fallback {@link ExpressionFactory} registered with this BNF; false * otherwise (which is the default value) */ public void setHandleSubExpression(boolean handleSubExpression) { this.handleSubExpression = handleSubExpression; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getClass().getSimpleName()); toString(sb); return sb.toString(); } /** * Adds to the given builder more information about this {@link JPQLQueryBNF}. * * @param sb The builder used to add information about this class */ protected void toString(StringBuilder sb) { sb.append("("); sb.append(id); sb.append(")\n\nidentifiers="); sb.append(getIdentifiers()); sb.append("\n\nexpressionFactories="); sb.append(Arrays.toString(getExpressionFactoryIdsImp())); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy