/******************************************************************************* * 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.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.persistence.jpa.jpql.WordParser; /** * The <b>FROM</b> clause of a query defines the domain of the query by declaring identification * variables. An identification variable is an identifier declared in the <b>FROM</b> clause of a * query. The domain of the query may be constrained by path expressions. Identification variables * designate instances of a particular entity abstract schema type. The <b>FROM</b> clause can * contain multiple identification variable declarations separated by a comma (,). * * @see FromClause * @see SimpleFromClause * * @version 2.5 * @since 2.3 * @author Pascal Filion */ public abstract class AbstractFromClause extends AbstractExpression { /** * The {@link Expression} that represents the <code><b>AS OF</b></code> clause. * * @since 2.5 */ private AbstractExpression asOfClause; /** * The declaration portion of this <b>FROM</b> clause. */ private AbstractExpression declaration; /** * Determines whether a whitespace was parsed after the identifier <b>FROM</b>. */ private boolean hasSpace; /** * Determines whether there is a whitespace after the hierarchical query clause. * * @since 2.5 */ private boolean hasSpaceAfterHierarchicalQueryClause; /** * Determines whether there is a whitespace after the declaration and either the hierarchical * query clause or the <code><b>AS OF</b></code> clause was parsed. * * @since 2.5 */ private boolean hasSpaceDeclaration; /** * The hierarchical query clause, which holds onto the <code><b>START WITH</b></code> and * <code><b>CONNECT BY</b></code> clauses. * * @since 2.5 */ private AbstractExpression hierarchicalQueryClause; /** * The actual identifier found in the string representation of the JPQL query. */ private String identifier; /** * Creates a new <code>AbstractFromClause</code>. * * @param parent The parent of this expression */ protected AbstractFromClause(AbstractExpression parent) { super(parent, FROM); } /** * {@inheritDoc} */ public void acceptChildren(ExpressionVisitor visitor) { getDeclaration().accept(visitor); getHierarchicalQueryClause().accept(visitor); getAsOfClause().accept(visitor); } /** * {@inheritDoc} */ @Override protected void addChildrenTo(Collection<Expression> children) { children.add(getDeclaration()); children.add(getHierarchicalQueryClause()); children.add(getAsOfClause()); } /** * {@inheritDoc} */ @Override protected void addOrderedChildrenTo(List<Expression> children) { // 'FROM' children.add(buildStringExpression(FROM)); // Space between FROM and the declaration if (hasSpace) { children.add(buildStringExpression(SPACE)); } // Declaration if (declaration != null) { children.add(declaration); } if (hasSpaceDeclaration) { children.add(buildStringExpression(SPACE)); } // Hierarchical query clause if (hierarchicalQueryClause != null) { children.add(hierarchicalQueryClause); } if (hasSpaceAfterHierarchicalQueryClause) { children.add(buildStringExpression(SPACE)); } // 'AS OF' clause if (asOfClause != null) { children.add(asOfClause); } } /** * Creates a new {@link CollectionExpression} that will wrap the single declaration. * * @return The single declaration represented by a temporary collection */ public final CollectionExpression buildCollectionExpression() { List<AbstractExpression> children = new ArrayList<AbstractExpression>(1); children.add((AbstractExpression) getDeclaration()); List<Boolean> commas = new ArrayList<Boolean>(1); commas.add(Boolean.FALSE); List<Boolean> spaces = new ArrayList<Boolean>(1); spaces.add(Boolean.FALSE); return new CollectionExpression(this, children, commas, spaces, true); } /** * {@inheritDoc} */ @Override public final JPQLQueryBNF findQueryBNF(Expression expression) { if ((declaration != null) && declaration.isAncestor(expression)) { return getQueryBNF(getDeclarationQueryBNFId()); } return super.findQueryBNF(expression); } /** * Returns the actual <b>FROM</b> identifier found in the string representation of the JPQL * query, which has the actual case that was used. * * @return The <b>FROM</b> identifier that was actually parsed */ public final String getActualIdentifier() { return identifier; } /** * Returns the {@link Expression} representing the <b>AS OF</b> clause. * * @return The expression representing the <b>AS OF</b> clause */ public final Expression getAsOfClause() { if (asOfClause == null) { asOfClause = buildNullExpression(); } return asOfClause; } /** * Returns the {@link Expression} that represents the declaration of this clause. * * @return The expression that was parsed representing the declaration */ public final Expression getDeclaration() { if (declaration == null) { declaration = buildNullExpression(); } return declaration; } /** * Returns the BNF of the declaration part of this clause. * * @return The BNF of the declaration part of this clause */ public abstract String getDeclarationQueryBNFId(); /** * Returns the {@link Expression} representing the hierarchical query clause. * * @return The expression representing the hierarchical query clause * @since 2.5 */ public final Expression getHierarchicalQueryClause() { if (hierarchicalQueryClause == null) { hierarchicalQueryClause = buildNullExpression(); } return hierarchicalQueryClause; } /** * Determines whether the <b>AS OF</b> clause is defined. * * @return <code>true</code> if the query that got parsed had the <b>AS OF</b> clause */ public final boolean hasAsOfClause() { return asOfClause != null && !asOfClause.isNull(); } /** * Determines whether the declaration of this clause was parsed. * * @return <code>true</code> if the declaration of this clause was parsed; <code>false</code> if * it was not parsed */ public final boolean hasDeclaration() { return declaration != null && !declaration.isNull(); } /** * Determines whether the hierarchical query clause was parsed or not. * * @return <code>true</code> if the query that got parsed had the hierarchical query clause * @since 2.5 */ public final boolean hasHierarchicalQueryClause() { return hierarchicalQueryClause != null && !hierarchicalQueryClause.isNull(); } /** * Determines whether a whitespace was found after the declaration query clause, which will be * <code>true</code> if it's followed by either the hierarchical query clause or the <code><b>AS * OF</b></code> clause. * * @return <code>true</code> if there was a whitespace after the declaration; <code>false</code> otherwise * @since 2.5 */ public final boolean hasSpaceAfterDeclaration() { return hasSpaceDeclaration; } /** * Determines whether a whitespace was parsed after the <b>FROM</b> identifier. * * @return <code>true</code> if a whitespace was parsed after the <b>FROM</b> identifier; * <code>false</code> otherwise */ public final boolean hasSpaceAfterFrom() { return hasSpace; } /** * Determines whether a whitespace was found after the hierarchical query clause. In some cases, * the space is owned by a child of the hierarchical query clause. * * @return <code>true</code> if there was a whitespace after the hierarchical query clause and * owned by this expression; <code>false</code> otherwise * @since 2.5 */ public final boolean hasSpaceAfterHierarchicalQueryClause() { return hasSpaceAfterHierarchicalQueryClause; } /** * {@inheritDoc} */ @Override protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) { char character = wordParser.character(); // TODO: Add parameter tolerance and check for these 4 signs if tolerant is turned on only // this could happen while parsing an invalid query return wordParser.isArithmeticSymbol(character) || super.isParsingComplete(wordParser, word, expression); } /** * {@inheritDoc} */ @Override protected void parse(WordParser wordParser, boolean tolerant) { // Parse 'FROM' identifier = wordParser.moveForward(FROM); hasSpace = wordParser.skipLeadingWhitespace() > 0; // Parse the declaration declaration = parse(wordParser, getDeclarationQueryBNFId(), tolerant); int count = wordParser.skipLeadingWhitespace(); hasSpaceDeclaration = (count > 0); // Parse hierarchical query clause if (wordParser.startsWithIdentifier(START_WITH) || wordParser.startsWithIdentifier(CONNECT_BY) || wordParser.startsWithIdentifier(ORDER_SIBLINGS_BY)) { hierarchicalQueryClause = new HierarchicalQueryClause(this); hierarchicalQueryClause.parse(wordParser, tolerant); count = wordParser.skipLeadingWhitespace(); hasSpaceAfterHierarchicalQueryClause = (count > 0); } // AS OF clause if (wordParser.startsWithIdentifier(AS_OF)) { asOfClause = new AsOfClause(this); asOfClause.parse(wordParser, tolerant); } else if (hasSpaceAfterHierarchicalQueryClause) { hasSpaceAfterHierarchicalQueryClause = false; wordParser.moveBackward(count); } else if (hierarchicalQueryClause == null) { hasSpaceDeclaration = false; wordParser.moveBackward(count); } } /** * {@inheritDoc} */ @Override protected boolean shouldParseWithFactoryFirst() { return true; } /** * {@inheritDoc} */ @Override protected void toParsedText(StringBuilder writer, boolean actual) { // 'FROM' writer.append(actual ? identifier : FROM); if (hasSpace) { writer.append(SPACE); } // Declaration if (declaration != null) { declaration.toParsedText(writer, actual); } if (hasSpaceDeclaration) { writer.append(SPACE); } // Hierarchical query clause if (hierarchicalQueryClause != null) { hierarchicalQueryClause.toParsedText(writer, actual); } if (hasSpaceAfterHierarchicalQueryClause) { writer.append(SPACE); } // AS OF clause if (asOfClause != null) { asOfClause.toParsedText(writer, actual); } } }