1 /*
2 * *************************************************************************************************************************************************************
3 *
4 * TheseFoolishThings: Miscellaneous utilities
5 * http://tidalwave.it/projects/thesefoolishthings
6 *
7 * Copyright (C) 2009 - 2026 by Tidalwave s.a.s. (http://tidalwave.it)
8 *
9 * *************************************************************************************************************************************************************
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * 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
17 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18 *
19 * *************************************************************************************************************************************************************
20 *
21 * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22 * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23 *
24 * *************************************************************************************************************************************************************
25 */
26 package it.tidalwave.util;
27
28 // import javax.annotation.Nonnegative;
29 import jakarta.annotation.Nonnull;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.Comparator;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Optional;
36 import java.util.OptionalInt;
37 import java.util.function.BiFunction;
38 import java.util.function.Consumer;
39 import java.util.function.Function;
40 import java.util.function.Supplier;
41 import java.util.stream.Stream;
42 import java.io.Serializable;
43 import it.tidalwave.util.impl.finder.ArrayListFinder;
44 import it.tidalwave.util.impl.finder.MappingFinder;
45 import it.tidalwave.util.impl.finder.ProviderFinder;
46 import it.tidalwave.util.impl.finder.SupplierFinder;
47 import lombok.AccessLevel;
48 import lombok.AllArgsConstructor;
49 import lombok.EqualsAndHashCode;
50 import lombok.RequiredArgsConstructor;
51 import lombok.ToString;
52
53 /***************************************************************************************************************************************************************
54 *
55 * A factory for providing results of a search. {@code Finder} implementations must be <em>immutable</em>.
56 *
57 * @author Fabrizio Giudici
58 *
59 **************************************************************************************************************************************************************/
60 public interface Finder<T> extends Cloneable, Serializable
61 {
62 /***********************************************************************************************************************************************************
63 * A tag interface to mark objects which are meaningful sort criteria that can be passed to {@link Finder#sort(it.tidalwave.util.Finder.SortCriterion)}.
64 * In general, a {@code SortCriterion} is just a behaviourless and methodless object, that should be specifically handled by concrete implementations of
65 * {@link Finder}. The only exceptions are {@link InMemorySortCriterion} objects.
66 **********************************************************************************************************************************************************/
67 public static interface SortCriterion
68 {
69 public static final Class<SortCriterion> _SortCriterion_ = SortCriterion.class;
70
71 /** A special {@link SortCriterion} which indicates that no sort has been performed. */
72 public static final SortCriterion UNSORTED = (InMemorySortCriterion<Object>)(results, sortDirection) -> {};
73
74 public static final SortCriterion DEFAULT = UNSORTED;
75 }
76
77 /***********************************************************************************************************************************************************
78 * An interface that should be implemented by specific {@link SortCriterion} objects which are capable to implement by themselves the sorting of objects, by
79 * post-processing an existing collection of objects. While this is often convenient, it is possible for it to be inefficient in cases in which the original
80 * source of objects is capable to perform the sort in an optimized way (e.g. an SQL database by means of {@code ORDER BY}). The facility class
81 * {@link it.tidalwave.util.spi.HierarchicFinderSupport} supports {@code FilterSortCriterion} objects out of the box.
82 **********************************************************************************************************************************************************/
83 public static interface InMemorySortCriterion<U> extends SortCriterion
84 {
85 /*******************************************************************************************************************************************************
86 * Performs the sort of results.
87 * @param results the list of objects to be sorted in place
88 ******************************************************************************************************************************************************/
89 public default void sort (@Nonnull final List<? extends U> results)
90 {
91 sort(results, SortDirection.ASCENDING);
92 }
93
94 /*******************************************************************************************************************************************************
95 * Performs the sort of results.
96 * @param results the list of objects to be sorted in place
97 * @param sortDirection the sort direction
98 ******************************************************************************************************************************************************/
99 // START SNIPPET: sort
100 public void sort (@Nonnull List<? extends U> results, @Nonnull SortDirection sortDirection);
101 // END SNIPPET: sort
102
103 /*******************************************************************************************************************************************************
104 * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
105 * @param <U> the type of the objects to compare
106 * @param comparator the {@code Comparator}
107 * @return the new {@code SortCriterion}
108 ******************************************************************************************************************************************************/
109 @Nonnull
110 public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator)
111 {
112 return of(comparator, comparator.getClass().getSimpleName());
113 }
114
115 /*******************************************************************************************************************************************************
116 * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
117 * @param <U> the type of the objects to compare
118 * @param comparator the {@code Comparator}
119 * @param name a name
120 * @return the new {@code SortCriterion}
121 ******************************************************************************************************************************************************/
122 @Nonnull
123 public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator,
124 @Nonnull final String name)
125 {
126 return new DefaultInMemorySortCriterion<>(comparator, name);
127 }
128
129 /*******************************************************************************************************************************************************
130 *
131 ******************************************************************************************************************************************************/
132 @AllArgsConstructor @ToString @EqualsAndHashCode
133 static class DefaultInMemorySortCriterion<U> implements Finder.InMemorySortCriterion<U>, Serializable
134 {
135 private static final long serialVersionUID = 76093596048395982L;
136
137 @Nonnull
138 private final Comparator<? super U> comparator;
139
140 @Nonnull
141 private final String name;
142
143 @Override
144 public void sort (@Nonnull final List<? extends U> results, @Nonnull final SortDirection sortDirection)
145 {
146 results.sort((Comparator<U>)(o1, o2) -> comparator.compare(o1, o2) * sortDirection.intValue());
147 }
148 }
149 }
150
151 /***********************************************************************************************************************************************************
152 * An enumeration to define the direction of a sort (ascending or descending).
153 **********************************************************************************************************************************************************/
154 @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
155 public static enum SortDirection
156 {
157 ASCENDING(+1), DESCENDING(-1);
158
159 private final int intValue;
160
161 /** @return +1 for ascending direction, -1 for descending */
162 public int intValue()
163 {
164 return intValue;
165 }
166 }
167
168 /***********************************************************************************************************************************************************
169 * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
170 * @param firstResult the index of the first result to return
171 * @return the {@code Finder}
172 **********************************************************************************************************************************************************/
173 // START SNIPPET: from
174 @Nonnull
175 public Finder<T> from (/* @Nonnegative */ int firstResult);
176 // END SNIPPET: from
177
178 /***********************************************************************************************************************************************************
179 * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
180 * @param firstResult the index of the first result to return
181 * @return the {@code Finder}
182 * @since 3.2-ALPHA-19
183 **********************************************************************************************************************************************************/
184 @Nonnull
185 public default Finder<T> from (@Nonnull final Optional<Integer> firstResult)
186 {
187 return firstResult.map(this::from).orElse(this);
188 }
189
190 /***********************************************************************************************************************************************************
191 * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
192 * @param firstResult the index of the first result to return
193 * @return the {@code Finder}
194 * @since 3.2-ALPHA-22
195 **********************************************************************************************************************************************************/
196 @Nonnull
197 public default Finder<T> from (@Nonnull final OptionalInt firstResult)
198 {
199 return firstResult.isPresent() ? from(firstResult.getAsInt()) : this;
200 }
201
202 /***********************************************************************************************************************************************************
203 * Tells the {@code Finder} that only a maximum number of found items will be returned.
204 * @param maxResults the max number of results to return
205 * @return the {@code Finder}
206 **********************************************************************************************************************************************************/
207 // START SNIPPET: max
208 @Nonnull
209 public Finder<T> max (/* @Nonnegative */ int maxResults);
210 // END SNIPPET: max
211
212 /***********************************************************************************************************************************************************
213 * Tells the {@code Finder} that only a maximum number of found items will be returned.
214 * @param maxResults the max number of results to return
215 * @return the {@code Finder}
216 * @since 3.2-ALPHA-19
217 **********************************************************************************************************************************************************/
218 @Nonnull
219 public default Finder<T> max (@Nonnull final Optional<Integer> maxResults)
220 {
221 return maxResults.map(this::max).orElse(this);
222 }
223
224 /***********************************************************************************************************************************************************
225 * Tells the {@code Finder} that only a maximum number of found items will be returned.
226 * @param maxResults the max number of results to return
227 * @return the {@code Finder}
228 * @since 3.2-ALPHA-22
229 **********************************************************************************************************************************************************/
230 @Nonnull
231 public default Finder<T> max (@Nonnull final OptionalInt maxResults)
232 {
233 return maxResults.isPresent() ? max(maxResults.getAsInt()) : this;
234 }
235
236 /***********************************************************************************************************************************************************
237 * Tells the {@code Finder} that results should be created with the given context. This method can be called multiple times; contexts are accumulated.
238 * Not all implementations of {@code Finder} have this working.
239 * @param context the context
240 * @return the {@code Finder}
241 **********************************************************************************************************************************************************/
242 @Nonnull
243 public default Finder<T> withContext (@Nonnull final Object context)
244 {
245 throw new UnsupportedOperationException("Not implemented.");
246 }
247
248 /***********************************************************************************************************************************************************
249 * Tells the {@code Finder} that the specified type of results is expected.
250 * Not all implementations of {@code Finder} have this working.
251 * @param <U> the static type
252 * @param type the dynamic type
253 * @return the {@code Finder}
254 **********************************************************************************************************************************************************/
255 @Nonnull
256 public default <U> Finder<U> ofType (@Nonnull final Class<U> type)
257 {
258 throw new UnsupportedOperationException("Not implemented.");
259 }
260
261 /***********************************************************************************************************************************************************
262 * Tells the {@code Finder} that results will be sorted according to the given criterion, in ascending direction.
263 * @param criterion the sort criterion
264 * @return the {@code Finder}
265 **********************************************************************************************************************************************************/
266 @Nonnull
267 public default Finder<T> sort (@Nonnull final SortCriterion criterion)
268 {
269 return sort(criterion, SortDirection.ASCENDING);
270 }
271
272 /***********************************************************************************************************************************************************
273 * Tells the {@code Finder} that results will be sorted according to the given criterion and direction.
274 * @param criterion the sort criterion
275 * @param direction the sort direction
276 * @return the {@code Finder}
277 **********************************************************************************************************************************************************/
278 @Nonnull
279 public Finder<T> sort (@Nonnull SortCriterion criterion, @Nonnull SortDirection direction);
280
281 /***********************************************************************************************************************************************************
282 * Performs the search and returns the found items.
283 * @return the searched items
284 **********************************************************************************************************************************************************/
285 // START SNIPPET: results
286 @Nonnull
287 public List<T> results();
288 // END SNIPPET: results
289
290 /***********************************************************************************************************************************************************
291 * Performs the search and returns the count of found items.
292 * @return the count of found items
293 **********************************************************************************************************************************************************/
294 // START SNIPPET: count
295 /* @Nonnegative */
296 public int count();
297 // END SNIPPET: count
298
299 /***********************************************************************************************************************************************************
300 * Performs the search assuming that it will return a single item and returns it. This method fails if the search returns more than one single item.
301 * @return the optional result
302 * @throws RuntimeException if the search returned more than one single item
303 * @since 3.2-ALPHA-1 (previously in Finder8)
304 **********************************************************************************************************************************************************/
305 // START SNIPPET: optionalResult
306 @Nonnull
307 public default Optional<T> optionalResult()
308 // END SNIPPET: optionalResult
309 {
310 final var results = results();
311
312 if (results.size() > 1)
313 {
314 throw new RuntimeException(results.size() + " results, expected only one");
315 }
316
317 return results.stream().findFirst();
318 }
319
320 /***********************************************************************************************************************************************************
321 * Performs the search and returns only the first found item.
322 * @return the first result
323 * @since 3.2-ALPHA-1 (previously in Finder8)
324 **********************************************************************************************************************************************************/
325 // START SNIPPET: optionalFirstResult
326 @Nonnull
327 public default Optional<T> optionalFirstResult()
328 // END SNIPPET: optionalFirstResult
329 {
330 return stream().findFirst();
331 }
332
333 /***********************************************************************************************************************************************************
334 * Returns a stream of results.
335 * @return the stream
336 * @since 3.2-ALPHA-1 (previously in Finder8)
337 **********************************************************************************************************************************************************/
338 @Nonnull
339 public default Stream<T> stream()
340 {
341 return results().stream();
342 }
343
344 /***********************************************************************************************************************************************************
345 * Returns an iterator of results.
346 * @return the iterator
347 * @since 3.2-ALPHA-1 (previously in Finder8)
348 **********************************************************************************************************************************************************/
349 @Nonnull
350 public default Iterator<T> iterator()
351 {
352 return stream().iterator();
353 }
354
355 /***********************************************************************************************************************************************************
356 * Iterates through results.
357 * @param consumer the consumer
358 * @since 3.2-ALPHA-22
359 **********************************************************************************************************************************************************/
360 public default void forEach (@Nonnull final Consumer<? super T> consumer)
361 {
362 stream().forEach(consumer);
363 }
364
365 /***********************************************************************************************************************************************************
366 * Performs the search assuming that it will return a single item and returns it. This method fails if the search returns more than one single item.
367 * @return the found item
368 * @throws NotFoundException if the search didn't find anything
369 * @throws RuntimeException if the search returned more than one single item
370 * @deprecated Use {@link #optionalResult()} instead
371 **********************************************************************************************************************************************************/
372 @Nonnull @Deprecated
373 public default T result()
374 throws NotFoundException
375 {
376 return optionalResult().orElseThrow(NotFoundException::new);
377 }
378
379 /***********************************************************************************************************************************************************
380 * Performs the search and returns only the first found item.
381 * @return the first found item
382 * @throws NotFoundException if the search didn't find anything
383 * @deprecated Use {@link #optionalFirstResult()} instead
384 **********************************************************************************************************************************************************/
385 @Nonnull @Deprecated
386 public default T firstResult()
387 throws NotFoundException
388 {
389 return optionalFirstResult().orElseThrow(NotFoundException::new);
390 }
391
392 /***********************************************************************************************************************************************************
393 * Returns an empty {@code Finder}.
394 * @param <U> the type of the {@code Finder}
395 * @return the empty {@code Finder}
396 * @since 3.2-ALPHA-1 (previously in HierarchicFinderSupport.emptyFinder())
397 **********************************************************************************************************************************************************/
398 @Nonnull
399 public static <U> Finder<U> empty()
400 {
401 return ofCloned(Collections.emptyList());
402 }
403
404 /***********************************************************************************************************************************************************
405 * Returns a wrapped {@code Finder} on a given collection of elements. The collection is cloned and will be immutable. If you need to compute the collection
406 * on demand, use {@link #ofSupplier(Supplier)}. This method retrieves the full range of results that will be later segmented in compliance with the values
407 * specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results is already available of if it is not expensive to compute.
408 * The alternate method {@link #ofProvider(BiFunction)} allows to access the 'from' and 'max' parameter, so only the required items need to be provided.
409 * @param <U> the type of the {@code Finder}
410 * @param items the objects to wrap
411 * @return the wrapped {@code Finder}
412 * @see #ofSupplier(Supplier)
413 * @see #ofProvider(BiFunction)
414 * @since 3.2-ALPHA-1
415 **********************************************************************************************************************************************************/
416 // START SNIPPET: ofCloned
417 @Nonnull
418 public static <U> Finder<U> ofCloned (@Nonnull final Collection<? extends U> items)
419 // END SNIPPET: ofCloned
420 {
421 return new ArrayListFinder<>(items);
422 }
423
424 /***********************************************************************************************************************************************************
425 * Returns a wrapped {@code Finder} on a given supplier. The collection will be cloned after being supplied. This method retrieves the full range of results
426 * that will be later segmented in compliance with the values specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results
427 * is already available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows to access the 'from' and 'max'
428 * parameter, so only the required items need to be provided.
429 * @param <U> the type of the {@code Finder}
430 * @param supplier the supplier
431 * @return the wrapped {@code Finder}
432 * @see #ofCloned(Collection)
433 * @see #ofProvider(BiFunction)
434 * @since 3.2-ALPHA-15
435 **********************************************************************************************************************************************************/
436 // START SNIPPET: ofsupplier
437 @Nonnull
438 public static <U> Finder<U> ofSupplier (@Nonnull final Supplier<? extends Collection<? extends U>> supplier)
439 // END SNIPPET: ofsupplier
440 {
441 return new SupplierFinder<>(supplier);
442 }
443
444 /***********************************************************************************************************************************************************
445 * Returns a wrapped {@code Finder} on a given function to provide results. The function receives the 'from' and 'max' arguments to select a subrange of the
446 * results. The collection will be cloned after being supplied.
447 * @param <U> the type of the {@code Finder}
448 * @param provider the function providing results
449 * @return the wrapped {@code Finder}
450 * @see #ofCloned(Collection)
451 * @see #ofSupplier(Supplier)
452 * @since 3.2-ALPHA-15
453 **********************************************************************************************************************************************************/
454 // START SNIPPET: ofProvider
455 @Nonnull
456 public static <U> Finder<U> ofProvider (@Nonnull final BiFunction<Integer, Integer, ? extends Collection<? extends U>> provider)
457 // END SNIPPET: ofProvider
458 {
459 return new ProviderFinder<>(provider);
460 }
461
462 /***********************************************************************************************************************************************************
463 * Returns a mapping {@code Finder} on a given delegate {@code Finder}. The mapper finder provides the same results as the delegate, transformed by a mapper
464 * function.
465 * @param <U> the type of the {@code Finder}
466 * @param <V> the type of the delegate {@code Finder}
467 * @param delegate the delegate finder
468 * @param mapper the mapper function
469 * @return the wrapped {@code Finder}
470 * @since 3.2-ALPHA-15
471 **********************************************************************************************************************************************************/
472 // START SNIPPET: mapping
473 @Nonnull
474 public static <U, V> Finder<U> mapping (@Nonnull final Finder<V> delegate, @Nonnull final Function<? super V, ? extends U> mapper)
475 // END SNIPPET: mapping
476 {
477 return new MappingFinder<>(delegate, mapper);
478 }
479 }