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