View Javadoc
1   /*
2    * Copyright 2019-2022 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    @Schema(description = "The sort order items.")
66    @XmlElementRef
67    private final List<SortOrderItem> items = new ArrayList<>();
68  
69    /**
70     * Instantiates an empty sort order.
71     */
72    protected SortOrder() {
73      super();
74    }
75  
76    /**
77     * Instantiates a new unmodifiable sort order.
78     *
79     * @param sortOrderItems the sort order items
80     */
81    @JsonCreator
82    public SortOrder(@JsonProperty("items") Collection<? extends SortOrderItem> sortOrderItems) {
83      if (!isNull(sortOrderItems)) {
84        this.items.addAll(sortOrderItems);
85      }
86    }
87  
88    /**
89     * Gets the unmodifiable list of sort order items.
90     *
91     * @return the list of sort order items
92     */
93    public List<SortOrderItem> getItems() {
94      return Collections.unmodifiableList(items);
95    }
96  
97    /**
98     * Checks whether the list of items is empty or not.
99     *
100    * @return {@code true} if the list of items is empty, otherwise {@code false}
101    */
102   @XmlTransient
103   @JsonIgnore
104   public boolean isEmpty() {
105     return items.isEmpty();
106   }
107 
108   /**
109    * Checks whether this sort order contains any entries. If there are entries, this is sorted,
110    * otherwise it is unsorted.
111    *
112    * @return {@code true} if the list of sort orders is not empty (aka sorted), otherwise
113    *     {@code false}
114    */
115   @XmlTransient
116   @JsonIgnore
117   public boolean isSorted() {
118     return !isEmpty();
119   }
120 
121   /**
122    * Checks whether this sort order contains any entries. If there are no entries, this is unsorted,
123    * otherwise it is sorted.
124    *
125    * @return {@code true} if the list of sort orders is empty (aka unsorted), otherwise
126    *     {@code false}
127    */
128   @XmlTransient
129   @JsonIgnore
130   public boolean isUnsorted() {
131     return !isSorted();
132   }
133 
134   /**
135    * Creates the sort order text of this ordering descriptions.
136    *
137    * <p>The syntax of the ordering description is
138    * <pre>
139    * fieldNameOrPath0,direction,case-handling,null-handling;fieldNameOrPath1,direction,case-handling,null-handling
140    * </pre>
141    *
142    * <p>For example
143    * <pre>
144    * created,desc;person.lastName,asc;person.firstName,asc
145    * </pre>
146    *
147    * @return the sort order text
148    */
149   @JsonIgnore
150   @XmlTransient
151   public String getSortOrderText() {
152     return getSortOrderText(SortOrderTextSeparators.defaults());
153   }
154 
155   /**
156    * Creates the sort order text of this ordering descriptions.
157    *
158    * <p>The syntax of the ordering description is
159    * <pre>
160    * fieldNameOrPath0,direction,case-handling,null-handling;fieldNameOrPath1,direction,case-handling,null-handling
161    * </pre>
162    *
163    * <p>For example
164    * <pre>
165    * created,desc;person.lastName,asc;person.firstName,asc
166    * </pre>
167    *
168    * @param separators the separators
169    * @return the sort order text
170    */
171   public String getSortOrderText(SortOrderTextSeparators separators) {
172     String separator = Optional.ofNullable(separators)
173         .orElseGet(SortOrderTextSeparators::defaults)
174         .getChainSeparator();
175     return items.stream()
176         .map(item -> item.getSortOrderText(separators))
177         .collect(Collectors.joining(separator));
178   }
179 
180   @Override
181   public String toString() {
182     return getSortOrderText();
183   }
184 
185   /**
186    * From sort order text.
187    *
188    * @param source the sort order text
189    * @return the sort order
190    */
191   public static SortOrder fromSortOrderText(String source) {
192     return fromSortOrderText(source, SortOrderTextSeparators.defaults());
193   }
194 
195   /**
196    * From sort order text.
197    *
198    * @param source the sort order text
199    * @param separators the separators
200    * @return the sort order
201    */
202   public static SortOrder fromSortOrderText(String source, SortOrderTextSeparators separators) {
203     if (isNull(source)) {
204       return unsorted();
205     }
206     String separator = Optional.ofNullable(separators)
207         .orElseGet(SortOrderTextSeparators::defaults)
208         .getChainSeparator();
209     return Optional.of(source.trim())
210         .map(text -> {
211           List<SortOrderItem> sortOrderItems = new ArrayList<>();
212           StringTokenizer tokenizer = new StringTokenizer(text, separator);
213           while (tokenizer.hasMoreTokens()) {
214             sortOrderItems.add(SortOrderItem
215                 .fromSortOrderText(tokenizer.nextToken(), separators));
216           }
217           if (sortOrderItems.isEmpty()) {
218             sortOrderItems.add(new SortOrderItem());
219           }
220           return new SortOrder(sortOrderItems);
221         })
222         .orElseGet(SortOrder::new);
223   }
224 
225   /**
226    * Creates new sort order with the given items.
227    *
228    * @param sortOrderItems the sort orders
229    * @return the sort order
230    */
231   public static SortOrder by(SortOrderItem... sortOrderItems) {
232     return Optional.ofNullable(sortOrderItems)
233         .map(so -> new SortOrder(Arrays.asList(so)))
234         .orElseGet(SortOrder::new);
235   }
236 
237   public static SortOrder unsorted() {
238     return new SortOrder();
239   }
240 
241 }