Finder.java

  1. /*
  2.  * *************************************************************************************************************************************************************
  3.  *
  4.  * TheseFoolishThings: Miscellaneous utilities
  5.  * http://tidalwave.it/projects/thesefoolishthings
  6.  *
  7.  * Copyright (C) 2009 - 2024 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 javax.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.  * @it.tidalwave.javadoc.draft
  57.  *
  58.  **************************************************************************************************************************************************************/
  59. public interface Finder<T> extends Cloneable, Serializable
  60.   {
  61.     /***********************************************************************************************************************************************************
  62.      * A tag interface to mark objects which are meaningful sort criteria that can be passed to
  63.      * {@link Finder#sort(it.tidalwave.util.Finder.SortCriterion)}. In general, a {@code SortCriterion} is just a
  64.      * 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.         /** A special {@link SortCriterion} which indicates that no sort has been performed. */
  71.         public static final SortCriterion UNSORTED = (InMemorySortCriterion<Object>)(results, sortDirection) -> {};

  72.         public static final SortCriterion DEFAULT = UNSORTED;
  73.       }

  74.     /***********************************************************************************************************************************************************
  75.      * An interface that should be implemented by specific {@link SortCriterion} objects which are capable to implement
  76.      * by themselves the sorting of objects, by post-processing an existing collection of objects. While this is often
  77.      * convenient, it is possible for it to be inefficient in cases in which the original source of objects is capable
  78.      * to perform the sort in an optimized way (e.g. an SQL database by means of {@code ORDER BY}). The facility class
  79.        * {@link it.tidalwave.util.spi.HierarchicFinderSupport} supports {@code FilterSortCriterion} objects out of the box.
  80.      **********************************************************************************************************************************************************/
  81.     public static interface InMemorySortCriterion<U> extends SortCriterion
  82.       {
  83.         /***************************************************************************************************************
  84.          *
  85.          * Performs the sort of results.
  86.          *
  87.          * @param  results        the list of objects to be sorted in place
  88.          *
  89.          **************************************************************************************************************/
  90.         public default void sort (@Nonnull final List<? extends U> results)
  91.           {
  92.             sort(results, SortDirection.ASCENDING);
  93.           }

  94.         /***************************************************************************************************************
  95.          *
  96.          * Performs the sort of results.
  97.          *
  98.          * @param  results        the list of objects to be sorted in place
  99.          * @param  sortDirection  the sort direction
  100.          *
  101.          **************************************************************************************************************/
  102.         // START SNIPPET: sort
  103.         public void sort (@Nonnull List<? extends U> results, @Nonnull SortDirection sortDirection);
  104.         // END SNIPPET: sort

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

  119.         /***************************************************************************************************************
  120.          *
  121.          * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
  122.          *
  123.          * @param <U>           the type of the objects to compare
  124.          * @param comparator    the {@code Comparator}
  125.          * @param name          a name
  126.          * @return              the new {@code SortCriterion}
  127.          *
  128.          **************************************************************************************************************/
  129.         @Nonnull
  130.         public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator,
  131.                                                        @Nonnull final String name)
  132.           {
  133.             return new DefaultInMemorySortCriterion<>(comparator, name);
  134.           }

  135.         /***************************************************************************************************************
  136.          *
  137.          **************************************************************************************************************/
  138.         @AllArgsConstructor @ToString @EqualsAndHashCode
  139.         static class DefaultInMemorySortCriterion<U> implements Finder.InMemorySortCriterion<U>, Serializable
  140.           {
  141.             private static final long serialVersionUID = 76093596048395982L;

  142.             @Nonnull
  143.             private final Comparator<? super U> comparator;

  144.             @Nonnull
  145.             private final String name;

  146.             @Override
  147.             public void sort (@Nonnull final List<? extends U> results, @Nonnull final SortDirection sortDirection)
  148.               {
  149.                 results.sort((Comparator<U>)(o1, o2) -> comparator.compare(o1, o2) * sortDirection.intValue());
  150.               }
  151.           }
  152.       }

  153.     /***********************************************************************************************************************************************************
  154.      * An enumeration to define the direction of a sort (ascending or descending).
  155.      *
  156.      * @it.tidalwave.javadoc.stable
  157.      **********************************************************************************************************************************************************/
  158.     @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
  159.     public static enum SortDirection
  160.       {
  161.         ASCENDING(+1), DESCENDING(-1);

  162.         private final int intValue;

  163.         /** @return  +1 for ascending direction, -1 for descending */
  164.         public int intValue()
  165.           {
  166.             return intValue;
  167.           }
  168.       }

  169.     /***********************************************************************************************************************************************************
  170.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  171.      *
  172.      * @param   firstResult    the index of the first result to return
  173.      * @return                 the {@code Finder}
  174.      **********************************************************************************************************************************************************/
  175.     // START SNIPPET: from
  176.     @Nonnull
  177.       public Finder<T> from (@Nonnegative int firstResult);
  178.     // END SNIPPET: from

  179.     /***********************************************************************************************************************************************************
  180.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  181.      *
  182.      * @param   firstResult    the index of the first result to return
  183.      * @return                 the {@code Finder}
  184.      * @since   3.2-ALPHA-19
  185.      **********************************************************************************************************************************************************/
  186.     @Nonnull
  187.     public default Finder<T> from (@Nonnull final Optional<Integer> firstResult)
  188.       {
  189.         return firstResult.map(this::from).orElse(this);
  190.       }

  191.     /***********************************************************************************************************************************************************
  192.      * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
  193.      *
  194.      * @param   firstResult    the index of the first result to return
  195.      * @return                 the {@code Finder}
  196.      * @since   3.2-ALPHA-22
  197.      **********************************************************************************************************************************************************/
  198.     @Nonnull
  199.     public default Finder<T> from (@Nonnull final OptionalInt firstResult)
  200.       {
  201.         return firstResult.isPresent() ? from(firstResult.getAsInt()) : this;
  202.       }

  203.     /***********************************************************************************************************************************************************
  204.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  205.      *
  206.      * @param   maxResults    the max number of results to return
  207.      * @return                the {@code Finder}
  208.      **********************************************************************************************************************************************************/
  209.     // START SNIPPET: max
  210.     @Nonnull
  211.     public Finder<T> max (@Nonnegative int maxResults);
  212.     // END SNIPPET: max

  213.     /***********************************************************************************************************************************************************
  214.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  215.      *
  216.      * @param   maxResults    the max number of results to return
  217.      * @return                the {@code Finder}
  218.      * @since   3.2-ALPHA-19
  219.      **********************************************************************************************************************************************************/
  220.     @Nonnull
  221.     public default Finder<T> max (@Nonnull final Optional<Integer> maxResults)
  222.       {
  223.         return maxResults.map(this::max).orElse(this);
  224.       }

  225.     /***********************************************************************************************************************************************************
  226.      * Tells the {@code Finder} that only a maximum number of found items will be returned.
  227.      *
  228.      * @param   maxResults    the max number of results to return
  229.      * @return                the {@code Finder}
  230.      * @since   3.2-ALPHA-22
  231.      **********************************************************************************************************************************************************/
  232.     @Nonnull
  233.     public default Finder<T> max (@Nonnull final OptionalInt maxResults)
  234.       {
  235.         return maxResults.isPresent() ? max(maxResults.getAsInt()) : this;
  236.       }

  237.     /***********************************************************************************************************************************************************
  238.      * Tells the {@code Finder} that results should be created with the given context. This method can be called
  239.      * multiple times; contexts are accumulated.
  240.      *
  241.      * @param  context    the context
  242.      * @return            the {@code Finder}
  243.      **********************************************************************************************************************************************************/
  244.     @Nonnull
  245.     public default Finder<T> withContext (@Nonnull final Object context)
  246.       {
  247.         throw new UnsupportedOperationException("Not implemented yet.");
  248.       }

  249.     /***********************************************************************************************************************************************************
  250.      * Tells the {@code Finder} that the specified type of results is expected.
  251.      *
  252.      * @param <U>  the static type
  253.      * @param   type          the dynamic type
  254.      * @return                the {@code Finder}
  255.      **********************************************************************************************************************************************************/
  256.     @Nonnull
  257.     public default <U> Finder<U> ofType (@Nonnull final Class<U> type)
  258.       {
  259.         throw new UnsupportedOperationException("Not implemented yet.");
  260.       }

  261.     /***********************************************************************************************************************************************************
  262.      * Tells the {@code Finder} that results will be sorted according to the given criterion, in ascending direction.
  263.      *
  264.      * @param  criterion  the sort criterion
  265.      * @return            the {@code Finder}
  266.      **********************************************************************************************************************************************************/
  267.     @Nonnull
  268.     public default Finder<T> sort (@Nonnull final SortCriterion criterion)
  269.       {
  270.         return sort(criterion, SortDirection.ASCENDING);
  271.       }


  272.     /***********************************************************************************************************************************************************
  273.      * Tells the {@code Finder} that results will be sorted according to the given criterion and direction.
  274.      *
  275.      * @param  criterion  the sort criterion
  276.      * @param  direction  the sort direction
  277.      * @return            the {@code Finder}
  278.      **********************************************************************************************************************************************************/
  279.     @Nonnull
  280.     public Finder<T> sort (@Nonnull SortCriterion criterion, @Nonnull SortDirection direction);

  281.     /***********************************************************************************************************************************************************
  282.      * Performs the search and returns the found items.
  283.      *
  284.      * @return            the searched items
  285.      **********************************************************************************************************************************************************/
  286.     // START SNIPPET: results
  287.     @Nonnull
  288.     public List<T> results();
  289.     // END SNIPPET: results

  290.     /***********************************************************************************************************************************************************
  291.      * Performs the search and returns the count of found items.
  292.      *
  293.      * @return            the count of found items
  294.      **********************************************************************************************************************************************************/
  295.     // START SNIPPET: count
  296.     @Nonnegative
  297.     public int count();
  298.     // END SNIPPET: count

  299.     /***********************************************************************************************************************************************************
  300.      * Performs the search assuming that it will return a single item and returns it. This method fails if the search
  301.      * returns more than one single item.
  302.      *
  303.      * @return            the optional result
  304.      * @throws RuntimeException   if the search returned more than one single item
  305.      *
  306.      * @since 3.2-ALPHA-1 (previously in Finder8)
  307.      **********************************************************************************************************************************************************/
  308.     // START SNIPPET: optionalResult
  309.     @Nonnull
  310.     public default Optional<T> optionalResult()
  311.     // END SNIPPET: optionalResult
  312.       {
  313.         final var results = results();

  314.         if (results.size() > 1)
  315.           {
  316.             throw new RuntimeException(results.size() + " results, expected only one");
  317.           }

  318.         return results.stream().findFirst();
  319.       }

  320.     /***********************************************************************************************************************************************************
  321.      * Performs the search and returns only the first found item.
  322.      *
  323.      * @return            the first result
  324.      * @since 3.2-ALPHA-1 (previously in Finder8)
  325.      **********************************************************************************************************************************************************/
  326.     // START SNIPPET: optionalFirstResult
  327.     @Nonnull
  328.     public default Optional<T> optionalFirstResult()
  329.     // END SNIPPET: optionalFirstResult
  330.       {
  331.         return stream().findFirst();
  332.       }

  333.     /***********************************************************************************************************************************************************
  334.      * Returns a stream of results.
  335.      *
  336.      * @return    the stream
  337.      * @since 3.2-ALPHA-1 (previously in Finder8)
  338.      **********************************************************************************************************************************************************/
  339.     @Nonnull
  340.     public default Stream<T> stream()
  341.       {
  342.         return results().stream();
  343.       }

  344.     /***********************************************************************************************************************************************************
  345.      * Returns an iterator of results.
  346.      *
  347.      * @return    the iterator
  348.      * @since 3.2-ALPHA-1 (previously in Finder8)
  349.      **********************************************************************************************************************************************************/
  350.     @Nonnull
  351.     public default Iterator<T> iterator()
  352.       {
  353.         return stream().iterator();
  354.       }

  355.     /***********************************************************************************************************************************************************
  356.      * Iterates through results.
  357.      *
  358.      * @param   consumer  the consumer
  359.      * @since 3.2-ALPHA-22
  360.      **********************************************************************************************************************************************************/
  361.     public default void forEach (@Nonnull final Consumer<? super T> consumer)
  362.       {
  363.         stream().forEach(consumer);
  364.       }

  365.     /***********************************************************************************************************************************************************
  366.      * Performs the search assuming that it will return a single item and returns it. This method fails if the search
  367.      * returns more than one single item.
  368.      *
  369.      * @return                    the found item
  370.      * @throws NotFoundException  if the search didn't find anything
  371.      * @throws RuntimeException   if the search returned more than one single item
  372.      * @deprecated                Use {@link #optionalResult()} instead
  373.      **********************************************************************************************************************************************************/
  374.     @Nonnull @Deprecated
  375.     public default T result()
  376.             throws NotFoundException, RuntimeException
  377.       {
  378.         return optionalResult().orElseThrow(NotFoundException::new);
  379.       }

  380.     /***********************************************************************************************************************************************************
  381.      * Performs the search and returns only the first found item.
  382.      *
  383.      * @return                    the first found item
  384.      * @throws NotFoundException  if the search didn't find anything
  385.      * @deprecated                Use {@link #optionalFirstResult()} instead
  386.      **********************************************************************************************************************************************************/
  387.     @Nonnull @Deprecated
  388.     public default T firstResult()
  389.             throws NotFoundException
  390.       {
  391.         return optionalFirstResult().orElseThrow(NotFoundException::new);
  392.       }

  393.     /***********************************************************************************************************************************************************
  394.      * Returns an empty {@code Finder}.
  395.      *
  396.      * @param   <U>     the type of the {@code Finder}
  397.      * @return          the empty {@code Finder}
  398.      * @since 3.2-ALPHA-1 (previously in HierarchicFinderSupport.emptyFinder())
  399.      **********************************************************************************************************************************************************/
  400.     @Nonnull
  401.     public static <U> Finder<U> empty()
  402.       {
  403.         return ofCloned(Collections.emptyList());
  404.       }

  405.     /***********************************************************************************************************************************************************
  406.      * Returns a wrapped {@code Finder} on a given collection of elements. The collection is cloned and will be
  407.      * immutable.
  408.      * If you need to compute the collection on demand, use {@link #ofSupplier(Supplier)}.
  409.      * This method retrieves the full range of results that will be later segmented in compliance with the values
  410.      * specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results is already
  411.      * available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows
  412.      * to access the 'from' and 'max' parameter, so only the required items need to be provided.
  413.      *
  414.      * @param   <U>     the type of the {@code Finder}
  415.      * @param   items   the objects to wrap
  416.      * @return          the wrapped {@code Finder}
  417.      * @see             #ofSupplier(Supplier)
  418.      * @see             #ofProvider(BiFunction)
  419.      * @since 3.2-ALPHA-1
  420.      **********************************************************************************************************************************************************/
  421.     // START SNIPPET: ofCloned
  422.     @Nonnull
  423.     public static <U> Finder<U> ofCloned (@Nonnull final Collection<? extends U> items)
  424.     // END SNIPPET: ofCloned
  425.       {
  426.         return new ArrayListFinder<>(items);
  427.       }

  428.     /***********************************************************************************************************************************************************
  429.      * Returns a wrapped {@code Finder} on a given supplier. The collection will be cloned after being supplied.
  430.      * This method retrieves the full range of results that will be later segmented in compliance with the values
  431.      * specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results is already
  432.      * available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows
  433.      * to access the 'from' and 'max' parameter, so only the required items need to be provided.
  434.      *
  435.      * @param   <U>       the type of the {@code Finder}
  436.      * @param   supplier  the supplier
  437.      * @return            the wrapped {@code Finder}
  438.      * @see               #ofCloned(Collection)
  439.      * @see               #ofProvider(BiFunction)
  440.      * @since 3.2-ALPHA-15
  441.      **********************************************************************************************************************************************************/
  442.     // START SNIPPET: ofsupplier
  443.     @Nonnull
  444.     public static <U> Finder<U> ofSupplier (@Nonnull final Supplier<? extends Collection<? extends U>> supplier)
  445.     // END SNIPPET: ofsupplier
  446.       {
  447.         return new SupplierFinder<>(supplier);
  448.       }

  449.     /***********************************************************************************************************************************************************
  450.      * Returns a wrapped {@code Finder} on a given function to provide results. The function receives the 'from' and
  451.      * 'max' arguments to select a subrange of the results. The collection will be cloned after being supplied.
  452.      *
  453.      * @param   <U>       the type of the {@code Finder}
  454.      * @param   provider  the function providing results
  455.      * @return            the wrapped {@code Finder}
  456.      * @see               #ofCloned(Collection)
  457.      * @see               #ofSupplier(Supplier)
  458.      * @since 3.2-ALPHA-15
  459.      **********************************************************************************************************************************************************/
  460.     // START SNIPPET: ofProvider
  461.     @Nonnull
  462.     public static <U> Finder<U> ofProvider (
  463.             @Nonnull final BiFunction<Integer, Integer, ? extends Collection<? extends U>> provider)
  464.     // END SNIPPET: ofProvider
  465.       {
  466.         return new ProviderFinder<>(provider);
  467.       }

  468.     /***********************************************************************************************************************************************************
  469.      * Returns a mapping {@code Finder} on a given delegate {@code Finder}. The mapper finder provides the same
  470.      * results as the delegate, transformed by a mapper function.
  471.      *
  472.      * @param   <U>       the type of the {@code Finder}
  473.      * @param   <V>       the type of the delegate {@code Finder}
  474.      * @param   delegate  the delegate finder
  475.      * @param   mapper the mapper function
  476.      * @return            the wrapped {@code Finder}
  477.      * @since 3.2-ALPHA-15
  478.      **********************************************************************************************************************************************************/
  479.     // START SNIPPET: mapping
  480.     @Nonnull
  481.     public static <U, V> Finder<U> mapping (@Nonnull final Finder<V> delegate,
  482.                                             @Nonnull final Function<? super V, ? extends U> mapper)
  483.     // END SNIPPET: mapping
  484.       {
  485.         return new MappingFinder<>(delegate, mapper);
  486.       }
  487.   }