View Javadoc
1   /*
2   * Copyright 2019-2026 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.comparator.model;
18  
19  import static java.util.Objects.isNull;
20  
21  import com.fasterxml.jackson.annotation.JsonCreator;
22  import com.fasterxml.jackson.annotation.JsonIgnore;
23  import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
24  import com.fasterxml.jackson.annotation.JsonProperty;
25  import io.swagger.v3.oas.annotations.media.Schema;
26  import jakarta.xml.bind.annotation.XmlAccessType;
27  import jakarta.xml.bind.annotation.XmlAccessorType;
28  import jakarta.xml.bind.annotation.XmlElementRef;
29  import jakarta.xml.bind.annotation.XmlRootElement;
30  import jakarta.xml.bind.annotation.XmlTransient;
31  import jakarta.xml.bind.annotation.XmlType;
32  import java.io.Serial;
33  import java.io.Serializable;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.List;
39  import java.util.Optional;
40  import java.util.StringTokenizer;
41  import java.util.stream.Collectors;
42  import lombok.EqualsAndHashCode;
43  
44  /**
45   * The sort order is a list of sort order items.
46   *
47   * @author Christian Bremer
48   */
49  @XmlAccessorType(XmlAccessType.FIELD)
50  @XmlRootElement(name = "sortOrder")
51  @XmlType(name = "sortOrderType")
52  @JsonIgnoreProperties(ignoreUnknown = true)
53  @Schema(description = "The sort order.")
54  @EqualsAndHashCode
55  public class SortOrder implements Serializable {
56  
57    @Serial
58    private static final long serialVersionUID = 1;
59  
60    /**
61     * The constant DEFAULT_SEPARATOR.
62     */
63    public static final String DEFAULT_SEPARATOR = ";";
64  
65    /**
66     * The items of the sort order.
67     */
68    @Schema(description = "The sort order items.")
69    @XmlElementRef
70    private final List<SortOrderItem> items = new ArrayList<>();
71  
72    /**
73     * Instantiates an empty sort order.
74     */
75    protected SortOrder() {
76      super();
77    }
78  
79    /**
80     * Instantiates a new unmodifiable sort order.
81     *
82     * @param sortOrderItems the sort order items
83     */
84    @JsonCreator
85    public SortOrder(@JsonProperty("items") Collection<? extends SortOrderItem> sortOrderItems) {
86      if (!isNull(sortOrderItems)) {
87        this.items.addAll(sortOrderItems);
88      }
89    }
90  
91    /**
92     * Gets the unmodifiable list of sort order items.
93     *
94     * @return the list of sort order items
95     */
96    public List<SortOrderItem> getItems() {
97      return Collections.unmodifiableList(items);
98    }
99  
100   /**
101    * Checks whether the list of items is empty or not.
102    *
103    * @return {@code true} if the list of items is empty, otherwise {@code false}
104    */
105   @XmlTransient
106   @JsonIgnore
107   public boolean isEmpty() {
108     return items.isEmpty();
109   }
110 
111   /**
112    * Checks whether this sort order contains any entries. If there are entries, this is sorted,
113    * otherwise it is unsorted.
114    *
115    * @return {@code true} if the list of sort orders is not empty (aka sorted), otherwise
116    *     {@code false}
117    */
118   @XmlTransient
119   @JsonIgnore
120   public boolean isSorted() {
121     return !isEmpty();
122   }
123 
124   /**
125    * Checks whether this sort order contains any entries. If there are no entries, this is unsorted,
126    * otherwise it is sorted.
127    *
128    * @return {@code true} if the list of sort orders is empty (aka unsorted), otherwise
129    *     {@code false}
130    */
131   @XmlTransient
132   @JsonIgnore
133   public boolean isUnsorted() {
134     return !isSorted();
135   }
136 
137   /**
138    * Creates the sort order text of this ordering descriptions.
139    *
140    * <p>The syntax of the ordering description is
141    * <pre>
142    * fieldNameOrPath0,direction,case-handling,null-handling;fieldNameOrPath1,direction,case-handling,null-handling
143    * </pre>
144    *
145    * <p>For example
146    * <pre>
147    * created,desc;person.lastName,asc;person.firstName,asc
148    * </pre>
149    *
150    * @return the sort order text
151    */
152   @JsonIgnore
153   @XmlTransient
154   public String getSortOrderText() {
155     return getSortOrderText(SortOrderTextSeparators.defaults());
156   }
157 
158   /**
159    * Creates the sort order text of this ordering descriptions.
160    *
161    * <p>The syntax of the ordering description is
162    * <pre>
163    * fieldNameOrPath0,direction,case-handling,null-handling;fieldNameOrPath1,direction,case-handling,null-handling
164    * </pre>
165    *
166    * <p>For example
167    * <pre>
168    * created,desc;person.lastName,asc;person.firstName,asc
169    * </pre>
170    *
171    * @param separators the separators
172    * @return the sort order text
173    */
174   public String getSortOrderText(SortOrderTextSeparators separators) {
175     String separator = Optional.ofNullable(separators)
176         .orElseGet(SortOrderTextSeparators::defaults)
177         .getChainSeparator();
178     return items.stream()
179         .map(item -> item.getSortOrderText(separators))
180         .collect(Collectors.joining(separator));
181   }
182 
183   @Override
184   public String toString() {
185     return getSortOrderText();
186   }
187 
188   /**
189    * From sort order text.
190    *
191    * @param source the sort order text
192    * @return the sort order
193    */
194   public static SortOrder fromSortOrderText(String source) {
195     return fromSortOrderText(source, SortOrderTextSeparators.defaults());
196   }
197 
198   /**
199    * From sort order text.
200    *
201    * @param source the sort order text
202    * @param separators the separators
203    * @return the sort order
204    */
205   public static SortOrder fromSortOrderText(String source, SortOrderTextSeparators separators) {
206     if (isNull(source)) {
207       return unsorted();
208     }
209     String separator = Optional.ofNullable(separators)
210         .orElseGet(SortOrderTextSeparators::defaults)
211         .getChainSeparator();
212     return Optional.of(source.trim())
213         .map(text -> {
214           List<SortOrderItem> sortOrderItems = new ArrayList<>();
215           StringTokenizer tokenizer = new StringTokenizer(text, separator);
216           while (tokenizer.hasMoreTokens()) {
217             sortOrderItems.add(SortOrderItem
218                 .fromSortOrderText(tokenizer.nextToken(), separators));
219           }
220           if (sortOrderItems.isEmpty()) {
221             sortOrderItems.add(new SortOrderItem());
222           }
223           return new SortOrder(sortOrderItems);
224         })
225         .orElseGet(SortOrder::new);
226   }
227 
228   /**
229    * Creates new sort order with the given items.
230    *
231    * @param sortOrderItems the sort orders
232    * @return the sort order
233    */
234   public static SortOrder by(SortOrderItem... sortOrderItems) {
235     return Optional.ofNullable(sortOrderItems)
236         .map(so -> new SortOrder(Arrays.asList(so)))
237         .orElseGet(SortOrder::new);
238   }
239 
240   /**
241    * Unsorted sort order.
242    *
243    * @return the unsorted sort order
244    */
245   public static SortOrder unsorted() {
246     return new SortOrder();
247   }
248 
249 }