/******************************************************************************* * Copyright (c) 2006, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation * ******************************************************************************/ package org.eclipse.persistence.jpa.jpql.parser; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; 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 #childNonCompoundQueryBNFs}. * 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()); } // Synchronize to make sure only one thread populates the list of JPQL identifiers calculateExpressionFactories(); return cachedExpressionFactories.get(identifier.toUpperCase()); } /** * 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; } /** * {@inheritDoc} */ @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())); } }