blob: 41210092c40a9e5823902013166de5c39c27b430 [file] [log] [blame]
/*
* 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.tools.model.query;
import java.io.IOException;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.persistence.jpa.jpql.Assert;
import org.eclipse.persistence.jpa.jpql.parser.Join;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
/**
* A <code><b>JOIN</b></code> enables the fetching of an association as a side effect of the
* execution of a query. A <code><b>JOIN</b></code> is specified over an entity and its related
* entities.
*
* <div><p><b>BNF:</b> <code>join ::= join_spec join_association_path_expression [AS] identification_variable</code></p></div>
* <p>
* A <b>JOIN FETCH</b> enables the fetching of an association as a side effect of the execution of
* a query. A <b>JOIN FETCH</b> is specified over an entity and its related entities.
*
* <div><p><b>BNF:</b> <code>fetch_join ::= join_spec FETCH join_association_path_expression</code></p></div>
*
* @see Join
*
* @version 2.4
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings({"nls", "unused"}) // unused used for the import statement: see bug 330740
public class JoinStateObject extends AbstractStateObject {
/**
* Flag used to determine if the <code><b>AS</b></code> identifier is used or not.
*/
private boolean as;
/**
* The state object holding the identification variable.
*/
private IdentificationVariableStateObject identificationVariable;
/**
* The state object holding the join association path.
*/
private CollectionValuedPathExpressionStateObject joinAssociationPath;
/**
* One of the joining types used by this state object.
*/
private String joinType;
/**
* Notifies the visibility of the <code><b>AS</b></code> identifier has changed.
*/
public static final String AS_PROPERTY = "as";
/**
* Notifies the join type property has changed.
*/
public static final String JOIN_TYPE_PROPERTY = "joinType";
/**
* Creates a new <code>JoinStateObject</code>.
*
* @param parent The parent of this state object
* @param joinType One of the joining types
*/
public JoinStateObject(AbstractIdentificationVariableDeclarationStateObject parent,
String joinType) {
this(parent, joinType, false);
}
/**
* Creates a new <code>JoinStateObject</code>.
*
* @param parent The parent of this state object
* @param joinType One of the joining types
* @param as Determine whether the <code><b>AS</b></code> identifier is used or not
*/
public JoinStateObject(AbstractIdentificationVariableDeclarationStateObject parent,
String joinType,
boolean as) {
super(parent);
validateJoinType(joinType);
this.joinType = joinType;
this.as = as;
}
@Override
public void accept(StateObjectVisitor visitor) {
visitor.visit(this);
}
/**
* Makes sure the <code><b>AS</b></code> identifier is specified.
*
* @return This object
*/
public JoinStateObject addAs() {
if (!as) {
setAs(true);
}
return this;
}
@Override
protected void addChildren(List<StateObject> children) {
super.addChildren(children);
children.add(joinAssociationPath);
children.add(identificationVariable);
}
/**
* Adds the given segments to the end of the join association path expression. The identification
* variable will not be affected.
*
* @param paths The new path expression
*/
public void addJoinAssociationPaths(List<String> paths) {
joinAssociationPath.addItems(paths);
}
@Override
public Join getExpression() {
return (Join) super.getExpression();
}
/**
* Returns the name of the identification variable that defines the join association path.
*
* @return The variable defining the join association path
*/
public String getIdentificationVariable() {
return identificationVariable.getText();
}
/**
* Returns the state object holding the identification variable.
*
* @return The portion of the joining expression representing the identification variable
*/
public IdentificationVariableStateObject getIdentificationVariableStateObject() {
return identificationVariable;
}
/**
* Returns the {@link StateObject} representing the identification variable that starts the path
* expression, which can be a sample identification variable, a map value, map key or map entry
* expression.
*
* @return The root of the path expression
*/
public StateObject getJoinAssociationIdentificationVariable() {
return joinAssociationPath.getIdentificationVariable();
}
/**
* Returns the {@link CollectionValuedPathExpressionStateObject} representing the join
* association path.
*
* @return The state object representing the join association path
*/
public CollectionValuedPathExpressionStateObject getJoinAssociationPathStateObject() {
return joinAssociationPath;
}
/**
* Returns the joining type.
*
* @return The joining type of this joining expression
*/
public String getJoinType() {
return joinType;
}
@Override
public AbstractIdentificationVariableDeclarationStateObject getParent() {
return (AbstractIdentificationVariableDeclarationStateObject) super.getParent();
}
/**
* Determines whether the <code><b>AS</b></code> identifier is used or not.
*
* @return <code>true</code> if the <code><b>AS</b></code> identifier is part of the expression;
* <code>false</code> otherwise
*/
public boolean hasAs() {
return as;
}
/**
* Determines whether the identifier <b>FETCH</b> was parsed.
*
* @return <code>true</code> if the identifier <b>FETCH</b> was parsed; <code>false</code> otherwise
*/
public boolean hasFetch() {
String joinType = getJoinType();
return joinType == JOIN_FETCH ||
joinType == INNER_JOIN_FETCH ||
joinType == LEFT_JOIN_FETCH ||
joinType == LEFT_OUTER_JOIN_FETCH;
}
/**
* Determines whether the identification variable has been defined.
*
* @return <code>true</code> if the identification has been defined; <code>false</code> otherwise
*/
public boolean hasIdentificationVariable() {
return identificationVariable.hasText();
}
@Override
protected void initialize() {
super.initialize();
joinAssociationPath = new CollectionValuedPathExpressionStateObject(this);
identificationVariable = new IdentificationVariableStateObject(this);
}
@Override
public boolean isEquivalent(StateObject stateObject) {
if (super.isEquivalent(stateObject)) {
JoinStateObject join = (JoinStateObject) stateObject;
return joinType.equals(join.joinType) &&
areEquivalent(joinAssociationPath, join.joinAssociationPath) &&
(as == join.as) &&
identificationVariable.isEquivalent(join.identificationVariable);
}
return false;
}
/**
* Returns the segments in the state field path in order.
*
* @return An <code>Iterator</code> over the segments of the state field path
*/
public ListIterable<String> joinAssociationPaths() {
return joinAssociationPath.items();
}
/**
* Returns the number of segments in the path expression.
*
* @return The number of segments
*/
public int joinAssociationPathSize() {
return joinAssociationPath.itemsSize();
}
/**
* Makes sure the <code><b>AS</b></code> identifier is not specified.
*/
public void removeNot() {
if (as) {
setAs(false);
}
}
/**
* Sets whether the <code><b>AS</b></code> identifier is used or not.
*
* @param as <code>true</code> if the <code><b>AS</b></code> identifier is part of the expression;
* <code>false</code> otherwise
*/
public void setAs(boolean as) {
boolean oldAs = this.as;
this.as = as;
firePropertyChanged(AS_PROPERTY, oldAs, as);
}
/**
* Keeps a reference of the {@link Join parsed object} object, which should only be done when
* this object is instantiated during the conversion of a parsed JPQL query into {@link
* StateObject StateObjects}.
*
* @param expression The {@link Join parsed object} representing a <code><b>JOIN</b></code>
* expression
*/
public void setExpression(Join expression) {
super.setExpression(expression);
}
/**
* Sets the name of the identification variable that defines the join association path.
*
* @param identificationVariable The new variable defining the join association path
*/
public void setIdentificationVariable(String identificationVariable) {
this.identificationVariable.setText(identificationVariable);
}
/**
* Sets the {@link StateObject} representing the identification variable that starts the path
* expression, which can be a sample identification variable, a map value, map key or map entry
* expression.
*
* @param identificationVariable The root of the path expression
*/
public void setJoinAssociationIdentificationVariable(StateObject identificationVariable) {
joinAssociationPath.setIdentificationVariable(identificationVariable);
}
/**
* Changes the path expression with the list of segments, the identification variable will also
* be updated with the first segment.
*
* @param path The new path expression
*/
public void setJoinAssociationPath(String path) {
joinAssociationPath.setPath(path);
}
/**
* Changes the path expression with the list of segments, the identification variable will also
* be updated with the first segment.
*
* @param paths The new path expression
*/
public void setJoinAssociationPaths(ListIterator<String> paths) {
joinAssociationPath.setPaths(paths);
}
/**
* Changes the path expression with the list of segments, the identification variable will also
* be updated with the first segment.
*
* @param paths The new path expression
*/
public void setJoinAssociationPaths(String[] paths) {
joinAssociationPath.setPaths(paths);
}
/**
* Sets the joining type.
*
* @param joinType One of the joining types
*/
public void setJoinType(String joinType) {
validateJoinType(joinType);
String oldJoinType = this.joinType;
this.joinType = joinType;
firePropertyChanged(JOIN_TYPE_PROPERTY, oldJoinType, joinType);
}
/**
* Toggles the usage of the <code><b>AS</b></code> identifier.
*/
public void toggleAs() {
setAs(!as);
}
@Override
protected void toTextInternal(Appendable writer) throws IOException {
writer.append(joinType);
writer.append(SPACE);
joinAssociationPath.toString(writer);
writer.append(SPACE);
if (as) {
writer.append(AS);
writer.append(SPACE);
}
identificationVariable.toString(writer);
}
/**
* Validates the given join type.
*
* @param joinType One of the possible joining types
*/
protected void validateJoinType(String joinType) {
Assert.isValid(
joinType,
"The join type is not valid",
JOIN, LEFT_JOIN, LEFT_OUTER_JOIN, INNER_JOIN,
JOIN_FETCH, LEFT_JOIN_FETCH, LEFT_OUTER_JOIN_FETCH, INNER_JOIN_FETCH
);
}
}