org.eclipse.persistence.jpa.jpql.parser.OrderByItem 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, 2024 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.Collection;
import java.util.List;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.WordParser;
/**
* An orderby_item must be one of the following:
*
* - A {@link StateFieldPathExpression state_field_path_expression} that evaluates to an orderable
* state field of an entity or embeddable class abstract schema type designated in the SELECT clause
* by one of the following:
*
* - A general_identification_variable
*
- A single_valued_object_path_expression
*
* - A {@link StateFieldPathExpression state_field_path_expression} that evaluates to the same
* state field of the same entity or embeddable abstract schema type as a {@link StateFieldPathExpression
* state_field_path_expression} in the SELECT clause
*
- A {@link ResultVariable result_variable} that refers to an orderable item in the SELECT
* clause for which the same {@link ResultVariable result_variable} has been specified. This may be
* the result of an aggregate_expression, a
scalar_expression
, or a {@link
* StateFieldPathExpression state_field_path_expression} in the SELECT clause.
*
*
* The keyword ASC specifies that ascending ordering be used for the associated orderby_item;
* the keyword DESC specifies that descending ordering be used. Ascending ordering is the
* default.
*
* The keyword NULLS FIRST specifies that nulls first ordering be used for the associated orderby_item;
* the keyword NULLS LAST specifies that nulls last ordering be used. Ascending ordering is the
* default.
*
* JPA 1.0:
* BNF: orderby_item ::= state_field_path_expression [ ASC | DESC ]
*
* JPA 2.0
* BNF: orderby_item ::= state_field_path_expression | result_variable [ ASC | DESC ]
*
* EclipseLink 2.4:
* BNF: orderby_item ::= state_field_path_expression | result_variable [ ASC | DESC ] [ NULLS FIRST | NULLS LAST ]
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public final class OrderByItem extends AbstractExpression {
/**
* The {@link Expression} representing the order by expression.
*/
private AbstractExpression expression;
/**
* The actual 'FIRST' identifier found in the string representation of the JPQL query.
*/
private String firstIdentifier;
/**
* Determines whether a whitespace was parsed after the order by expression.
*/
private boolean hasSpaceAfterExpression;
/**
* Determines whether a whitespace was parsed after NULLS
.
*/
private boolean hasSpaceAfterNulls;
/**
* Determines whether a whitespace was parsed after the ordering.
*/
private boolean hasSpaceAfterOrdering;
/**
* The actual 'LAST' identifier found in the string representation of the JPQL query.
*/
private String lastIdentifier;
/**
* The keyword NULLS FIRST specifies ordering null first; the keyword NULLS LAST
* specifies ordering nulls last.
*/
private NullOrdering nullOrdering;
/**
* The actual 'NULLS' identifier found in the string representation of the JPQL query.
*/
private String nullsIdentifier;
/**
* The keyword ASC specifies that ascending ordering be used; the keyword DESC
* specifies that descending ordering be used. Ascending ordering is the default.
*/
private Ordering ordering;
/**
* The actual ordering identifier found in the string representation of the JPQL query.
*/
private String orderingIdentifier;
/**
* Creates a new OrderByItem
.
*
* @param parent The parent of this expression
*/
public OrderByItem(AbstractExpression parent) {
super(parent);
}
@Override
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
@Override
public void acceptChildren(ExpressionVisitor visitor) {
getExpression().accept(visitor);
}
@Override
protected void addChildrenTo(Collection children) {
children.add(getExpression());
}
@Override
protected void addOrderedChildrenTo(List children) {
// Order By expression
if (expression != null) {
children.add(expression);
}
if (hasSpaceAfterExpression) {
children.add(buildStringExpression(SPACE));
}
// Ordering type
if (ordering != Ordering.DEFAULT) {
children.add(buildStringExpression(ordering.toString()));
}
if (hasSpaceAfterOrdering) {
children.add(buildStringExpression(SPACE));
}
// 'NULLS'
if (nullsIdentifier != null) {
children.add(buildStringExpression(NULLS));
}
if (hasSpaceAfterNulls) {
children.add(buildStringExpression(SPACE));
}
// 'FIRST'
if (firstIdentifier != null) {
children.add(buildStringExpression(FIRST));
}
// 'LAST'
else if (lastIdentifier != null) {
children.add(buildStringExpression(LAST));
}
}
@Override
public JPQLQueryBNF findQueryBNF(Expression expression) {
if ((this.expression != null) && this.expression.isAncestor(expression)) {
return getQueryBNF(InternalOrderByItemBNF.ID);
}
return super.findQueryBNF(expression);
}
/**
* Returns the actual null
ordering identifier found in the string representation of
* the JPQL query, which has the actual case that was used.
*
* @return The null
ordering identifier that was actually parsed, if one was present,
* otherwise an empty string is returned
*/
public String getActualNullOrdering() {
// NULLS FIRST
if ((nullsIdentifier != null) && (firstIdentifier != null)) {
return nullsIdentifier + SPACE + firstIdentifier;
}
// NULLS LAST
if ((nullsIdentifier != null) && (lastIdentifier != null)) {
return nullsIdentifier + SPACE + lastIdentifier;
}
if (nullsIdentifier != null) {
return nullsIdentifier;
}
if (firstIdentifier != null) {
return firstIdentifier;
}
else if (lastIdentifier != null) {
return lastIdentifier;
}
return ExpressionTools.EMPTY_STRING;
}
/**
* Returns the actual ordering identifier found in the string representation of the JPQL query,
* which has the actual case that was used.
*
* @return The ordering identifier that was actually parsed, if one was present, otherwise an
* empty string is returned
*/
public String getActualOrdering() {
return (orderingIdentifier != null) ? orderingIdentifier : ExpressionTools.EMPTY_STRING;
}
/**
* Returns the {@link Expression} that represents the order by expression.
*
* @return The expression that was parsed representing the order by expression
*/
public Expression getExpression() {
if (expression == null) {
expression = buildNullExpression();
}
return expression;
}
/**
* Returns the enum constant representing the null ordering type.
*
* @return The constant representing the null ordering, in the case the ordering was not parsed,
* then {@link NullOrdering#DEFAULT} is returned
*/
public NullOrdering getNullOrdering() {
return nullOrdering;
}
/**
* Returns the enum constant representing the ordering type.
*
* @return The constant representing the ordering, in the case the ordering was not parsed, then
* {@link Ordering#DEFAULT} is returned
*/
public Ordering getOrdering() {
return ordering;
}
@Override
public JPQLQueryBNF getQueryBNF() {
return getQueryBNF(OrderByItemBNF.ID);
}
/**
* Determines whether the order by expression was parsed.
*
* @return true
if the order by expression was parsed; false
otherwise
*/
public boolean hasExpression() {
return expression != null &&
!expression.isNull();
}
/**
* Determines whether the NULLS
identifier was parsed.
*
* @return true
if the NULLS
identifier was parsed;
* false
otherwise
*/
public boolean hasNulls() {
return nullsIdentifier != null;
}
/**
* Determines whether ASC
or DESC
was parsed.
*
* @return true
if the ordering status was parsed; false
otherwise
* @since 2.5
*/
public boolean hasOrdering() {
return ordering != Ordering.DEFAULT;
}
/**
* Determines whether a whitespace was parsed after the order by expression.
*
* @return true
if there was a whitespace after the order by expression;
* false
otherwise
*/
public boolean hasSpaceAfterExpression() {
return hasSpaceAfterExpression;
}
/**
* Determines whether a whitespace was parsed after the NULLS
identifier. If the
* composite identifier was fully parsed - NULLS FIRST
or NULLS LAST
-
* then this is always true
, but if only NULLS
was parsed, then this
* can be useful to determine if there was a space after.
*
* @return true
if there was a whitespace after the NULLS
identifier;
* false
otherwise
*/
public boolean hasSpaceAfterNulls() {
return hasSpaceAfterNulls;
}
/**
* Determines whether a whitespace was parsed after the ordering.
*
* @return true
if there was a whitespace after the ordering;
* false
otherwise
*/
public boolean hasSpaceAfterOrdering() {
return hasSpaceAfterOrdering;
}
/**
* Determines whether the ordering was specified as being ascendant.
*
* @return true
if ASC was parsed; false
otherwise
*/
public boolean isAscending() {
return ordering == Ordering.ASC;
}
/**
* Determines whether the ordering was not specified.
*
* @return true
if no ordering was parsed; false
otherwise
*/
public boolean isDefault() {
return ordering == Ordering.DEFAULT;
}
/**
* Determines whether the ordering was specified as being descendant.
*
* @return true
if DESC was parsed; false
otherwise
*/
public boolean isDescending() {
return ordering == Ordering.DESC;
}
/**
* Determines whether the ordering was specified as being nulls first.
*
* @return true
if NULLS FIRST was parsed; false
otherwise
*/
public boolean isNullsFirst() {
return nullOrdering == NullOrdering.NULLS_FIRST;
}
/**
* Determines whether the ordering was specified as being nulls first.
*
* @return true
if NULLS LAST was parsed; false
otherwise
*/
public boolean isNullsLast() {
return nullOrdering == NullOrdering.NULLS_LAST;
}
@Override
protected boolean isParsingComplete(WordParser wordParser, String word, Expression expression) {
return word.equalsIgnoreCase(ASC) ||
word.equalsIgnoreCase(DESC) ||
word.equalsIgnoreCase("NULLS") ||
super.isParsingComplete(wordParser, word, expression);
}
@Override
protected void parse(WordParser wordParser, boolean tolerant) {
// Parse the state field path expression
expression = parse(wordParser, InternalOrderByItemBNF.ID, tolerant);
hasSpaceAfterExpression = wordParser.skipLeadingWhitespace() > 0;
// Parse ASC/DESC
if (!wordParser.isTail()) {
String word = wordParser.word();
// Parse 'ASC'
if (word.equalsIgnoreCase(ASC)) {
ordering = Ordering.ASC;
orderingIdentifier = wordParser.moveForward(ASC.length());
}
// Parse 'DESC'
else if (word.equalsIgnoreCase(DESC)) {
ordering = Ordering.DESC;
orderingIdentifier = wordParser.moveForward(DESC.length());
}
else {
ordering = Ordering.DEFAULT;
}
}
else {
ordering = Ordering.DEFAULT;
}
// Parse NULLS FIRST/NULLS LAST
if (!wordParser.isTail()) {
int count = wordParser.skipLeadingWhitespace();
hasSpaceAfterOrdering = (count > 0);
// Parse 'NULLS'
if (wordParser.startsWithIdentifier(NULLS)) {
nullsIdentifier = wordParser.moveForward(NULLS);
hasSpaceAfterNulls = wordParser.skipLeadingWhitespace() > 0;
}
// Parse 'FIRT'
if (wordParser.startsWithIdentifier(FIRST)) {
firstIdentifier = wordParser.moveForward(FIRST);
}
// Parse 'LAST'
else if (wordParser.startsWithIdentifier(LAST)) {
lastIdentifier = wordParser.moveForward(LAST);
}
if ((nullsIdentifier != null) && (firstIdentifier != null)) {
nullOrdering = NullOrdering.NULLS_FIRST;
}
else if ((nullsIdentifier != null) && (lastIdentifier != null)) {
nullOrdering = NullOrdering.NULLS_LAST;
}
else {
nullOrdering = NullOrdering.DEFAULT;
}
}
else {
nullOrdering = NullOrdering.DEFAULT;
}
}
@Override
protected void toParsedText(StringBuilder writer, boolean actual) {
// Order By expression
if (expression != null) {
expression.toParsedText(writer, actual);
}
if (hasSpaceAfterExpression) {
writer.append(SPACE);
}
// Ordering type
if (ordering != Ordering.DEFAULT) {
writer.append(actual ? orderingIdentifier : ordering.name());
}
if (hasSpaceAfterOrdering) {
writer.append(SPACE);
}
// 'NULLS'
if (nullsIdentifier != null) {
writer.append(actual? nullsIdentifier : NULLS);
}
if (hasSpaceAfterNulls) {
writer.append(SPACE);
}
// 'FIRST'
if (firstIdentifier != null) {
writer.append(actual? firstIdentifier : FIRST);
}
// 'LAST'
else if (lastIdentifier != null) {
writer.append(actual? lastIdentifier : LAST);
}
}
/**
* This enumeration lists all the possible choices for ordering nulls in an item.
*/
public enum NullOrdering {
/**
* The constant used when the ordering is not specify.
*/
DEFAULT(ExpressionTools.EMPTY_STRING),
/**
* The constant for 'NULLS FIRST', which tells to order nulls first.
*/
NULLS_FIRST(Expression.NULLS_FIRST),
/**
* The constant for 'NULLS LAST', which tells to order nulls last.
*/
NULLS_LAST(Expression.NULLS_LAST);
/**
* The actual composite identifier.
*/
private String identifier;
/**
* Creates a new NullOrdering
.
*
* @param identifier The actual composite identifier
*/
NullOrdering(String identifier) {
this.identifier = identifier;
}
/**
* Returns the actual JPQL composite identifiers identified by this enum constant.
*
* @return The composite identifiers
*/
public String getIdentifier() {
return identifier;
}
@Override
public String toString() {
return identifier;
}
}
/**
* This enumeration lists all the possible choices for ordering an item.
*/
public enum Ordering {
/**
* The constant for 'ASC', which tells to order the items in ascending ordering.
*/
ASC,
/**
* The constant used when the ordering is not specify, the default is ascending ordering.
*/
DEFAULT,
/**
* The constant for 'DESC', which tells to order the items in descending ordering.
*/
DESC
}
}