| /* |
| * 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 org.eclipse.persistence.jpa.jpql.Assert; |
| import org.eclipse.persistence.jpa.jpql.parser.FunctionsReturningStringsBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.JPQLExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.LiteralBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.StringPrimaryBNF; |
| import org.eclipse.persistence.jpa.jpql.parser.TrimExpression; |
| import org.eclipse.persistence.jpa.jpql.parser.TrimExpression.Specification; |
| import static org.eclipse.persistence.jpa.jpql.parser.AbstractExpression.*; |
| |
| /** |
| * The <code><b>TRIM</b></code> function trims the specified character from a string. If the |
| * character to be trimmed is not specified, it is assumed to be space (or blank). The optional |
| * <code>trim_character</code> is a single-character string literal or a character-valued input |
| * parameter (i.e., char or <code>Character</code>). If a trim specification is not provided, |
| * <code><b>BOTH</b></code> is assumed. The <code><b>TRIM</b></code> function returns the trimmed |
| * string. |
| * |
| * <div><p><b>BNF:</b> <code>expression ::= TRIM([[trim_specification] [trim_character] FROM] string_primary)</code></p></div> |
| * |
| * @see TrimExpression |
| * |
| * @version 2.5 |
| * @since 2.4 |
| * @author Pascal Filion |
| */ |
| @SuppressWarnings({"nls", "unused"}) // unused used for the import statement: see bug 330740 |
| public class TrimExpressionStateObject extends AbstractSingleEncapsulatedExpressionStateObject { |
| |
| /** |
| * Defines the way the string is trimmed. |
| */ |
| private Specification specification; |
| |
| /** |
| * The trim character, if specified. |
| */ |
| private StateObject trimCharacter; |
| |
| /** |
| * Notifies the visibility of the <code><b>FROM</b></code> identifier has changed. |
| */ |
| public static final String HAS_FROM_PROPERTY = "hasFrom"; |
| |
| /** |
| * Notifies the specification property has changed. |
| */ |
| public static final String SPECIFICATION_PROPERTY = "specification"; |
| |
| /** |
| * Notify the state object representing the trim character has changed. |
| */ |
| public static final String TRIM_CHARACTER_PROPERTY = "trimCharacterStateObject"; |
| |
| /** |
| * Creates a new <code>TrimExpressionStateObject</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 TrimExpressionStateObject(StateObject parent) { |
| super(parent); |
| } |
| |
| /** |
| * Creates a new <code>TrimExpressionStateObject</code>. |
| * |
| * @param parent The parent of this state object, which cannot be <code>null</code> |
| * @param stateObject The {@link StateObject} representing the encapsulated expression |
| * @param specification Defines the way the string is trimmed, or {@link |
| * org.eclipse.persistence.jpa.jpql.parser.TrimExpression.Specification#DEFAULT |
| * Specification.DEFAULT} when it is not present |
| * @exception NullPointerException The given parent cannot be <code>null</code> |
| */ |
| public TrimExpressionStateObject(StateObject parent, |
| Specification specification, |
| StateObject stateObject) { |
| |
| this(parent, specification, null, stateObject); |
| } |
| |
| /** |
| * Creates a new <code>TrimExpressionStateObject</code>. |
| * |
| * @param parent The parent of this state object, which cannot be <code>null</code> |
| * @param specification Defines the way the string is trimmed, or {@link |
| * org.eclipse.persistence.jpa.jpql.parser.TrimExpression.Specification#DEFAULT |
| * Specification.DEFAULT} when it is not present |
| * @param trimCharacter The trim character |
| * @param stateObject The {@link StateObject} representing the encapsulated expression |
| * @exception NullPointerException The given parent cannot be <code>null</code> |
| */ |
| public TrimExpressionStateObject(StateObject parent, |
| Specification specification, |
| StateObject trimCharacter, |
| StateObject stateObject) { |
| |
| super(parent, stateObject); |
| this.specification = specification; |
| this.trimCharacter = parent(trimCharacter); |
| } |
| |
| /** |
| * Creates a new <code>TrimExpressionStateObject</code>. |
| * |
| * @param parent The parent of this state object, which cannot be <code>null</code> |
| * @param jpqlFragment The portion of the query representing the encapsulated expression |
| * @exception NullPointerException The given parent cannot be <code>null</code> |
| */ |
| public TrimExpressionStateObject(StateObject parent, String jpqlFragment) { |
| super(parent, jpqlFragment); |
| } |
| |
| @Override |
| public void accept(StateObjectVisitor visitor) { |
| visitor.visit(this); |
| } |
| |
| @Override |
| public TrimExpression getExpression() { |
| return (TrimExpression) super.getExpression(); |
| } |
| |
| @Override |
| public String getIdentifier() { |
| return TRIM; |
| } |
| |
| @Override |
| protected String getQueryBNFId() { |
| return StringPrimaryBNF.ID; |
| } |
| |
| /** |
| * Returns the new trim specification. |
| * |
| * @return The new trim specification; which is never <code>null</code> |
| */ |
| public Specification getSpecification() { |
| return specification; |
| } |
| |
| /** |
| * Returns the {@link StateObject} representing the trim character. |
| * |
| * @return The {@link StateObject} representing the trim character or <code>null</code> if it is |
| * not present |
| */ |
| public StateObject getTrimCharacter() { |
| return trimCharacter; |
| } |
| |
| /** |
| * Determines whether the way the trim is trimmed was parsed. |
| * |
| * @return <code>true</code> if the query contained the way the trim needs to be trimmed; |
| * <code>false</code> otherwise |
| */ |
| public boolean hasSpecification() { |
| return (specification != Specification.DEFAULT); |
| } |
| |
| /** |
| * Determines whether the character used to trim the string was specified. |
| * |
| * @return <code>true</code> if the character used for trimming was specified; <code>false</code> |
| * otherwise |
| */ |
| public boolean hasTrimCharacter() { |
| return trimCharacter != null; |
| } |
| |
| @Override |
| public boolean isEquivalent(StateObject stateObject) { |
| |
| if (super.isEquivalent(stateObject)) { |
| TrimExpressionStateObject trim = (TrimExpressionStateObject) stateObject; |
| return specification == trim.specification && |
| areEquivalent(trimCharacter, trim.trimCharacter); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public void parse(String jpqlFragment) { |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append(TRIM); |
| sb.append(LEFT_PARENTHESIS); |
| sb.append(jpqlFragment); |
| sb.append(RIGHT_PARENTHESIS); |
| |
| JPQLExpression jpqlExpression = new JPQLExpression( |
| sb, |
| getGrammar(), |
| FunctionsReturningStringsBNF.ID, |
| true |
| ); |
| |
| TrimExpression trimExpression = (TrimExpression) jpqlExpression.getQueryStatement(); |
| setSpecification(trimExpression.getSpecification()); |
| parseTrimCharacter(trimExpression.getTrimCharacter().toParsedText()); |
| super.parse(trimExpression.getExpression().toParsedText()); |
| |
| // The trim character is actually the string primary |
| if (!hasStateObject() && hasTrimCharacter()) { |
| setStateObject(new StringLiteralStateObject(this, trimCharacter.toString())); |
| trimCharacter = null; |
| } |
| } |
| |
| /** |
| * Parses the given JPQL fragment, which represents either a single-character string literal or a |
| * character-valued input parameter, the fragment will be parsed and converted into a {@link |
| * StateObject}. |
| * |
| * @param jpqlFragment The portion of the query to parse |
| */ |
| public void parseTrimCharacter(CharSequence jpqlFragment) { |
| StateObject stateObject = buildStateObject(jpqlFragment, LiteralBNF.ID); |
| setTrimCharacter(stateObject); |
| } |
| |
| /** |
| * Removes the trim specification. |
| */ |
| public void removeSpecification() { |
| setSpecification(Specification.DEFAULT); |
| } |
| |
| /** |
| * Removes the trim character if it is defined. |
| */ |
| public void removeTrimCharacter() { |
| setTrimCharacter(null); |
| } |
| |
| /** |
| * Keeps a reference of the {@link TrimExpression 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 TrimExpression parsed object} representing a <code><b>TRIM</b></code> |
| * expression |
| */ |
| public void setExpression(TrimExpression expression) { |
| super.setExpression(expression); |
| } |
| |
| /** |
| * Sets the new trim specification. |
| * |
| * @param specification The new trim specification; <code>null</code> is not valid |
| */ |
| public void setSpecification(Specification specification) { |
| Assert.isNotNull(specification, "The Specification cannot be null"); |
| Specification oldSpecification = this.specification; |
| this.specification = specification; |
| firePropertyChanged(SPECIFICATION_PROPERTY, oldSpecification, specification); |
| } |
| |
| @Override |
| public void setStateObject(StateObject stateObject) { |
| super.setStateObject(stateObject); |
| } |
| |
| /** |
| * Sets the character to trim from the string. If the character to be trimmed is not specified, |
| * it is assumed to be space (or blank). It is a single-character string literal or a character- |
| * valued input parameter (i.e., char or <code>Character</code>). |
| * |
| * @param trimCharacter The trim character or <code>null</code> to remove it |
| */ |
| public void setTrimCharacter(StateObject trimCharacter) { |
| StateObject oldTrimCharacter = this.trimCharacter; |
| this.trimCharacter = parent(trimCharacter); |
| firePropertyChanged(TRIM_CHARACTER_PROPERTY, oldTrimCharacter, trimCharacter); |
| } |
| |
| @Override |
| protected void toTextEncapsulatedExpression(Appendable writer) throws IOException { |
| |
| // Specification |
| if (specification != Specification.DEFAULT) { |
| writer.append(specification.name()); |
| writer.append(SPACE); |
| } |
| |
| // Trim character |
| if (hasTrimCharacter()) { |
| trimCharacter.toString(writer); |
| writer.append(SPACE); |
| } |
| |
| // FROM |
| if ((specification != Specification.DEFAULT) || hasTrimCharacter()) { |
| writer.append(FROM); |
| writer.append(SPACE); |
| } |
| |
| // String primary |
| super.toTextEncapsulatedExpression(writer); |
| } |
| } |