| /* |
| * 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); |
| } |
| } |
| } |