View Javadoc
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   }