Finder.java

  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. // import javax.annotation.Nonnegative;
  28. import jakarta.annotation.Nonnull;
  29. import java.util.Collection;
  30. import java.util.Collections;
  31. import java.util.Comparator;
  32. import java.util.Iterator;
  33. import java.util.List;
  34. import java.util.Optional;
  35. import java.util.OptionalInt;
  36. import java.util.function.BiFunction;
  37. import java.util.function.Consumer;
  38. import java.util.function.Function;
  39. import java.util.function.Supplier;
  40. import java.util.stream.Stream;
  41. import java.io.Serializable;
  42. import it.tidalwave.util.impl.finder.ArrayListFinder;
  43. import it.tidalwave.util.impl.finder.MappingFinder;
  44. import it.tidalwave.util.impl.finder.ProviderFinder;
  45. import it.tidalwave.util.impl.finder.SupplierFinder;
  46. import lombok.AccessLevel;
  47. import lombok.AllArgsConstructor;
  48. import lombok.EqualsAndHashCode;
  49. import lombok.RequiredArgsConstructor;
  50. import lombok.ToString;

  51. /***************************************************************************************************************************************************************
  52.  *
  53.  * A factory for providing results of a search. {@code Finder} implementations must be <em>immutable</em>.
  54.  *
  55.  * @author  Fabrizio Giudici
  56.  *
  57.  **************************************************************************************************************************************************************/
  58. public interface Finder<T> extends Cloneable, Serializable
  59.   {
  60.     /***********************************************************************************************************************************************************
  61.      * A tag interface to mark objects which are meaningful sort criteria that can be passed to {@link Finder#sort(it.tidalwave.util.Finder.SortCriterion)}.
  62.      * In general, a {@code SortCriterion} is just a behaviourless and methodless object, that should be specifically handled by concrete implementations of
  63.      * {@link Finder}. The only exceptions are {@link InMemorySortCriterion} objects.
  64.      **********************************************************************************************************************************************************/
  65.     public static interface SortCriterion
  66.       {
  67.         public static final Class<SortCriterion> _SortCriterion_ = SortCriterion.class;

  68.         /** A special {@link SortCriterion} which indicates that no sort has been performed. */
  69.         public static final SortCriterion UNSORTED = (InMemorySortCriterion<Object>)(results, sortDirection) -> {};

  70.         public static final SortCriterion DEFAULT = UNSORTED;
  71.       }

  72.     /***********************************************************************************************************************************************************
  73.      * An interface that should be implemented by specific {@link SortCriterion} objects which are capable to implement by themselves the sorting of objects, by
  74.      * 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
  75.      * 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
  76.      * {@link it.tidalwave.util.spi.HierarchicFinderSupport} supports {@code FilterSortCriterion} objects out of the box.
  77.      **********************************************************************************************************************************************************/
  78.     public static interface InMemorySortCriterion<U> extends SortCriterion
  79.       {
  80.         /*******************************************************************************************************************************************************
  81.          * Performs the sort of results.
  82.          * @param  results        the list of objects to be sorted in place
  83.          ******************************************************************************************************************************************************/
  84.         public default void sort (@Nonnull final List<? extends U> results)
  85.           {
  86.             sort(results, SortDirection.ASCENDING);
  87.           }

  88.         /*******************************************************************************************************************************************************
  89.          * Performs the sort of results.
  90.          * @param  results        the list of objects to be sorted in place
  91.          * @param  sortDirection  the sort direction
  92.          ******************************************************************************************************************************************************/
  93.         // START SNIPPET: sort
  94.         public void sort (@Nonnull List<? extends U> results, @Nonnull SortDirection sortDirection);
  95.         // END SNIPPET: sort

  96.         /*******************************************************************************************************************************************************
  97.          * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
  98.          * @param <U>           the type of the objects to compare
  99.          * @param comparator    the {@code Comparator}
  100.          * @return              the new {@code SortCriterion}
  101.          ******************************************************************************************************************************************************/
  102.         @Nonnull
  103.         public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator)
  104.           {
  105.             return of(comparator, comparator.getClass().getSimpleName());
  106.           }

  107.         /*******************************************************************************************************************************************************
  108.          * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
  109.          * @param <U>           the type of the objects to compare
  110.          * @param comparator    the {@code Comparator}
  111.          * @param name          a name
  112.          * @return              the new {@code SortCriterion}
  113.          ******************************************************************************************************************************************************/
  114.         @Nonnull
  115.         public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator,
  116.                                                        @Nonnull final String name)
  117.           {
  118.             return new DefaultInMemorySortCriterion<>(comparator, name);
  119.           }

  120.         /*******************************************************************************************************************************************************
  121.          *
  122.          ******************************************************************************************************************************************************/
  123.         @AllArgsConstructor @ToString @EqualsAndHashCode
  124.         static class DefaultInMemorySortCriterion<U> implements Finder.InMemorySortCriterion<U>, Serializable
  125.           {
  126.             private static final long serialVersionUID = 76093596048395982L;

  127.             @Nonnull
  128.             private final Comparator<? super U> comparator;

  129.             @Nonnull
  130.             private final String name;

  131.             @Override
  132.             public void sort (@Nonnull final List<? extends U> results, @Nonnull final SortDirection sortDirection)
  133.               {
  134.                 results.sort((Comparator<U>)(o1, o2) -> comparator.compare(o1, o2) * sortDirection.intValue());
  135.               }
  136.           }
  137.       }

  138.     /***********************************************************************************************************************************************************
  139.      * An enumeration to define the direction of a sort (ascending or descending).
  140.      **********************************************************************************************************************************************************/
  141.     @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
  142.     public static enum SortDirection
  143.       {
  144.         ASCENDING(+1), DESCENDING(-1);

  145.         private final int intValue;

  146.         /** @return  +1 for ascending direction, -1 for descending */
  147.         public int intValue()
  148.           {
  149.             return intValue;
  150.           }
  151.       }

  152.     /***********************************************************************************************************************************************************
  153.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  154.      * @param   firstResult       the index of the first result to return
  155.      * @return                    the {@code Finder}
  156.      **********************************************************************************************************************************************************/
  157.     // START SNIPPET: from
  158.     @Nonnull
  159.       public Finder<T> from (/* @Nonnegative */ int firstResult);
  160.     // END SNIPPET: from

  161.     /***********************************************************************************************************************************************************
  162.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  163.      * @param   firstResult       the index of the first result to return
  164.      * @return                    the {@code Finder}
  165.      * @since   3.2-ALPHA-19
  166.      **********************************************************************************************************************************************************/
  167.     @Nonnull
  168.     public default Finder<T> from (@Nonnull final Optional<Integer> firstResult)
  169.       {
  170.         return firstResult.map(this::from).orElse(this);
  171.       }

  172.     /***********************************************************************************************************************************************************
  173.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  174.      * @param   firstResult       the index of the first result to return
  175.      * @return                    the {@code Finder}
  176.      * @since   3.2-ALPHA-22
  177.      **********************************************************************************************************************************************************/
  178.     @Nonnull
  179.     public default Finder<T> from (@Nonnull final OptionalInt firstResult)
  180.       {
  181.         return firstResult.isPresent() ? from(firstResult.getAsInt()) : this;
  182.       }

  183.     /***********************************************************************************************************************************************************
  184.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  185.      * @param   maxResults        the max number of results to return
  186.      * @return                    the {@code Finder}
  187.      **********************************************************************************************************************************************************/
  188.     // START SNIPPET: max
  189.     @Nonnull
  190.     public Finder<T> max (/* @Nonnegative */ int maxResults);
  191.     // END SNIPPET: max

  192.     /***********************************************************************************************************************************************************
  193.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  194.      * @param   maxResults        the max number of results to return
  195.      * @return                    the {@code Finder}
  196.      * @since   3.2-ALPHA-19
  197.      **********************************************************************************************************************************************************/
  198.     @Nonnull
  199.     public default Finder<T> max (@Nonnull final Optional<Integer> maxResults)
  200.       {
  201.         return maxResults.map(this::max).orElse(this);
  202.       }

  203.     /***********************************************************************************************************************************************************
  204.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  205.      * @param   maxResults        the max number of results to return
  206.      * @return                    the {@code Finder}
  207.      * @since   3.2-ALPHA-22
  208.      **********************************************************************************************************************************************************/
  209.     @Nonnull
  210.     public default Finder<T> max (@Nonnull final OptionalInt maxResults)
  211.       {
  212.         return maxResults.isPresent() ? max(maxResults.getAsInt()) : this;
  213.       }

  214.     /***********************************************************************************************************************************************************
  215.      * Tells the {@code Finder} that results should be created with the given context. This method can be called  multiple times; contexts are accumulated.
  216.      * Not all implementations of {@code Finder} have this working.
  217.      * @param  context            the context
  218.      * @return                    the {@code Finder}
  219.      **********************************************************************************************************************************************************/
  220.     @Nonnull
  221.     public default Finder<T> withContext (@Nonnull final Object context)
  222.       {
  223.         throw new UnsupportedOperationException("Not implemented.");
  224.       }

  225.     /***********************************************************************************************************************************************************
  226.      * Tells the {@code Finder} that the specified type of results is expected.
  227.      * Not all implementations of {@code Finder} have this working.
  228.      * @param   <U>               the static type
  229.      * @param   type              the dynamic type
  230.      * @return                    the {@code Finder}
  231.      **********************************************************************************************************************************************************/
  232.     @Nonnull
  233.     public default <U> Finder<U> ofType (@Nonnull final Class<U> type)
  234.       {
  235.         throw new UnsupportedOperationException("Not implemented.");
  236.       }

  237.     /***********************************************************************************************************************************************************
  238.      * Tells the {@code Finder} that results will be sorted according to the given criterion, in ascending direction.
  239.      * @param  criterion          the sort criterion
  240.      * @return                    the {@code Finder}
  241.      **********************************************************************************************************************************************************/
  242.     @Nonnull
  243.     public default Finder<T> sort (@Nonnull final SortCriterion criterion)
  244.       {
  245.         return sort(criterion, SortDirection.ASCENDING);
  246.       }

  247.     /***********************************************************************************************************************************************************
  248.      * Tells the {@code Finder} that results will be sorted according to the given criterion and direction.
  249.      * @param  criterion          the sort criterion
  250.      * @param  direction          the sort direction
  251.      * @return                    the {@code Finder}
  252.      **********************************************************************************************************************************************************/
  253.     @Nonnull
  254.     public Finder<T> sort (@Nonnull SortCriterion criterion, @Nonnull SortDirection direction);

  255.     /***********************************************************************************************************************************************************
  256.      * Performs the search and returns the found items.
  257.      * @return                    the searched items
  258.      **********************************************************************************************************************************************************/
  259.     // START SNIPPET: results
  260.     @Nonnull
  261.     public List<T> results();
  262.     // END SNIPPET: results

  263.     /***********************************************************************************************************************************************************
  264.      * Performs the search and returns the count of found items.
  265.      * @return                    the count of found items
  266.      **********************************************************************************************************************************************************/
  267.     // START SNIPPET: count
  268.     /* @Nonnegative */
  269.     public int count();
  270.     // END SNIPPET: count

  271.     /***********************************************************************************************************************************************************
  272.      * 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.
  273.      * @return                    the optional result
  274.      * @throws RuntimeException   if the search returned more than one single item
  275.      * @since 3.2-ALPHA-1 (previously in Finder8)
  276.      **********************************************************************************************************************************************************/
  277.     // START SNIPPET: optionalResult
  278.     @Nonnull
  279.     public default Optional<T> optionalResult()
  280.     // END SNIPPET: optionalResult
  281.       {
  282.         final var results = results();

  283.         if (results.size() > 1)
  284.           {
  285.             throw new RuntimeException(results.size() + " results, expected only one");
  286.           }

  287.         return results.stream().findFirst();
  288.       }

  289.     /***********************************************************************************************************************************************************
  290.      * Performs the search and returns only the first found item.
  291.      * @return            the first result
  292.      * @since 3.2-ALPHA-1 (previously in Finder8)
  293.      **********************************************************************************************************************************************************/
  294.     // START SNIPPET: optionalFirstResult
  295.     @Nonnull
  296.     public default Optional<T> optionalFirstResult()
  297.     // END SNIPPET: optionalFirstResult
  298.       {
  299.         return stream().findFirst();
  300.       }

  301.     /***********************************************************************************************************************************************************
  302.      * Returns a stream of results.
  303.      * @return    the stream
  304.      * @since     3.2-ALPHA-1 (previously in Finder8)
  305.      **********************************************************************************************************************************************************/
  306.     @Nonnull
  307.     public default Stream<T> stream()
  308.       {
  309.         return results().stream();
  310.       }

  311.     /***********************************************************************************************************************************************************
  312.      * Returns an iterator of results.
  313.      * @return                    the iterator
  314.      * @since 3.2-ALPHA-1 (previously in Finder8)
  315.      **********************************************************************************************************************************************************/
  316.     @Nonnull
  317.     public default Iterator<T> iterator()
  318.       {
  319.         return stream().iterator();
  320.       }

  321.     /***********************************************************************************************************************************************************
  322.      * Iterates through results.
  323.      * @param   consumer          the consumer
  324.      * @since   3.2-ALPHA-22
  325.      **********************************************************************************************************************************************************/
  326.     public default void forEach (@Nonnull final Consumer<? super T> consumer)
  327.       {
  328.         stream().forEach(consumer);
  329.       }

  330.     /***********************************************************************************************************************************************************
  331.      * 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.
  332.      * @return                    the found item
  333.      * @throws NotFoundException  if the search didn't find anything
  334.      * @throws RuntimeException   if the search returned more than one single item
  335.      * @deprecated                Use {@link #optionalResult()} instead
  336.      **********************************************************************************************************************************************************/
  337.     @Nonnull @Deprecated
  338.     public default T result()
  339.             throws NotFoundException
  340.       {
  341.         return optionalResult().orElseThrow(NotFoundException::new);
  342.       }

  343.     /***********************************************************************************************************************************************************
  344.      * Performs the search and returns only the first found item.
  345.      * @return                    the first found item
  346.      * @throws NotFoundException  if the search didn't find anything
  347.      * @deprecated                Use {@link #optionalFirstResult()} instead
  348.      **********************************************************************************************************************************************************/
  349.     @Nonnull @Deprecated
  350.     public default T firstResult()
  351.             throws NotFoundException
  352.       {
  353.         return optionalFirstResult().orElseThrow(NotFoundException::new);
  354.       }

  355.     /***********************************************************************************************************************************************************
  356.      * Returns an empty {@code Finder}.
  357.      * @param   <U>               the type of the {@code Finder}
  358.      * @return                    the empty {@code Finder}
  359.      * @since 3.2-ALPHA-1 (previously in HierarchicFinderSupport.emptyFinder())
  360.      **********************************************************************************************************************************************************/
  361.     @Nonnull
  362.     public static <U> Finder<U> empty()
  363.       {
  364.         return ofCloned(Collections.emptyList());
  365.       }

  366.     /***********************************************************************************************************************************************************
  367.      * 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
  368.      * on demand, use {@link #ofSupplier(Supplier)}. This method retrieves the full range of results that will be later segmented in compliance with the values
  369.      * 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.
  370.      * The alternate method {@link #ofProvider(BiFunction)} allows to access the 'from' and 'max' parameter, so only the required items need to be provided.
  371.      * @param   <U>               the type of the {@code Finder}
  372.      * @param   items             the objects to wrap
  373.      * @return                    the wrapped {@code Finder}
  374.      * @see                       #ofSupplier(Supplier)
  375.      * @see                       #ofProvider(BiFunction)
  376.      * @since   3.2-ALPHA-1
  377.      **********************************************************************************************************************************************************/
  378.     // START SNIPPET: ofCloned
  379.     @Nonnull
  380.     public static <U> Finder<U> ofCloned (@Nonnull final Collection<? extends U> items)
  381.     // END SNIPPET: ofCloned
  382.       {
  383.         return new ArrayListFinder<>(items);
  384.       }

  385.     /***********************************************************************************************************************************************************
  386.      * 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
  387.      * 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
  388.      * is already available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows to access the 'from' and 'max'
  389.      * parameter, so only the required items need to be provided.
  390.      * @param   <U>               the type of the {@code Finder}
  391.      * @param   supplier          the supplier
  392.      * @return                    the wrapped {@code Finder}
  393.      * @see                       #ofCloned(Collection)
  394.      * @see                       #ofProvider(BiFunction)
  395.      * @since   3.2-ALPHA-15
  396.      **********************************************************************************************************************************************************/
  397.     // START SNIPPET: ofsupplier
  398.     @Nonnull
  399.     public static <U> Finder<U> ofSupplier (@Nonnull final Supplier<? extends Collection<? extends U>> supplier)
  400.     // END SNIPPET: ofsupplier
  401.       {
  402.         return new SupplierFinder<>(supplier);
  403.       }

  404.     /***********************************************************************************************************************************************************
  405.      * 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
  406.      * results. The collection will be cloned after being supplied.
  407.      * @param   <U>               the type of the {@code Finder}
  408.      * @param   provider          the function providing results
  409.      * @return                    the wrapped {@code Finder}
  410.      * @see                       #ofCloned(Collection)
  411.      * @see                       #ofSupplier(Supplier)
  412.      * @since   3.2-ALPHA-15
  413.      **********************************************************************************************************************************************************/
  414.     // START SNIPPET: ofProvider
  415.     @Nonnull
  416.     public static <U> Finder<U> ofProvider (@Nonnull final BiFunction<Integer, Integer, ? extends Collection<? extends U>> provider)
  417.     // END SNIPPET: ofProvider
  418.       {
  419.         return new ProviderFinder<>(provider);
  420.       }

  421.     /***********************************************************************************************************************************************************
  422.      * 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
  423.      * function.
  424.      * @param   <U>               the type of the {@code Finder}
  425.      * @param   <V>               the type of the delegate {@code Finder}
  426.      * @param   delegate          the delegate finder
  427.      * @param   mapper            the mapper function
  428.      * @return                    the wrapped {@code Finder}
  429.      * @since   3.2-ALPHA-15
  430.      **********************************************************************************************************************************************************/
  431.     // START SNIPPET: mapping
  432.     @Nonnull
  433.     public static <U, V> Finder<U> mapping (@Nonnull final Finder<V> delegate, @Nonnull final Function<? super V, ? extends U> mapper)
  434.     // END SNIPPET: mapping
  435.       {
  436.         return new MappingFinder<>(delegate, mapper);
  437.       }
  438.   }