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