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

org.eclipse.persistence.jpa.jpql.parser.Join 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 java.util.StringTokenizer;

import org.eclipse.persistence.jpa.jpql.WordParser;

/**
 * A JOIN enables the fetching of an association as a side effect of the execution of a query.
 * A JOIN is specified over an entity and its related entities.
 *
 * 
BNF: join ::= join_spec join_association_path_expression [AS] identification_variable
*

* A JOIN FETCH enables the fetching of an association as a side effect of the execution of * a query. A JOIN FETCH is specified over an entity and its related entities. * *

BNF: fetch_join ::= join_spec FETCH join_association_path_expression
*

* EclipseLink 2.4 * *

BNF: join_spec { abstract_schema_name | join_association_path_expression } [AS] identification_variable [join_condition]
* * @version 2.5 * @since 2.3 * @author Pascal Filion */ @SuppressWarnings("nls") public final class Join extends AbstractExpression { /** * The actual AS identifier found in the string representation of the JPQL query. */ private String asIdentifier; /** * Determines whether a whitespace was parsed after AS. */ private boolean hasSpaceAfterAs; /** * Determines whether a whitespace was parsed after the identification variable. */ private boolean hasSpaceAfterIdentificationVariable; /** * Determines whether a whitespace was parsed after JOIN. */ private boolean hasSpaceAfterJoin; /** * Determines whether a whitespace was parsed after the join association path expression. */ private boolean hasSpaceAfterJoinAssociation; /** * The {@link Expression} representing the identification variable. */ private AbstractExpression identificationVariable; /** * The {@link Expression} representing the join association path expression. */ private AbstractExpression joinAssociationPath; /** * The actual JOIN identifier found in the string representation of the JPQL query. */ private String joinIdentifier; /** * The {@link Expression} representing the join ON clause. */ private AbstractExpression onClause; /** * Make sure when parsing the identification variable with tolerance turned on, a the join * condition identifier ON is not parsed. */ private boolean parsingIdentificationVariable; /** * Creates a new Join. * * @param parent The parent of this expression * @param identifier The full JOIN identifier */ public Join(AbstractExpression parent, String identifier) { super(parent, identifier); } @Override public void accept(ExpressionVisitor visitor) { visitor.visit(this); } @Override public void acceptChildren(ExpressionVisitor visitor) { getJoinAssociationPath().accept(visitor); getIdentificationVariable().accept(visitor); getOnClause().accept(visitor); } @Override protected void addChildrenTo(Collection children) { children.add(getJoinAssociationPath()); children.add(getIdentificationVariable()); children.add(getOnClause()); } @Override protected void addOrderedChildrenTo(List children) { String join = getText(); // Break the identifier into multiple identifiers if (join.indexOf(SPACE) != -1) { StringTokenizer tokenizer = new StringTokenizer(join, " ", true); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); children.add(buildStringExpression(token)); } } else { children.add(buildStringExpression(join)); } if (hasSpaceAfterJoin) { children.add(buildStringExpression(SPACE)); } // Join association path if (joinAssociationPath != null) { children.add(joinAssociationPath); } if (hasSpaceAfterJoinAssociation) { children.add(buildStringExpression(SPACE)); } // 'AS' if (asIdentifier != null) { children.add(buildStringExpression(AS)); if (hasSpaceAfterAs) { children.add(buildStringExpression(SPACE)); } } // Identification variable if (identificationVariable != null) { children.add(identificationVariable); } if (hasSpaceAfterIdentificationVariable) { children.add(buildStringExpression(SPACE)); } // ON clause if (onClause != null) { children.add(onClause); } } @Override public JPQLQueryBNF findQueryBNF(Expression expression) { if ((joinAssociationPath != null) && joinAssociationPath.isAncestor(expression)) { return getQueryBNF(JoinAssociationPathExpressionBNF.ID); } if ((identificationVariable != null) && identificationVariable.isAncestor(expression)) { return getQueryBNF(IdentificationVariableBNF.ID); } if ((onClause != null) && onClause.isAncestor(expression)) { return getQueryBNF(OnClauseBNF.ID); } return super.findQueryBNF(expression); } /** * Returns the actual AS found in the string representation of the JPQL query, which has * the actual case that was used. * * @return The AS identifier that was actually parsed, or an empty string if it was not * parsed */ public String getActualAsIdentifier() { return asIdentifier; } /** * Returns the actual identifier found in the string representation of the JPQL query, which has * the actual case that was used. * * @return The identifier identifier that was actually parsed */ public String getActualIdentifier() { return joinIdentifier; } /** * Returns the {@link Expression} that represents the identification variable. * * @return The expression that was parsed representing the identification variable */ public Expression getIdentificationVariable() { if (identificationVariable == null) { identificationVariable = buildNullExpression(); } return identificationVariable; } /** * Returns the identifier this expression represents. * * @return Either JOIN, INNER JOIN, LEFT JOIN or LEFT OUTER JOIN. * Although it's possible to have an incomplete identifier if the query is not complete */ public String getIdentifier() { return getText(); } /** * Returns the {@link Expression} that represents the join association path expression. * * @return The expression that was parsed representing the join association path expression */ public Expression getJoinAssociationPath() { if (joinAssociationPath == null) { joinAssociationPath = buildNullExpression(); } return joinAssociationPath; } /** * Returns the {@link Expression} that represents the ON clause if present. * * @return The expression that was parsed representing the identification variable */ public Expression getOnClause() { if (onClause == null) { onClause = buildNullExpression(); } return onClause; } @Override public JPQLQueryBNF getQueryBNF() { return getQueryBNF(JoinBNF.ID); } /** * Determines whether the identifier AS was parsed. * * @return true if the identifier AS was parsed; false otherwise */ public boolean hasAs() { return asIdentifier != null; } /** * Determines whether the identifier FETCH was parsed. * * @return true if the identifier FETCH was parsed; false otherwise */ public boolean hasFetch() { String identifier = getText(); return identifier == JOIN_FETCH || identifier == INNER_JOIN_FETCH || identifier == LEFT_JOIN_FETCH || identifier == LEFT_OUTER_JOIN_FETCH; } /** * Determines whether the identification variable was parsed. * * @return true if the identification variable was parsed; false otherwise */ public boolean hasIdentificationVariable() { return identificationVariable != null && !identificationVariable.isNull() && !identificationVariable.isVirtual(); } /** * Determines whether the join association path expression was parsed. * * @return true if the join association path expression was parsed; false * otherwise */ public boolean hasJoinAssociationPath() { return joinAssociationPath != null && !joinAssociationPath.isNull(); } /** * Determines whether the ON clause was parsed. * * @return true if the ON clause was parsed; false otherwise */ public boolean hasOnClause() { return onClause != null && !onClause.isNull(); } /** * Determines whether a whitespace was parsed after AS. * * @return true if there was a whitespace after AS; false * otherwise */ public boolean hasSpaceAfterAs() { return hasSpaceAfterAs; } /** * Determines whether a whitespace was parsed before ON. * * @return true if there was a whitespace before ON; false * otherwise */ public boolean hasSpaceAfterIdentificationVariable() { return hasSpaceAfterIdentificationVariable; } /** * Determines whether a whitespace was parsed after JOIN. * * @return true if there was a whitespace after JOIN; false otherwise */ public boolean hasSpaceAfterJoin() { return hasSpaceAfterJoin; } /** * Determines whether a whitespace was parsed after the join association path expression. * * @return true if there was a whitespace after join association path expression; * false otherwise */ public boolean hasSpaceAfterJoinAssociation() { return hasSpaceAfterJoinAssociation; } /** * Determines whether this {@link Join} is a left join, i.e. {@link Expression#LEFT_JOIN}, * {@link Expression#LEFT_JOIN_FETCH}, {@link Expression#LEFT_OUTER_JOIN}, {@link * Expression#LEFT_OUTER_JOIN_FETCH}. * * @return true if this {@link Join} expression is a {@link Expression#LEFT_JOIN}, * {@link Expression#LEFT_JOIN_FETCH}, {@link Expression#LEFT_OUTER_JOIN}, {@link * Expression#LEFT_OUTER_JOIN_FETCH}; false otherwise */ public boolean isLeftJoin() { String identifier = getIdentifier(); return identifier == LEFT_JOIN || identifier == LEFT_OUTER_JOIN || identifier == LEFT_JOIN_FETCH || identifier == LEFT_OUTER_JOIN_FETCH; } @Override protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) { // Make sure when parsing the identification variable with tolerance turned on, // a the join condition identifier ON is not parsed if (parsingIdentificationVariable && word.equalsIgnoreCase(ON)) { return true; } return word.equalsIgnoreCase(AS) || word.equalsIgnoreCase(INNER) || word.equalsIgnoreCase(JOIN) || word.equalsIgnoreCase(LEFT) || word.equalsIgnoreCase(OUTER) || super.isParsingComplete(wordParser, word, expression); } @Override protected void parse(WordParser wordParser, boolean tolerant) { // Parse the JOIN identifier joinIdentifier = wordParser.moveForward(getText()); hasSpaceAfterJoin = wordParser.skipLeadingWhitespace() > 0; // Parse the join association path expression if (tolerant) { joinAssociationPath = parse(wordParser, JoinAssociationPathExpressionBNF.ID, tolerant); } // TREAT expression else if (wordParser.startsWithIdentifier(TREAT)) { joinAssociationPath = new TreatExpression(this); joinAssociationPath.parse(wordParser, tolerant); } // Abstract schema name, Collection-valued path expression or state field path expression else { String path = wordParser.word(); if (path.indexOf('.') == -1) { joinAssociationPath = new AbstractSchemaName(this, path); joinAssociationPath.parse(wordParser, tolerant); } else { joinAssociationPath = new CollectionValuedPathExpression(this, path); joinAssociationPath.parse(wordParser, tolerant); } } int count = wordParser.skipLeadingWhitespace(); hasSpaceAfterJoinAssociation = count > 0; // Parse 'AS' if (wordParser.startsWithIdentifier(AS)) { asIdentifier = wordParser.moveForward(AS); hasSpaceAfterAs = wordParser.skipLeadingWhitespace() > 0; } // Parse the identification variable parsingIdentificationVariable = true; if (tolerant) { identificationVariable = parse(wordParser, IdentificationVariableBNF.ID, tolerant); } else { String word = wordParser.word(); if ((word.length() > 0) && !isParsingComplete(wordParser, word, null)) { identificationVariable = new IdentificationVariable(this, word); identificationVariable.parse(wordParser, tolerant); } } parsingIdentificationVariable = false; // A JOIN FETCH without '[AS] identification_variable' will not keep the // whitespace after the join association for backward compatibility (for now) if (asIdentifier == null && hasSpaceAfterJoinAssociation && identificationVariable == null && hasFetch() && !wordParser.startsWithIdentifier(ON)) { hasSpaceAfterJoinAssociation = false; wordParser.moveBackward(count); count = 0; } else { count = wordParser.skipLeadingWhitespace(); } // Parse the ON clause if (tolerant) { onClause = parse(wordParser, OnClauseBNF.ID, tolerant); } else if (wordParser.startsWithIdentifier(ON)) { onClause = new OnClause(this); onClause.parse(wordParser, tolerant); } if (onClause != null) { hasSpaceAfterIdentificationVariable = (count > 0); } else { wordParser.moveBackward(count); } } @Override protected void toParsedText(StringBuilder writer, boolean actual) { // Join identifier writer.append(actual ? joinIdentifier : getText()); if (hasSpaceAfterJoin) { writer.append(SPACE); } // Join association path if (joinAssociationPath != null) { joinAssociationPath.toParsedText(writer, actual); } if (hasSpaceAfterJoinAssociation) { writer.append(SPACE); } // 'AS' if (asIdentifier != null) { writer.append(actual ? asIdentifier : AS); if (hasSpaceAfterAs) { writer.append(SPACE); } } // Identification variable if (identificationVariable != null) { identificationVariable.toParsedText(writer, actual); } if (hasSpaceAfterIdentificationVariable) { writer.append(SPACE); } // ON clause if (onClause != null) { onClause.toParsedText(writer, actual); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy