blob: f41d5d5a59cfee7294dcebc5a776ff0d9550e6d1 [file] [log] [blame]
/*
* Copyright (c) 2006, 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.parser;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* The <b>TRIM</b> 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
* Character). If a trim specification is not provided, <b>BOTH</b> is assumed. The <b>TRIM</b>
* function returns the trimmed string.
* <p>
* JPA 1.0, 2.0:
* <div><b>BNF:</b> <code>expression ::= TRIM([[trim_specification] [trim_character] FROM] string_primary)</code></div>
*
* <div><b>BNF:</b> <code>trim_character ::= string_literal | input_parameter</code></div>
* <p>
* JPA 2.1:
* <div><b>BNF:</b> <code>expression ::= TRIM([[trim_specification] [trim_character] FROM] string_expression)</code></div>
*
* <div><b>BNF:</b> <code>trim_character ::= string_literal | input_parameter</code><p></p></div>
*
* <div>Example: <code><b>UPDATE</b> Student st <b>SET</b> st.sname=TRIM(st.sname)</code><p></p></div>
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public final class TrimExpression extends AbstractSingleEncapsulatedExpression {
/**
* The actual <b>FROM</b> identifier found in the string representation of the JPQL query.
*/
private String fromIdentifier;
/**
* Determines whether the identifier <b>FROM</b> was part of the query.
*/
private boolean hasFrom;
/**
* Determines whether a space was parsed after the identifier <b>FROM</b>.
*/
private boolean hasSpaceAfterFrom;
/**
* Determines whether a space was parsed after the trim specification.
*/
private boolean hasSpaceAfterSpecification;
/**
* Determines whether a space was parsed after the trim character.
*/
private boolean hasSpaceAfterTrimCharacter;
/**
* The specification specifies how to trim the string.
*/
private Specification specification;
/**
* The actual trim specification identifier found in the string representation of the JPQL query.
*/
private String specificationIdentifier;
/**
* The character used for trimming the string.
*/
private AbstractExpression trimCharacter;
/**
* Creates a new <code>TrimExpression</code>.
*
* @param parent The parent of this expression
*/
public TrimExpression(AbstractExpression parent) {
super(parent, TRIM);
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getTrimCharacter().accept(visitor);
super.acceptChildren(visitor);
}
@Override
protected void addOrderedEncapsulatedExpressionTo(List<Expression> children) {
// Trim specification
if (hasSpecification()) {
children.add(buildStringExpression(specification.name()));
}
if (hasSpaceAfterSpecification) {
children.add(buildStringExpression(SPACE));
}
// Trim character
if (hasTrimCharacter()) {
children.add(trimCharacter);
}
if (hasSpaceAfterTrimCharacter) {
children.add(buildStringExpression(SPACE));
}
// 'FROM'
if (hasFrom) {
children.add(buildStringExpression(FROM));
}
if (hasSpaceAfterFrom) {
children.add(buildStringExpression(SPACE));
}
// String primary
super.addOrderedEncapsulatedExpressionTo(children);
}
@Override
public String getEncapsulatedExpressionQueryBNFId() {
return StringPrimaryBNF.ID;
}
/**
* Returns the actual <b>FROM</b> identifier found in the string representation of the JPQL
* query, which has the actual case that was used.
*
* @return The <b>FROM</b> identifier that was actually parsed, or an empty string if it was not
* parsed
*/
public String getActualFromIdentifier() {
return (fromIdentifier != null) ? fromIdentifier : ExpressionTools.EMPTY_STRING;
}
/**
* Returns the actual specification identifier found in the string representation of the JPQL
* query, which has the actual case that was used.
*
* @return The specification identifier that was actually parsed, or an empty string if it was
* not parsed
*/
public String getActualSpecificationIdentifier() {
return (specificationIdentifier != null) ? specificationIdentifier : ExpressionTools.EMPTY_STRING;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(FunctionsReturningStringsBNF.ID);
}
/**
* Returns the specification which specifies how to trim the string.
*
* @return One of the available choices for trimming the string
*/
public Specification getSpecification() {
return specification;
}
/**
* Returns the character used for trimming the string.
*
* @return The character, if one was parsed, that will be used to trim the string. If the
* character was not specified, then '\0' is the character
*/
public Expression getTrimCharacter() {
if (trimCharacter == null) {
trimCharacter = buildNullExpression();
}
return trimCharacter;
}
@Override
public boolean hasEncapsulatedExpression() {
return hasSpecification() || hasTrimCharacter() || hasFrom || hasExpression();
}
/**
* Determines whether the identifier <b>FROM</b> was part of the query.
*
* @return <code>true</code> if the identifier <b>FROM</b> was parsed; <code>false</code> otherwise
*/
public boolean hasFrom() {
return hasFrom;
}
/**
* Determines whether a whitespace was found after <b>FROM</b>.
*
* @return <code>true</code> if there was a whitespace after <b>FROM</b>; <code>false</code> otherwise
*/
public boolean hasSpaceAfterFrom() {
return hasSpaceAfterFrom;
}
/**
* Determines whether a whitespace was found after the way the string is trimmed.
*
* @return <code>true</code> if there was a whitespace after the trim specification;
* <code>false</code> otherwise
*/
public boolean hasSpaceAfterSpecification() {
return hasSpaceAfterSpecification;
}
/**
* Determines whether a whitespace was found after the character used to trim the string.
*
* @return <code>true</code> if there was a whitespace after the trim character;
* <code>false</code> otherwise
*/
public boolean hasSpaceAfterTrimCharacter() {
return hasSpaceAfterTrimCharacter;
}
/**
* 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 &&
!trimCharacter.isNull();
}
@Override
protected void parseEncapsulatedExpression(WordParser wordParser,
int whitespaceCount,
boolean tolerant) {
// Parse the trim specification
specification = parseTrimSpecification(wordParser);
if (specification != Specification.DEFAULT) {
specificationIdentifier = wordParser.moveForward(specification.name().length());
hasSpaceAfterSpecification = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the trim character
if (!wordParser.startsWithIdentifier(FROM)) {
// Make sure to parse with the encapsulated expression because if it is not
// the trim character but the string primary, then it has to be parsed correctly
trimCharacter = parse(wordParser, getEncapsulatedExpressionQueryBNFId(), tolerant);
}
if (hasTrimCharacter()) {
hasSpaceAfterTrimCharacter = wordParser.skipLeadingWhitespace() > 0;
}
// Parse 'FROM'
hasFrom = wordParser.startsWithIdentifier(FROM);
if (hasFrom) {
fromIdentifier = wordParser.moveForward(FROM);
hasSpaceAfterFrom = wordParser.skipLeadingWhitespace() > 0;
}
// Parse the string primary
super.parseEncapsulatedExpression(wordParser, whitespaceCount, tolerant);
// The trim character is actually the string primary
if (!hasFrom &&
!hasExpression() &&
hasTrimCharacter()) {
setExpression(trimCharacter);
trimCharacter = null;
if (hasSpaceAfterTrimCharacter) {
hasSpaceAfterTrimCharacter = false;
wordParser.moveBackward(1);
}
}
}
private Specification parseTrimSpecification(WordParser wordParser) {
if (wordParser.startsWithIdentifier(BOTH)) {
return Specification.BOTH;
}
if (wordParser.startsWithIdentifier(LEADING)) {
return Specification.LEADING;
}
if (wordParser.startsWithIdentifier(TRAILING)) {
return Specification.TRAILING;
}
return Specification.DEFAULT;
}
@Override
protected void toParsedTextEncapsulatedExpression(StringBuilder writer, boolean actual) {
// Trim specification
if (hasSpecification()) {
writer.append(specification);
}
if (hasSpaceAfterSpecification) {
writer.append(SPACE);
}
// Trim character
if (hasTrimCharacter()) {
trimCharacter.toParsedText(writer, actual);
}
if (hasSpaceAfterTrimCharacter) {
writer.append(SPACE);
}
// 'FROM'
if (hasFrom) {
writer.append(actual ? fromIdentifier : FROM);
}
if (hasSpaceAfterFrom) {
writer.append(SPACE);
}
// String primary
super.toParsedTextEncapsulatedExpression(writer, actual);
}
/**
* The possible ways to trim the string.
*/
public enum Specification {
/**
* The leading and trailing parts of the string will be trimmed.
*/
BOTH(Expression.BOTH),
/**
* Used when the trim specification is not specified, by default it means the leading and
* trailing parts of the string will be trimmed.
*/
DEFAULT(ExpressionTools.EMPTY_STRING),
/**
* Only the leading part of the string will be trimmed.
*/
LEADING(Expression.LEADING),
/**
* Only the trailing part of the string will be trimmed.
*/
TRAILING(Expression.TRAILING);
/**
* The actual constant associated with the constant.
*/
private String value;
private Specification(String value) {
this.value = value;
}
/**
* Returns the actual identifier associated with the constant.
*
* @return The identifier associated with the constant or an empty string for {@link #DEFAULT}
*/
public String getValue() {
return value;
}
}
}