blob: 455a8602e9df0ea8a1640a37c9947ed4efe7b9a0 [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.ConditionalExpressionBNF;
import org.eclipse.persistence.jpa.jpql.tools.model.IConditionalExpressionStateObjectBuilder;
import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*;
/**
* Conditional expressions are composed of other conditional expressions, comparison operations,
* logical operations, path expressions that evaluate to boolean values, boolean literals, and
* boolean input parameters. Arithmetic expressions can be used in comparison expressions.
* Arithmetic expressions are composed of other arithmetic expressions, arithmetic operations, path
* expressions that evaluate to numeric values, numeric literals, and numeric input parameters.
* Arithmetic operations use numeric promotion. Standard bracketing () for ordering expression
* evaluation is supported.
*
* <div><p><b>BNF:</b> <code>expression ::= identifier conditional_expression</code></p></div>
*
* @see org.eclipse.persistence.jpa.jpql.parser.AbstractConditionalClause AbstractConditionalClause
*
* @version 2.4
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public abstract class AbstractConditionalClauseStateObject extends AbstractStateObject {
/**
* The builder is cached during the creation of the conditional expression.
*/
private IConditionalExpressionStateObjectBuilder builder;
/**
* The state object representing the composition of the conditional expressions.
*/
private StateObject conditionalStateObject;
/**
* Notifies the conditional expression property has changed.
*/
public static final String CONDITIONAL_STATE_OBJECT_PROPERTY = "conditionalStateObject";
/**
* Creates a new <code>AbstractConditionalClauseStateObject</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 AbstractConditionalClauseStateObject(StateObject parent) {
super(parent);
}
/**
* Creates a new <code>AbstractConditionalClauseStateObject</code>.
*
* @param parent The parent of this state object, which cannot be <code>null</code>
* @param conditionalStateObject The {@link StateObject} representing the conditional expression
* @exception NullPointerException The given parent cannot be <code>null</code>
*/
protected AbstractConditionalClauseStateObject(StateObject parent,
StateObject conditionalStateObject) {
super(parent);
this.conditionalStateObject = parent(conditionalStateObject);
}
@Override
protected void addChildren(List<StateObject> children) {
super.addChildren(children);
if (conditionalStateObject != null) {
children.add(conditionalStateObject);
}
}
/**
* Parses the given JPQL fragment as the right side of an <code><b>AND</b></code> expression. The
* current conditional expression will become the left side of the <code><b>AND</b></code>
* expression.
*
* @param jpqlFragment The portion of the query representing the right side of the
* <code><b>AND</b></code> expression
* @return The newly created {@link AndExpressionStateObject}
*/
public AndExpressionStateObject andParse(String jpqlFragment) {
StateObject stateObject = buildStateObject(jpqlFragment, ConditionalExpressionBNF.ID);
// Make sure the current conditional expression is encapsulated if it's an OR expression in
// order to preserve logical operator precedence.
// Example: A or B and we're adding C, it has to become (A or B) and C
if (shouldEncapsulateORExpression(conditionalStateObject)) {
conditionalStateObject = new SubExpressionStateObject(this, conditionalStateObject);
}
// Make sure the right side of the AND expression is encapsulated in order to preserve logical
// operator precedence in the case it's an OR expression.
// Example: A and we're adding B or C, it has to become A and (B or C)
if (shouldEncapsulateORExpression(stateObject)) {
stateObject = new SubExpressionStateObject(this, stateObject);
}
AndExpressionStateObject andStateObject = new AndExpressionStateObject(
this,
conditionalStateObject,
stateObject
);
setConditional(andStateObject);
return andStateObject;
}
/**
* Creates and returns a new {@link IConditionalExpressionStateObjectBuilder} that can be used to
* programmatically create a conditional expression and once the expression is complete,
* {@link IConditionalExpressionStateObjectBuilder#commit()} will push the {@link StateObject}
* representation of that expression as this clause's conditional expression.
*
* @return A new builder that can be used to quickly create a conditional expression
*/
public IConditionalExpressionStateObjectBuilder getBuilder() {
if (builder == null) {
builder = getQueryBuilder().buildStateObjectBuilder(this);
}
return builder;
}
/**
* Returns the state object representing the composition of the conditional expressions.
*
* @return The actual conditional expression
*/
public StateObject getConditional() {
return conditionalStateObject;
}
/**
* Returns the JPQL identifier of this clause.
*
* @return The JPQL identifier of this conditional clause
*/
public abstract String getIdentifier();
/**
* Determines whether the {@link StateObject} representing the conditional expression is present
* or not.
*
* @return <code>true</code> if the conditional expression is not <code>null</code>;
* <code>false</code> otherwise
*/
public boolean hasConditional() {
return conditionalStateObject != null;
}
@Override
public boolean isEquivalent(StateObject stateObject) {
if (super.isEquivalent(stateObject)) {
AbstractConditionalClauseStateObject clause = (AbstractConditionalClauseStateObject) stateObject;
return areEquivalent(conditionalStateObject, clause.conditionalStateObject);
}
return false;
}
/**
* Parses the given JPQL fragment as the right side of an <code><b>OR</b></code> expression. The
* current conditional expression will become the left side of the <code><b>OR</b></code>
* expression.
*
* @param jpqlFragment The portion of the query representing the right side of the
* <code><b>OR</b></code> expression
* @return The newly created {@link OrExpressionStateObject}
*/
public OrExpressionStateObject orParse(String jpqlFragment) {
OrExpressionStateObject orStateObject = new OrExpressionStateObject(
this,
conditionalStateObject,
buildStateObject(jpqlFragment, ConditionalExpressionBNF.ID)
);
setConditional(orStateObject);
return orStateObject;
}
/**
* Parses the given JPQL fragment, which represents a conditional expression, and creates the
* {@link StateObject}.
*
* @param jpqlFragment The portion of the query representing a conditional expression
*/
public void parse(String jpqlFragment) {
StateObject stateObject = buildStateObject(jpqlFragment, ConditionalExpressionBNF.ID);
setConditional(stateObject);
}
/**
* Sets the given {@link StateObject} to be the conditional expression of this clause.
*
* @param conditionalStateObject The new {@link StateObject} representing the conditional
* expression
*/
public void setConditional(StateObject conditionalStateObject) {
builder = null;
StateObject oldConditionalStateObject = this.conditionalStateObject;
this.conditionalStateObject = parent(conditionalStateObject);
firePropertyChanged(CONDITIONAL_STATE_OBJECT_PROPERTY, oldConditionalStateObject, conditionalStateObject);
}
protected boolean shouldEncapsulateORExpression(StateObject stateObject) {
if (stateObject == null) {
return false;
}
final boolean[] encapsulate = { false };
StateObjectVisitor visitor = new AbstractStateObjectVisitor() {
@Override
public void visit(OrExpressionStateObject stateObject) {
encapsulate[0] = true;
}
};
stateObject.accept(visitor);
return encapsulate[0];
}
@Override
protected void toTextInternal(Appendable writer) throws IOException {
writer.append(getIdentifier());
if (conditionalStateObject != null) {
writer.append(SPACE);
conditionalStateObject.toString(writer);
}
}
}