org.eclipse.persistence.jpa.jpql.parser.OrderByItem Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2006, 2018 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);
}
/**
* {@inheritDoc}
*/
public void accept(ExpressionVisitor visitor) {
visitor.visit(this);
}
/**
* {@inheritDoc}
*/
public void acceptChildren(ExpressionVisitor visitor) {
getExpression().accept(visitor);
}
/**
* {@inheritDoc}
*/
@Override
protected void addChildrenTo(Collection children) {
children.add(getExpression());
}
/**
* {@inheritDoc}
*/
@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));
}
}
/**
* {@inheritDoc}
*/
@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;
}
/**
* {@inheritDoc}
*/
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;
}
/**
* {@inheritDoc}
*/
@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);
}
/**
* {@inheritDoc}
*/
@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;
}
}
/**
* {@inheritDoc}
*/
@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
*/
private 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;
}
/**
* {@inheritDoc}
*/
@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
}
}