org.eclipse.persistence.jpa.jpql.parser.TrimExpression Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* 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 TRIM 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 trim_character
* is a single-character string literal or a character-valued input parameter (i.e., char or
* Character). If a trim specification is not provided, BOTH is assumed. The TRIM
* function returns the trimmed string.
*
* JPA 1.0, 2.0:
*
BNF: expression ::= TRIM([[trim_specification] [trim_character] FROM] string_primary)
*
* BNF: trim_character ::= string_literal | input_parameter
*
* JPA 2.1:
* BNF: expression ::= TRIM([[trim_specification] [trim_character] FROM] string_expression)
*
* BNF: trim_character ::= string_literal | input_parameter
*
* Example: UPDATE Student st SET st.sname=TRIM(st.sname)
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
public final class TrimExpression extends AbstractSingleEncapsulatedExpression {
/**
* The actual FROM identifier found in the string representation of the JPQL query.
*/
private String fromIdentifier;
/**
* Determines whether the identifier FROM was part of the query.
*/
private boolean hasFrom;
/**
* Determines whether a space was parsed after the identifier FROM.
*/
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 TrimExpression
.
*
* @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 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 FROM identifier found in the string representation of the JPQL
* query, which has the actual case that was used.
*
* @return The FROM 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 FROM was part of the query.
*
* @return true
if the identifier FROM was parsed; false
otherwise
*/
public boolean hasFrom() {
return hasFrom;
}
/**
* Determines whether a whitespace was found after FROM.
*
* @return true
if there was a whitespace after FROM; false
otherwise
*/
public boolean hasSpaceAfterFrom() {
return hasSpaceAfterFrom;
}
/**
* Determines whether a whitespace was found after the way the string is trimmed.
*
* @return true
if there was a whitespace after the trim specification;
* false
otherwise
*/
public boolean hasSpaceAfterSpecification() {
return hasSpaceAfterSpecification;
}
/**
* Determines whether a whitespace was found after the character used to trim the string.
*
* @return true
if there was a whitespace after the trim character;
* false
otherwise
*/
public boolean hasSpaceAfterTrimCharacter() {
return hasSpaceAfterTrimCharacter;
}
/**
* Determines whether the way the trim is trimmed was parsed.
*
* @return true
if the query contained the way the trim needs to
* be trimmed; false
otherwise
*/
public boolean hasSpecification() {
return (specification != Specification.DEFAULT);
}
/**
* Determines whether the character used to trim the string was specified.
*
* @return true
if the character used for trimming was specified; false
* 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;
}
}
}