/*
 * Copyright 2008-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.domain;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.data.util.MethodInvocationRecorder;
import org.springframework.data.util.MethodInvocationRecorder.Recorded;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

Sort option for queries. You have to provide at least a list of properties to sort for that must not include null or empty strings. The direction defaults to DEFAULT_DIRECTION.
Author:Oliver Gierke, Thomas Darimont, Mark Paluch
/** * Sort option for queries. You have to provide at least a list of properties to sort for that must not include * {@literal null} or empty strings. The direction defaults to {@link Sort#DEFAULT_DIRECTION}. * * @author Oliver Gierke * @author Thomas Darimont * @author Mark Paluch */
@RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class Sort implements Streamable<org.springframework.data.domain.Sort.Order>, Serializable { private static final long serialVersionUID = 5737186511678863905L; private static final Sort UNSORTED = Sort.by(new Order[0]); public static final Direction DEFAULT_DIRECTION = Direction.ASC; private final List<Order> orders;
Creates a new Sort instance.
Params:
  • direction – defaults to DEFAULT_DIRECTION (for null cases, too)
  • properties – must not be null or contain null or empty strings.
/** * Creates a new {@link Sort} instance. * * @param direction defaults to {@link Sort#DEFAULT_DIRECTION} (for {@literal null} cases, too) * @param properties must not be {@literal null} or contain {@literal null} or empty strings. */
private Sort(Direction direction, List<String> properties) { if (properties == null || properties.isEmpty()) { throw new IllegalArgumentException("You have to provide at least one property to sort by!"); } this.orders = properties.stream() // .map(it -> new Order(direction, it)) // .collect(Collectors.toList()); }
Creates a new Sort for the given properties.
Params:
  • properties – must not be null.
Returns:
/** * Creates a new {@link Sort} for the given properties. * * @param properties must not be {@literal null}. * @return */
public static Sort by(String... properties) { Assert.notNull(properties, "Properties must not be null!"); return properties.length == 0 // ? Sort.unsorted() // : new Sort(DEFAULT_DIRECTION, Arrays.asList(properties)); }
Creates a new Sort for the given Orders.
Params:
  • orders – must not be null.
Returns:
/** * Creates a new {@link Sort} for the given {@link Order}s. * * @param orders must not be {@literal null}. * @return */
public static Sort by(List<Order> orders) { Assert.notNull(orders, "Orders must not be null!"); return orders.isEmpty() ? Sort.unsorted() : new Sort(orders); }
Creates a new Sort for the given Orders.
Params:
  • orders – must not be null.
Returns:
/** * Creates a new {@link Sort} for the given {@link Order}s. * * @param orders must not be {@literal null}. * @return */
public static Sort by(Order... orders) { Assert.notNull(orders, "Orders must not be null!"); return new Sort(Arrays.asList(orders)); }
Creates a new Sort for the given Orders.
Params:
  • direction – must not be null.
  • properties – must not be null.
Returns:
/** * Creates a new {@link Sort} for the given {@link Order}s. * * @param direction must not be {@literal null}. * @param properties must not be {@literal null}. * @return */
public static Sort by(Direction direction, String... properties) { Assert.notNull(direction, "Direction must not be null!"); Assert.notNull(properties, "Properties must not be null!"); Assert.isTrue(properties.length > 0, "At least one property must be given!"); return Sort.by(Arrays.stream(properties)// .map(it -> new Order(direction, it))// .collect(Collectors.toList())); }
Creates a new TypedSort for the given type.
Params:
  • type – must not be null.
Returns:
Since:2.2
/** * Creates a new {@link TypedSort} for the given type. * * @param type must not be {@literal null}. * @return * @since 2.2 */
public static <T> TypedSort<T> sort(Class<T> type) { return new TypedSort<>(type); }
Returns a Sort instances representing no sorting setup at all.
Returns:
/** * Returns a {@link Sort} instances representing no sorting setup at all. * * @return */
public static Sort unsorted() { return UNSORTED; }
Returns a new Sort with the current setup but descending order direction.
Returns:
/** * Returns a new {@link Sort} with the current setup but descending order direction. * * @return */
public Sort descending() { return withDirection(Direction.DESC); }
Returns a new Sort with the current setup but ascending order direction.
Returns:
/** * Returns a new {@link Sort} with the current setup but ascending order direction. * * @return */
public Sort ascending() { return withDirection(Direction.ASC); } public boolean isSorted() { return !orders.isEmpty(); } public boolean isUnsorted() { return !isSorted(); }
Returns a new Sort consisting of the Orders of the current Sort combined with the given ones.
Params:
  • sort – must not be null.
Returns:
/** * Returns a new {@link Sort} consisting of the {@link Order}s of the current {@link Sort} combined with the given * ones. * * @param sort must not be {@literal null}. * @return */
public Sort and(Sort sort) { Assert.notNull(sort, "Sort must not be null!"); ArrayList<Order> these = new ArrayList<>(this.orders); for (Order order : sort) { these.add(order); } return Sort.by(these); }
Returns the order registered for the given property.
Params:
  • property –
Returns:
/** * Returns the order registered for the given property. * * @param property * @return */
@Nullable public Order getOrderFor(String property) { for (Order order : this) { if (order.getProperty().equals(property)) { return order; } } return null; } /* * (non-Javadoc) * @see java.lang.Iterable#iterator() */ public Iterator<Order> iterator() { return this.orders.iterator(); } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (!(obj instanceof Sort)) { return false; } Sort that = (Sort) obj; return this.orders.equals(that.orders); } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int result = 17; result = 31 * result + orders.hashCode(); return result; } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return orders.isEmpty() ? "UNSORTED" : StringUtils.collectionToCommaDelimitedString(orders); }
Creates a new Sort with the current setup but the given order direction.
Params:
  • direction –
Returns:
/** * Creates a new {@link Sort} with the current setup but the given order direction. * * @param direction * @return */
private Sort withDirection(Direction direction) { return Sort.by(orders.stream().map(it -> new Order(direction, it.getProperty())).collect(Collectors.toList())); }
Enumeration for sort directions.
Author:Oliver Gierke
/** * Enumeration for sort directions. * * @author Oliver Gierke */
public static enum Direction { ASC, DESC;
Returns whether the direction is ascending.
Returns:
Since:1.13
/** * Returns whether the direction is ascending. * * @return * @since 1.13 */
public boolean isAscending() { return this.equals(ASC); }
Returns whether the direction is descending.
Returns:
Since:1.13
/** * Returns whether the direction is descending. * * @return * @since 1.13 */
public boolean isDescending() { return this.equals(DESC); }
Returns the Direction enum for the given String value.
Params:
  • value –
Throws:
Returns:
/** * Returns the {@link Direction} enum for the given {@link String} value. * * @param value * @throws IllegalArgumentException in case the given value cannot be parsed into an enum value. * @return */
public static Direction fromString(String value) { try { return Direction.valueOf(value.toUpperCase(Locale.US)); } catch (Exception e) { throw new IllegalArgumentException(String.format( "Invalid value '%s' for orders given! Has to be either 'desc' or 'asc' (case insensitive).", value), e); } }
Returns the Direction enum for the given String or null if it cannot be parsed into an enum value.
Params:
  • value –
Returns:
/** * Returns the {@link Direction} enum for the given {@link String} or null if it cannot be parsed into an enum * value. * * @param value * @return */
public static Optional<Direction> fromOptionalString(String value) { try { return Optional.of(fromString(value)); } catch (IllegalArgumentException e) { return Optional.empty(); } } }
Enumeration for null handling hints that can be used in Order expressions.
Author:Thomas Darimont
Since:1.8
/** * Enumeration for null handling hints that can be used in {@link Order} expressions. * * @author Thomas Darimont * @since 1.8 */
public static enum NullHandling {
Lets the data store decide what to do with nulls.
/** * Lets the data store decide what to do with nulls. */
NATIVE,
A hint to the used data store to order entries with null values before non null entries.
/** * A hint to the used data store to order entries with null values before non null entries. */
NULLS_FIRST,
A hint to the used data store to order entries with null values after non null entries.
/** * A hint to the used data store to order entries with null values after non null entries. */
NULLS_LAST; }
PropertyPath implements the pairing of an Direction and a property. It is used to provide input for Sort
Author:Oliver Gierke, Kevin Raymond
/** * PropertyPath implements the pairing of an {@link Direction} and a property. It is used to provide input for * {@link Sort} * * @author Oliver Gierke * @author Kevin Raymond */
public static class Order implements Serializable { private static final long serialVersionUID = 1522511010900108987L; private static final boolean DEFAULT_IGNORE_CASE = false; private static final NullHandling DEFAULT_NULL_HANDLING = NullHandling.NATIVE; private final Direction direction; private final String property; private final boolean ignoreCase; private final NullHandling nullHandling;
Creates a new Order instance. if order is null then order defaults to Sort.DEFAULT_DIRECTION
Params:
/** * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to * {@link Sort#DEFAULT_DIRECTION} * * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION} * @param property must not be {@literal null} or empty. */
public Order(@Nullable Direction direction, String property) { this(direction, property, DEFAULT_IGNORE_CASE, DEFAULT_NULL_HANDLING); }
Creates a new Order instance. if order is null then order defaults to Sort.DEFAULT_DIRECTION
Params:
  • direction – can be null, will default to Sort.DEFAULT_DIRECTION
  • property – must not be null or empty.
  • nullHandling – must not be null.
/** * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to * {@link Sort#DEFAULT_DIRECTION} * * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION} * @param property must not be {@literal null} or empty. * @param nullHandling must not be {@literal null}. */
public Order(@Nullable Direction direction, String property, NullHandling nullHandlingHint) { this(direction, property, DEFAULT_IGNORE_CASE, nullHandlingHint); }
Creates a new Order instance. Takes a single property. Direction defaults to Sort.DEFAULT_DIRECTION.
Params:
  • property – must not be null or empty.
Since:2.0
/** * Creates a new {@link Order} instance. Takes a single property. Direction defaults to * {@link Sort#DEFAULT_DIRECTION}. * * @param property must not be {@literal null} or empty. * @since 2.0 */
public static Order by(String property) { return new Order(DEFAULT_DIRECTION, property); }
Creates a new Order instance. Takes a single property. Direction is Direction.ASC and NullHandling NullHandling.NATIVE.
Params:
  • property – must not be null or empty.
Since:2.0
/** * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#ASC} and * NullHandling {@link NullHandling#NATIVE}. * * @param property must not be {@literal null} or empty. * @since 2.0 */
public static Order asc(String property) { return new Order(Direction.ASC, property, DEFAULT_NULL_HANDLING); }
Creates a new Order instance. Takes a single property. Direction is Direction.DESC and NullHandling NullHandling.NATIVE.
Params:
  • property – must not be null or empty.
Since:2.0
/** * Creates a new {@link Order} instance. Takes a single property. Direction is {@link Direction#DESC} and * NullHandling {@link NullHandling#NATIVE}. * * @param property must not be {@literal null} or empty. * @since 2.0 */
public static Order desc(String property) { return new Order(Direction.DESC, property, DEFAULT_NULL_HANDLING); }
Creates a new Order instance. if order is null then order defaults to Sort.DEFAULT_DIRECTION
Params:
  • direction – can be null, will default to Sort.DEFAULT_DIRECTION
  • property – must not be null or empty.
  • ignoreCase – true if sorting should be case insensitive. false if sorting should be case sensitive.
  • nullHandling – must not be null.
Since:1.7
/** * Creates a new {@link Order} instance. if order is {@literal null} then order defaults to * {@link Sort#DEFAULT_DIRECTION} * * @param direction can be {@literal null}, will default to {@link Sort#DEFAULT_DIRECTION} * @param property must not be {@literal null} or empty. * @param ignoreCase true if sorting should be case insensitive. false if sorting should be case sensitive. * @param nullHandling must not be {@literal null}. * @since 1.7 */
private Order(@Nullable Direction direction, String property, boolean ignoreCase, NullHandling nullHandling) { if (!StringUtils.hasText(property)) { throw new IllegalArgumentException("Property must not null or empty!"); } this.direction = direction == null ? DEFAULT_DIRECTION : direction; this.property = property; this.ignoreCase = ignoreCase; this.nullHandling = nullHandling; }
Returns the order the property shall be sorted for.
Returns:
/** * Returns the order the property shall be sorted for. * * @return */
public Direction getDirection() { return direction; }
Returns the property to order for.
Returns:
/** * Returns the property to order for. * * @return */
public String getProperty() { return property; }
Returns whether sorting for this property shall be ascending.
Returns:
/** * Returns whether sorting for this property shall be ascending. * * @return */
public boolean isAscending() { return this.direction.isAscending(); }
Returns whether sorting for this property shall be descending.
Returns:
Since:1.13
/** * Returns whether sorting for this property shall be descending. * * @return * @since 1.13 */
public boolean isDescending() { return this.direction.isDescending(); }
Returns whether or not the sort will be case sensitive.
Returns:
/** * Returns whether or not the sort will be case sensitive. * * @return */
public boolean isIgnoreCase() { return ignoreCase; }
Returns a new Order with the given Direction.
Params:
  • direction –
Returns:
/** * Returns a new {@link Order} with the given {@link Direction}. * * @param direction * @return */
public Order with(Direction direction) { return new Order(direction, this.property, this.ignoreCase, this.nullHandling); }
Returns a new Order
Params:
  • property – must not be null or empty.
Returns:
Since:1.13
/** * Returns a new {@link Order} * * @param property must not be {@literal null} or empty. * @return * @since 1.13 */
public Order withProperty(String property) { return new Order(this.direction, property, this.ignoreCase, this.nullHandling); }
Returns a new Sort instance for the given properties.
Params:
  • properties –
Returns:
/** * Returns a new {@link Sort} instance for the given properties. * * @param properties * @return */
public Sort withProperties(String... properties) { return Sort.by(this.direction, properties); }
Returns a new Order with case insensitive sorting enabled.
Returns:
/** * Returns a new {@link Order} with case insensitive sorting enabled. * * @return */
public Order ignoreCase() { return new Order(direction, property, true, nullHandling); }
Returns a Order with the given NullHandling.
Params:
  • nullHandling – can be null.
Returns:
Since:1.8
/** * Returns a {@link Order} with the given {@link NullHandling}. * * @param nullHandling can be {@literal null}. * @return * @since 1.8 */
public Order with(NullHandling nullHandling) { return new Order(direction, this.property, ignoreCase, nullHandling); }
Returns a Order with NullHandling.NULLS_FIRST as null handling hint.
Returns:
Since:1.8
/** * Returns a {@link Order} with {@link NullHandling#NULLS_FIRST} as null handling hint. * * @return * @since 1.8 */
public Order nullsFirst() { return with(NullHandling.NULLS_FIRST); }
Returns a Order with NullHandling.NULLS_LAST as null handling hint.
Returns:
Since:1.7
/** * Returns a {@link Order} with {@link NullHandling#NULLS_LAST} as null handling hint. * * @return * @since 1.7 */
public Order nullsLast() { return with(NullHandling.NULLS_LAST); }
Returns a Order with NullHandling.NATIVE as null handling hint.
Returns:
Since:1.7
/** * Returns a {@link Order} with {@link NullHandling#NATIVE} as null handling hint. * * @return * @since 1.7 */
public Order nullsNative() { return with(NullHandling.NATIVE); }
Returns the used NullHandling hint, which can but may not be respected by the used datastore.
Returns:
Since:1.7
/** * Returns the used {@link NullHandling} hint, which can but may not be respected by the used datastore. * * @return * @since 1.7 */
public NullHandling getNullHandling() { return nullHandling; } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { int result = 17; result = 31 * result + direction.hashCode(); result = 31 * result + property.hashCode(); result = 31 * result + (ignoreCase ? 1 : 0); result = 31 * result + nullHandling.hashCode(); return result; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (!(obj instanceof Order)) { return false; } Order that = (Order) obj; return this.direction.equals(that.direction) && this.property.equals(that.property) && this.ignoreCase == that.ignoreCase && this.nullHandling.equals(that.nullHandling); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { String result = String.format("%s: %s", property, direction); if (!NullHandling.NATIVE.equals(nullHandling)) { result += ", " + nullHandling; } if (ignoreCase) { result += ", ignoring case"; } return result; } }
Extension of Sort to use method handles to define properties to sort by.
Author:Oliver Gierke
Since:2.2
@soundtrackThe Intersphere - Linger (The Grand Delusion)
/** * Extension of Sort to use method handles to define properties to sort by. * * @author Oliver Gierke * @since 2.2 * @soundtrack The Intersphere - Linger (The Grand Delusion) */
public static class TypedSort<T> extends Sort { private static final long serialVersionUID = -3550403511206745880L; private final Recorded<T> recorded; private TypedSort(Class<T> type) { this(MethodInvocationRecorder.forProxyOf(type)); } private TypedSort(Recorded<T> recorded) { super(Collections.emptyList()); this.recorded = recorded; } public <S> TypedSort<S> by(Function<T, S> property) { return new TypedSort<>(recorded.record(property)); } public <S> TypedSort<S> by(Recorded.ToCollectionConverter<T, S> collectionProperty) { return new TypedSort<>(recorded.record(collectionProperty)); } public <S> TypedSort<S> by(Recorded.ToMapConverter<T, S> mapProperty) { return new TypedSort<>(recorded.record(mapProperty)); } @Override public Sort ascending() { return withDirection(Sort::ascending); } @Override public Sort descending() { return withDirection(Sort::descending); } private Sort withDirection(Function<Sort, Sort> direction) { return recorded.getPropertyPath() // .map(Sort::by) // .map(direction) // .orElseGet(Sort::unsorted); } /* * (non-Javadoc) * @see org.springframework.data.domain.Sort#iterator() */ @Override public Iterator<Order> iterator() { return recorded.getPropertyPath() // .map(Order::by) // .map(Collections::singleton) // .orElseGet(Collections::emptySet).iterator(); } /* * (non-Javadoc) * @see org.springframework.data.domain.Sort#toString() */ @Override public String toString() { return recorded.getPropertyPath() // .map(Sort::by) // .orElseGet(Sort::unsorted) // .toString(); } } }