blob: 2f22264cb36208e5351ea36b380de676cdc98d21 [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.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.tools.model.Problem;
import org.eclipse.persistence.jpa.jpql.tools.utility.iterable.SingleElementListIterable;
import org.eclipse.persistence.jpa.jpql.utility.iterable.ListIterable;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
/**
* @version 2.5
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings({"nls", "unused"}) // unused used for the import statement: see bug 330740
public abstract class AbstractRangeVariableDeclarationStateObject extends AbstractStateObject
implements VariableDeclarationStateObject {
/**
* Flag used to determine if the <code><b>AS</b></code> identifier is used or not.
*/
private boolean as;
/**
* The state object representing the identification variable used to reach the "root" object.
*/
private IdentificationVariableStateObject identificationVariable;
/**
* Flag used to determine if the identification variable is optional or not, which is <code>true</code>
* only when this is used by {@link UpdateClauseStateObject} or by {@link DeleteClauseStateObject}.
*/
private boolean identificationVariableOptional;
/**
* The state object holding the "root" for objects which may not be reachable by navigation.
*/
private StateObject rootStateObject;
/**
* Notifies the visibility of the <code><b>AS</b></code> identifier has changed.
*/
public static final String AS_PROPERTY = "as";
/**
* Notifies the identification variable property has changed.
*/
public static final String IDENTIFICATION_VARIABLE_PROPERTY = "identificationVariable";
/**
* Creates a new <code>RangeVariableDeclarationStateObject</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>
*/
public AbstractRangeVariableDeclarationStateObject(AbstractIdentificationVariableDeclarationStateObject parent) {
super(parent);
}
/**
* Creates a new <code>RangeVariableDeclarationStateObject</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>
*/
public AbstractRangeVariableDeclarationStateObject(AbstractIdentificationVariableDeclarationStateObject parent,
String root) {
super(parent);
setRootPath(root);
}
/**
* Creates a new <code>AbstractRangeVariableDeclarationStateObject</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>
*/
public AbstractRangeVariableDeclarationStateObject(AbstractModifyClauseStateObject parent) {
super(parent);
setIdentificationVariableOptional(true);
}
/**
* Makes sure the <code><b>AS</b></code> identifier is specified.
*
* @return This object
*/
public AbstractRangeVariableDeclarationStateObject addAs() {
if (!as) {
setAs(true);
}
return this;
}
@Override
protected void addChildren(List<StateObject> children) {
super.addChildren(children);
children.add(rootStateObject);
children.add(identificationVariable);
}
@Override
protected void addProblems(List<Problem> problems) {
super.addProblems(problems);
// TODO
if (!identificationVariableOptional) {
// validate
}
}
protected abstract StateObject buildRootStateObject();
@Override
public RangeVariableDeclaration getExpression() {
return (RangeVariableDeclaration) super.getExpression();
}
/**
* Returns the identification variable name that is ranging over the abstract schema type.
*
* @return The identification variable name
*/
public String getIdentificationVariable() {
return identificationVariable.getText();
}
/**
* Returns the {@link IdentificationVariableStateObject} holding onto the identification variable.
*
* @return The {@link IdentificationVariableStateObject}, which is never <code>null</code>
*/
public IdentificationVariableStateObject getIdentificationVariableStateObject() {
return identificationVariable;
}
/**
* Returns the "root" object for objects which may not be reachable by navigation.
*
* @return The "root" object
*/
public abstract String getRootPath();
/**
* Returns the {@link StateObject} holding onto the abstract schema name.
*
* @return The {@link StateObject}, which is never <code>null</code>
*/
public StateObject getRootStateObject() {
return rootStateObject;
}
/**
* 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 an identification variable was defined.
*
* @return <code>true</code> if an identification variable is defined; <code>false</code> otherwise
*/
public boolean hasIdentificationVariable() {
return identificationVariable.hasText();
}
@Override
public ListIterable<IdentificationVariableStateObject> identificationVariables() {
return new SingleElementListIterable<IdentificationVariableStateObject>(identificationVariable);
}
@Override
protected void initialize() {
super.initialize();
rootStateObject = buildRootStateObject();
identificationVariable = new IdentificationVariableStateObject(this);
}
@Override
public boolean isEquivalent(StateObject stateObject) {
if (super.isEquivalent(stateObject)) {
AbstractRangeVariableDeclarationStateObject declaration = (AbstractRangeVariableDeclarationStateObject) stateObject;
return as == declaration.as &&
identificationVariableOptional == declaration.identificationVariableOptional &&
identificationVariable.isEquivalent(declaration.identificationVariable) &&
areEquivalent(rootStateObject, declaration.rootStateObject);
}
return false;
}
/**
* Determines whether the identification variable is optional or not. The only time it is
* optional is when this model is used in a modify clause (<code><b>DELETE</b></code> or
* <code><b>UPDATE</b></code>).
*
* @return <code>true</code> if an identification variable is not required; <code>false</code> if
* it is required
*/
public boolean isIdentificationVariableOptional() {
return identificationVariableOptional;
}
/**
* Determines whether this identification variable is virtual, meaning it's not part of the query
* but is required for proper navigability.
*
* @return <code>true</code> if this identification variable was virtually created to fully
* qualify path expression; <code>false</code> if it was parsed
*/
public boolean isIdentificationVariableVirtual() {
return identificationVariable.isVirtual();
}
/**
* 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 RangeVariableDeclaration 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 RangeVariableDeclaration parsed object} representing a range
* variable declaration
*/
public void setExpression(RangeVariableDeclaration expression) {
super.setExpression(expression);
}
/**
* Sets the new identification variable that will range over the "root" object.
*
* @param identificationVariable The new identification variable
*/
public void setIdentificationVariable(String identificationVariable) {
String oldIdentificationVariable = getIdentificationVariable();
this.identificationVariable.setText(identificationVariable);
firePropertyChanged(IDENTIFICATION_VARIABLE_PROPERTY, oldIdentificationVariable, identificationVariable);
}
/**
* Sets whether the identification variable is optional or not. The only time it is optional is
* when this model is used in a modify clause (<code><b>DELETE</b></code> or
* <code><b>UPDATE</b></code>).
*
* @param identificationVariableOptional <code>true</code> if an identification variable is not
* required; <code>false</code> if it is required
*/
protected void setIdentificationVariableOptional(boolean identificationVariableOptional) {
this.identificationVariableOptional = identificationVariableOptional;
}
/**
* Sets the "root" object for objects which may not be reachable by navigation.
*
* @param root The "root" object
*/
public abstract void setRootPath(String root);
/**
* Toggles the usage of the <code><b>AS</b></code> identifier.
*/
public void toggleAs() {
setAs(!hasAs());
}
@Override
protected void toTextInternal(Appendable writer) throws IOException {
rootStateObject.toString(writer);
if (as) {
writer.append(SPACE);
writer.append(AS);
if (!identificationVariable.isVirtual()) {
writer.append(SPACE);
}
}
if (!identificationVariable.isVirtual()) {
if (rootStateObject != null && !as) {
writer.append(SPACE);
}
identificationVariable.toString(writer);
}
}
}