| /* |
| * 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 |
| // |
| // 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; |
| |
| /** |
| * <p>This defines a single Backus-Naur Form (BNF) of the JPQL grammar. The Java Persistence functional |
| * specifications are:</p> |
| * <ul> |
| * <li>{@link JPQLGrammar1_0}: <a href="http://jcp.org/en/jsr/detail?id=220">JSR 220: Enterprise JavaBeans™ version 3.0</a></li> |
| * <li>{@link JPQLGrammar2_0}: <a href="http://jcp.org/en/jsr/detail?id=317">JSR 317: Java™ Persistence 2.0</a></li> |
| * <li>{@link JPQLGrammar2_1}: <a href="http://jcp.org/en/jsr/detail?id=338">JSR 338: Java™ Persistence 2.1</a></li> |
| * </ul> |
| * |
| * <p>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.</p> |
| * |
| * @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<String, ExpressionFactory> 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<String> 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<String> 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 <code>JPQLQueryBNF</code>. |
| */ |
| private static final Filter<JPQLQueryBNF> nonCompoundFilter = buildNonCompoundFilter(); |
| |
| /** |
| * Creates a new <code>JPQLQueryBNF</code>. |
| * |
| * @param id The unique identifier of this BNF rule |
| * @exception NullPointerException The given unique identifier cannot be <code>null</code> |
| */ |
| protected JPQLQueryBNF(String id) { |
| super(); |
| initialize(id); |
| } |
| |
| private static Filter<JPQLQueryBNF> buildNonCompoundFilter() { |
| return new Filter<JPQLQueryBNF>() { |
| @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<JPQLQueryBNF> queryBNFs, Filter<JPQLQueryBNF> 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<JPQLQueryBNF> filter) { |
| |
| Set<JPQLQueryBNF> queryBNFs = new HashSet<JPQLQueryBNF>(); |
| 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<String, ExpressionFactory> factories = new HashMap<String, ExpressionFactory>(); |
| |
| // 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<JPQLQueryBNF> queryBNFs, Set<String> 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<JPQLQueryBNF> 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<JPQLQueryBNF> 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<JPQLQueryBNF> children() { |
| return new ArrayIterable<JPQLQueryBNF>(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.<JPQLQueryBNF>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; <code>null</code> 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<String> getExpressionFactoryIds() { |
| return new ArrayIterable<String>(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<JPQLQueryBNF> queryBNFs = new HashSet<JPQLQueryBNF>(); |
| Set<String> factoryIds = new HashSet<String>(); |
| 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 <code>null</code>. This will be used to parse a portion of the query when the |
| * registered {@link ExpressionFactory expression factories} cannot parse it. |
| * <p> |
| * Note: This method is only called if {@link #getFallbackBNFId()} does not return <code>null</code>. |
| * |
| * @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<String> 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<String>(cachedIdentifiers); |
| } |
| |
| // Synchronize to make sure only one thread populates the list of JPQL identifiers |
| calculateExpressionFactories(); |
| |
| return new ArrayIterable<String>(cachedIdentifiers); |
| } |
| |
| /** |
| * Determines whether the {@link Expression} handles a collection of sub-expressions that |
| * are aggregated by logical or arithmetic operators. |
| * |
| * @return <code>true</code> if the sub-expression to parse might have several logical and/or |
| * arithmetic expressions; <code>false</code> 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<JPQLQueryBNF> children = new HashSet<JPQLQueryBNF>(); |
| handleAggregate = calculateHandleAggregate(children); |
| } |
| } |
| |
| return handleAggregate; |
| } |
| |
| /** |
| * Determines whether the <code>Expression</code> handles a collection of sub-expressions that |
| * are separated by commas. |
| * |
| * @return <code>true</code> if the sub-expression to parse might have several sub-expressions |
| * separated by commas; <code>false</code> 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<JPQLQueryBNF> children = new HashSet<JPQLQueryBNF>(); |
| 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 <code>true</code> if this BNF handles parsing a sub-expression; <code>false</code> 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 <code>true</code> 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 <code>true</code> if the given word is a JPQL identifier and it is supported by this |
| * BNF; <code>false</code> 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 <code>JPQLQueryBNF</code>. |
| * |
| * @param id The unique identifier of this BNF rule |
| * @exception NullPointerException The given unique identifier cannot be <code>null</code> |
| */ |
| 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 <code>false</code> 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 <b>BETWEEN</b> since they are required |
| * to properly parse the query |
| */ |
| public Iterable<JPQLQueryBNF> nonCompoundChildren() { |
| |
| // No need to synchronize if the list of child BNFs (non-compound) was calculated |
| if (nonCompoundChildren != null) { |
| return new ArrayIterable<JPQLQueryBNF>(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<JPQLQueryBNF>(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 <code>queryBNFId</code> cannot be <code>null</code> |
| */ |
| protected final void registerChild(String queryBNFId) { |
| |
| if (queryBNFId == null) { |
| throw new NullPointerException("The queryBNFId cannot be null"); |
| } |
| |
| if (children == null) { |
| children = new LinkedList<String>(); |
| } |
| |
| 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 <code>expressionFactoryId</code> cannot be <code>null</code> |
| */ |
| protected final void registerExpressionFactory(String expressionFactoryId) { |
| |
| if (expressionFactoryId == null) { |
| throw new NullPointerException("The expressionFactoryId cannot be null"); |
| } |
| |
| if (expressionFactoryIds == null) { |
| expressionFactoryIds = new LinkedList<String>(); |
| } |
| |
| 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 <code>true</code> if this BNF represents a compound BNF and its children are |
| * not part of this BNF but only to support compound expression; <code>false</code> 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 <code>null</code>. This will be used to parse a portion of the query when the |
| * registered {@link ExpressionFactory expression factories} cannot parse it. |
| * <p> |
| * Note: This method is only called if {@link #getFallbackBNFId()} does not return <code>null</code>. |
| * |
| * @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 <code>true</code> if the sub-expression to parse might have several |
| * logical and/or arithmetic expressions; <code>false</code> otherwise |
| */ |
| public void setHandleAggregate(boolean handleAggregate) { |
| this.handleAggregate = handleAggregate; |
| } |
| |
| /** |
| * Sets whether the <code>Expression</code> handles a collection of sub-expressions that are |
| * separated by commas. |
| * |
| * @param handleCollection <code>true</code> if the sub-expression to parse might have several |
| * sub-expressions separated by commas; <code>false</code> 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 <code>true</code> if the expression represented by this BNF can be |
| * a nested array; <code>false</code> 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. |
| * <p> |
| * 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 <code>true</code>, the |
| * fallback {@link ExpressionFactory} will be immediately invoked. |
| * <p> |
| * 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 <code>FROM</code> |
| * clause has) so it uses the fallback factory to create {@link IdentificationVariableDeclaration}. |
| * Then <code>IdentificationVariableDeclaration</code> 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 <code>RangeVariableDeclaration</code>. |
| * |
| * <pre><code>FromClause |
| * |- IdentificationVariableDeclaration |
| * |- RangeVariableDeclaration |
| * |- SubExpression(subquery)</code></pre> |
| * |
| * In order to get this working, the following would have to be done into the grammar: |
| * |
| * <pre><code> 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); |
| * } |
| * }</code></pre> |
| * |
| * @param handleSubExpression <code>true</code> to let the creation of a sub-expression be |
| * created by the fallback {@link ExpressionFactory} registered with this BNF; <code>false</code> |
| * 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())); |
| } |
| } |