blob: ed53215fdbd0913b8b32f3bb541d00090573413a [file] [log] [blame]
/*
* Copyright (c) 2011, 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 org.eclipse.persistence.jpa.jpql.parser.AbstractSelectStatement;
import org.eclipse.persistence.jpa.jpql.tools.spi.IEntity;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
/**
* This state object represents the select statement, which has at least a <code><b>SELECT</b></code>
* clause and a <code><b>FROM</b></code> clause.
*
* @see SelectStatementStateObject
* @see FromClauseStateObject
* @see GroupByClauseStateObject
* @see HavingClauseStateObject
* @see SelectClauseStateObject
* @see WhereClauseStateObject
*
* @see AbstractSelectStatement
*
* @version 2.5
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public abstract class AbstractSelectStatementStateObject extends AbstractStateObject {
/**
* The state object representing the <code><b>FROM</b></code> clause.
*/
private AbstractFromClauseStateObject fromClause;
/**
* The state object representing the <code><b>GROUP BY</b></code> clause.
*/
private GroupByClauseStateObject groupByClause;
/**
* The state object representing the <code><b>HAVING</b></code> clause.
*/
private HavingClauseStateObject havingClause;
/**
* The state object representing the <code><b>SELECT</b></code> clause.
*/
private AbstractSelectClauseStateObject selectClause;
/**
* The state object representing the <code><b>WHERE</b></code> clause.
*/
private WhereClauseStateObject whereClause;
/**
* Notify the state object representing the <code><b>GROUP BY</b></code> clause has changed.
*/
public static final String GROUP_BY_CLAUSE_PROPERTY = "groupByClause";
/**
* Notify the state object representing the <code><b>HAVING</b></code> clause has changed.
*/
public static final String HAVING_CLAUSE_PROPERTY = "havingClause";
/**
* Notify the state object representing the <code><b>WHERE</b></code> clause has changed.
*/
public static final String WHERE_CLAUSE_PROPERTY = "whereClause";
/**
* Creates a new <code>AbstractSelectStatementStateObject</code>.
*
* @param parent The parent of this state object, which cannot be <code>null</code>
* @exception NullPointerException The given parent cannot be <code>null</code>
*/
protected AbstractSelectStatementStateObject(StateObject parent) {
super(parent);
}
@Override
protected void addChildren(List<StateObject> children) {
super.addChildren(children);
children.add(selectClause);
children.add(fromClause);
if (whereClause != null) {
children.add(whereClause);
}
if (groupByClause != null) {
children.add(groupByClause);
}
if (havingClause != null) {
children.add(havingClause);
}
}
/**
* Adds a new collection declaration to the <code><b>FROM</b></code> clause.
*
* @return The {@link CollectionMemberDeclarationStateObject} representing the collection
* declaration
*/
public CollectionMemberDeclarationStateObject addCollectionDeclaration() {
return getFromClause().addCollectionDeclaration();
}
/**
* Adds a new collection declaration to the <code><b>FROM</b></code> clause.
*
* @param collectionValuedPath The collection-valued path expression
* @param identificationVariable The variable defining the collection-valued path expression
* @return The {@link CollectionMemberDeclarationStateObject} representing the collection
* declaration
*/
public CollectionMemberDeclarationStateObject addCollectionDeclaration(String collectionValuedPath,
String identificationVariable) {
return getFromClause().addCollectionDeclaration(collectionValuedPath, identificationVariable);
}
/**
* Adds the <code><b>GROUP BY</b></code> clause. The clause is not added if it's already present.
*
* @return The {@link GroupByClauseStateObject}
*/
public GroupByClauseStateObject addGroupByClause() {
if (!hasGroupByClause()) {
setGroupByClause(new GroupByClauseStateObject(this));
}
return groupByClause;
}
/**
* Adds the <code><b>GROUP BY</b></code> clause and parses the given JPQL fragment. The clause is
* not added if it's already present.
*
* @param jpqlFragment The fragment of the JPQL to parse that represents the group by items, the
* fragment cannot start with <code><b>GROUP BY</b></code>
* @return The {@link GroupByClauseStateObject}
*/
public GroupByClauseStateObject addGroupByClause(String jpqlFragment) {
GroupByClauseStateObject stateObject = addGroupByClause();
stateObject.parse(jpqlFragment);
return stateObject;
}
/**
* Adds the <code><b>HAVING</b></code> clause. The clause is not added if it's already present.
*
* @return The {@link GroupByClauseStateObject}
*/
public HavingClauseStateObject addHavingClause() {
if (!hasHavingClause()) {
setHavingClause(new HavingClauseStateObject(this));
}
return havingClause;
}
/**
* Adds the <code><b>HAVING</b></code> clause and parses the given JPQL fragment. The clause is
* not added if it's already present.
*
* @param jpqlFragment The fragment of the JPQL to parse that represents the conditional expression,
* the fragment cannot start with <code><b>HAVING</b></code>
* @return The {@link HavingClauseStateObject}
*/
public HavingClauseStateObject addHavingClause(String jpqlFragment) {
HavingClauseStateObject stateObject = addHavingClause();
stateObject.parse(jpqlFragment);
return stateObject;
}
/**
* Adds a new range variable declaration to the <code><b>FROM</b></code> clause.
*
* @return The {@link StateObject} representing the new range variable declaration
*/
public IdentificationVariableDeclarationStateObject addRangeDeclaration() {
return getFromClause().addRangeDeclaration();
}
/**
* Adds to this select statement a new range variable declaration.
*
* @param entity The external form of the entity to add to the declaration list
* @param identificationVariable The unique identifier identifying the given {@link IEntity}
* @return The {@link StateObject} representing the new range variable declaration
*/
public IdentificationVariableDeclarationStateObject addRangeDeclaration(IEntity entity,
String identificationVariable) {
return getFromClause().addRangeDeclaration(entity, identificationVariable);
}
/**
* Adds to this select statement a new range variable declaration.
*
* @param entityName The name of the entity
* @param identificationVariable The unique identifier identifying the entity
* @return The {@link StateObject} representing the range variable declaration
*/
public IdentificationVariableDeclarationStateObject addRangeDeclaration(String entityName,
String identificationVariable) {
return getFromClause().addRangeDeclaration(entityName, identificationVariable);
}
/**
* Adds the <code><b>WHERE</b></code> clause. The clause is not added if it's already present.
*
* @return The {@link GroupByClauseStateObject}
*/
public WhereClauseStateObject addWhereClause() {
if (!hasWhereClause()) {
setWhereClause(new WhereClauseStateObject(this));
}
return whereClause;
}
/**
* Adds the <code><b>WHERE</b></code> clause and parses the given JPQL fragment. The clause is
* not added if it's already present.
*
* @param jpqlFragment The fragment of the JPQL to parse that represents the conditional expression,
* the fragment cannot start with <code><b>WHERE</b></code>
* @return The {@link WhereClauseStateObject}
*/
public WhereClauseStateObject addWhereClause(String jpqlFragment) {
WhereClauseStateObject stateObject = addWhereClause();
stateObject.parse(jpqlFragment);
return stateObject;
}
/**
* Creates the state object representing the <code><b>FROM</b></code> clause.
*
* @return A concrete instance of {@link AbstractFromClauseStateObject}
*/
protected abstract AbstractFromClauseStateObject buildFromClause();
/**
* Creates the state object representing the <code><b>SELECT</b></code> clause.
*
* @return A concrete instance of {@link AbstractSelectClauseStateObject}
*/
protected abstract AbstractSelectClauseStateObject buildSelectClause();
/**
* Returns the list of {@link VariableDeclarationStateObject} defining the variable declarations,
* which are mapping an entity to a variable or a collection-valued member to a variable.
* <p>
* Example:
* <ul>
* <li><code>Employee e</code></li>
* <li><code>IN (e.employees) AS emps</code></li>
* </ul>
*
* @return The list of {@link VariableDeclarationStateObject}
*/
public ListIterable<? extends VariableDeclarationStateObject> declarations() {
return fromClause.items();
}
@Override
public IdentificationVariableStateObject findIdentificationVariable(String identificationVariable) {
return fromClause.findIdentificationVariable(identificationVariable);
}
@Override
public DeclarationStateObject getDeclaration() {
return fromClause;
}
@Override
public AbstractSelectStatement getExpression() {
return (AbstractSelectStatement) super.getExpression();
}
/**
* Returns the state object representing the <code><b>FROM</b></code> clause.
*
* @return The state object representing the <code><b>FROM</b></code> clause, which is never
* <code>null</code>
*/
public AbstractFromClauseStateObject getFromClause() {
return fromClause;
}
/**
* Returns the state object representing the <code><b>GROUP BY</b></code> clause.
*
* @return Either the actual state object representing the <code><b>GROUP BY</b></code> clause or
* <code>null</code> if it's not present
*/
public GroupByClauseStateObject getGroupByClause() {
return groupByClause;
}
/**
* Returns the state object representing the <code><b>HAVING</b></code> clause.
*
* @return Either the actual state object representing the <code><b>HAVING</b></code> clause or
* <code>null</code> if it's not present
*/
public HavingClauseStateObject getHavingClause() {
return havingClause;
}
/**
* Returns the state object representing the <code><b>SELECT</b></code> clause.
*
* @return Either the actual state object representing the <code><b>SELECT</b></code> clause,
* which is never <code>null</code>
*/
public AbstractSelectClauseStateObject getSelectClause() {
return selectClause;
}
/**
* Returns the state object representing the <code><b>WHERE</b></code> clause.
*
* @return Either the actual state object representing the <code><b>WHERE</b></code> clause or
* the <code>null</code> state object since <code>null</code> is never returned
*/
public WhereClauseStateObject getWhereClause() {
return whereClause;
}
/**
* Returns the state object representing the <code><b>GROUP BY</b></code> clause.
*
* @return Either the actual state object representing the <code><b>GROUP BY</b></code> clause or
* <code>null</code> if it's not present
*/
public boolean hasGroupByClause() {
return groupByClause != null;
}
/**
* Returns the state object representing the <code><b>HAVING</b></code> clause.
*
* @return Either the actual state object representing the <code><b>HAVING</b></code> clause or
* <code>null</code> if it's not present
*/
public boolean hasHavingClause() {
return havingClause != null;
}
/**
* Returns the state object representing the <code><b>WHERE</b></code> clause.
*
* @return Either the actual state object representing the <code><b>WHERE</b></code> clause or
* <code>null</code> if it's not present
*/
public boolean hasWhereClause() {
return whereClause != null;
}
/**
* Returns the {@link IdentificationVariableStateObject IdentificationVariableStateObjects}
* holding onto the identification variables, which are the variables defined in the
* <code><b>FROM</b></code> clause.
* <p>
* Example:
* <ul>
* <li><code>Employee e</code>; <i>e</i> is returned</li>
* <li><code>IN (e.employees) AS emps</code>; <i>emps</i> is returned</li>
* <li><code>Manager m JOIN m.employees emps</code>; <i>m</i> and <i>emps</i> are returned</li>
* </ul>
*
* @return The list of {@link IdentificationVariableStateObject IdentificationVariableStateObjects}
*/
public Iterable<IdentificationVariableStateObject> identificationVariables() {
return fromClause.identificationVariables();
}
@Override
protected void initialize() {
super.initialize();
selectClause = buildSelectClause();
fromClause = buildFromClause();
}
@Override
public boolean isEquivalent(StateObject stateObject) {
if (super.isEquivalent(stateObject)) {
AbstractSelectStatementStateObject select = (AbstractSelectStatementStateObject) stateObject;
return areEquivalent(selectClause, select.selectClause) &&
areEquivalent(fromClause, select.fromClause) &&
areEquivalent(fromClause, select.fromClause) &&
areEquivalent(whereClause, select.whereClause) &&
areEquivalent(groupByClause, select.groupByClause) &&
areEquivalent(havingClause, select.havingClause);
}
return false;
}
/**
* Parses the given JPQL fragment and create the select item. For the top-level query, the
* fragment can contain several select items but for a subquery, it can represent only one.
*
* @param jpqlFragment The portion of the query representing one or several select items
*/
public void parseSelect(String jpqlFragment) {
getSelectClause().parse(jpqlFragment);
}
/**
* Removes the <code><b>GROUP BY</b></code> clause.
*/
public void removeGroupByClause() {
setGroupByClause(null);
}
/**
* Removes the <code><b>HAVING</b></code> clause.
*/
public void removeHavingClause() {
setHavingClause(null);
}
/**
* Removes the <code><b>WHERE</b></code> clause.
*/
public void removeWhereClause() {
setWhereClause(null);
}
private void setGroupByClause(GroupByClauseStateObject groupByClause) {
GroupByClauseStateObject oldGroupByClause = this.groupByClause;
this.groupByClause = groupByClause;
firePropertyChanged(GROUP_BY_CLAUSE_PROPERTY, oldGroupByClause, groupByClause);
}
private void setHavingClause(HavingClauseStateObject havingClause) {
HavingClauseStateObject oldHavingClause = this.havingClause;
this.havingClause = havingClause;
firePropertyChanged(HAVING_CLAUSE_PROPERTY, oldHavingClause, havingClause);
}
private void setWhereClause(WhereClauseStateObject whereClause) {
WhereClauseStateObject oldWhereClause = this.whereClause;
this.whereClause = whereClause;
firePropertyChanged(WHERE_CLAUSE_PROPERTY, oldWhereClause, whereClause);
}
/**
* Either adds or removes the state object representing the <code><b>GROUP BY</b></code> clause.
*/
public void toggleGroupByClause() {
if (hasGroupByClause()) {
removeGroupByClause();
}
else {
addGroupByClause();
}
}
/**
* Either adds or removes the state object representing the <code><b>HAVING</b></code> clause.
*/
public void toggleHavingClause() {
if (hasHavingClause()) {
removeHavingClause();
}
else {
addHavingClause();
}
}
/**
* Either adds or removes the state object representing the <code><b>WHERE</b></code> clause.
*/
public void toggleWhereClause() {
if (hasWhereClause()) {
removeWhereClause();
}
else {
addWhereClause();
}
}
@Override
protected void toTextInternal(Appendable writer) throws IOException {
selectClause.toString(writer);
writer.append(SPACE);
fromClause.toString(writer);
if (whereClause != null) {
writer.append(SPACE);
whereClause.toString(writer);
}
if (groupByClause != null) {
writer.append(SPACE);
groupByClause.toString(writer);
}
if (havingClause != null) {
writer.append(SPACE);
havingClause.toString(writer);
}
}
}