FinderSupport.java

  1. /*
  2.  * *********************************************************************************************************************
  3.  *
  4.  * TheseFoolishThings: Miscellaneous utilities
  5.  * http://tidalwave.it/projects/thesefoolishthings
  6.  *
  7.  * Copyright (C) 2009 - 2021 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
  12.  * the License. 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
  17.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  18.  * specific language governing permissions and limitations under the License.
  19.  *
  20.  * *********************************************************************************************************************
  21.  *
  22.  * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
  23.  * git clone https://github.com/tidalwave-it/thesefoolishthings-src
  24.  *
  25.  * *********************************************************************************************************************
  26.  */
  27. package it.tidalwave.util.spi;

  28. import java.lang.reflect.Constructor;
  29. import javax.annotation.Nonnegative;
  30. import javax.annotation.Nonnull;
  31. import java.util.ArrayList;
  32. import java.util.Collections;
  33. import java.util.List;
  34. import java.util.concurrent.CopyOnWriteArrayList;
  35. import it.tidalwave.util.Finder;
  36. import lombok.AccessLevel;
  37. import lombok.AllArgsConstructor;
  38. import lombok.Getter;
  39. import lombok.RequiredArgsConstructor;
  40. import lombok.ToString;
  41. import lombok.extern.slf4j.Slf4j;

  42. /***********************************************************************************************************************
  43.  *
  44.  * A support class for implementing a {@link Finder}. Subclasses only need to implement the {@link #computeResults()}
  45.  * method where <i>raw</i> results are retrieved (with raw we mean that they shouldn't be filtered or sorted, as
  46.  * post-processing will be performed by this class) and a clone constructor.
  47.  *
  48.  * If you don't need to extend the {@link Finder} with extra methods, please use the simplified
  49.  * {@link SimpleFinderSupport}.
  50.  *
  51.  * @author Fabrizio Giudici
  52.  * @it.tidalwave.javadoc.draft
  53.  *
  54.  **********************************************************************************************************************/
  55. @Slf4j @AllArgsConstructor(access = AccessLevel.PRIVATE) @ToString
  56. public class FinderSupport<TYPE, EXTENDED_FINDER extends Finder<TYPE>> implements Finder<TYPE>
  57.   {
  58.     private static final long serialVersionUID = 2467809593956684L;

  59.     @RequiredArgsConstructor
  60.     static class Sorter<Type>
  61.       {
  62.         @Nonnull
  63.         private final InMemorySortCriterion<Type> sortCriterion;

  64.         @Nonnull
  65.         private final SortDirection sortDirection;

  66.         public void sort (@Nonnull final List<? extends Type> results)
  67.           {
  68.             sortCriterion.sort(results, sortDirection);
  69.           }
  70.       }

  71.     private static final String MESSAGE =
  72.           "Since version 2.0, Implementations of Finder must have a clone constructor such as "
  73.         + "MyFinder(MyFinder other, Object override). This means that they can't be implemented by anonymous or inner, "
  74.         + "non static classes. See the javadoc for further information. Could not find constructor: ";

  75.     @Nonnull
  76.     private final String name;

  77.     @Nonnegative
  78.     private final int firstResult;

  79.     @Nonnegative
  80.     private final int maxResults;

  81.     @Nonnull @Getter(AccessLevel.PROTECTED)
  82.     private final List<Object> contexts;

  83.     @Nonnull
  84.     private final List<Sorter<TYPE>> sorters;

  85.     private static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE;

  86.     /*******************************************************************************************************************
  87.      *
  88.      * Creates an instance with the given name (that will be used for diagnostics).
  89.      *
  90.      * @param  name   the name
  91.      *
  92.      ******************************************************************************************************************/
  93.     protected FinderSupport (@Nonnull final String name)
  94.       {
  95.         this.name = name;
  96.         this.firstResult = 0;
  97.         this.maxResults = DEFAULT_MAX_RESULTS;
  98.         this.sorters = new ArrayList<>();
  99.         this.contexts = Collections.emptyList();
  100.         checkSubClass();
  101.       }

  102.     /*******************************************************************************************************************
  103.      *
  104.      * Default constructor.
  105.      *
  106.      ******************************************************************************************************************/
  107.     protected FinderSupport()
  108.       {
  109.         this.name = getClass().getName();
  110.         this.firstResult = 0;
  111.         this.maxResults = DEFAULT_MAX_RESULTS;
  112.         this.sorters = new ArrayList<>();
  113.         this.contexts = Collections.emptyList();
  114.         checkSubClass();
  115.       }

  116.     /*******************************************************************************************************************
  117.      *
  118.      * Clone constructor for subclasses.
  119.      *
  120.      * @param   other     the other instance to clone
  121.      * @param   holder    the holder object
  122.      *
  123.      ******************************************************************************************************************/
  124.     protected FinderSupport (@Nonnull final FinderSupport<TYPE, EXTENDED_FINDER> other, @Nonnull final Object holder)
  125.       {
  126.         log.trace("FinderSupport({}, {})", other, holder);
  127.         final FinderSupport<TYPE, EXTENDED_FINDER> source = getSource(FinderSupport.class, other, holder);
  128.         this.name = source.name;
  129.         this.firstResult = source.firstResult;
  130.         this.maxResults = source.maxResults;
  131.         this.sorters = source.sorters;
  132.         this.contexts = source.contexts; // it's always unmodifiable
  133.       }

  134.     /*******************************************************************************************************************
  135.      *
  136.      * This method throws an exception since a {@code Finder} extending this class must be cloned with
  137.      * {@link #clonedWith(Object)}.
  138.      *
  139.      * @see #clonedWith(Object)
  140.      * @deprecated
  141.      *
  142.      ******************************************************************************************************************/
  143.     @Override @Nonnull
  144.     public final FinderSupport<TYPE, EXTENDED_FINDER> clone()
  145.       {
  146.         throw new UnsupportedOperationException("\"FinderSupport.clone() no more supported");
  147.       }

  148.     /*******************************************************************************************************************
  149.      *
  150.      * Create a clone of this object calling the special copy constructor by reflection.
  151.      *
  152.      * @param   override  the override object
  153.      * @return            the clone
  154.      *
  155.      ******************************************************************************************************************/
  156.     @Nonnull
  157.     protected EXTENDED_FINDER clonedWith (@Nonnull final Object override)
  158.       {
  159.         try
  160.           {
  161.             final Constructor<? extends FinderSupport> constructor = getCloneConstructor();
  162.             constructor.setAccessible(true);
  163.             return (EXTENDED_FINDER)constructor.newInstance(this, override);
  164.           }
  165.         catch (Exception e)
  166.           {
  167.             throw new RuntimeException(e);
  168.           }
  169.       }

  170.     /*******************************************************************************************************************
  171.      *
  172.      * Create a clone of this object calling the special clone constructor by reflection.
  173.      *
  174.      * @param   override  the override object
  175.      * @return            the clone
  176.      * @deprecated        Use {@link #clonedWith(Object)} instead.
  177.      *
  178.      ******************************************************************************************************************/
  179.     @Nonnull @Deprecated
  180.     protected EXTENDED_FINDER clone (@Nonnull final Object override)
  181.       {
  182.         return clonedWith(override);
  183.       }

  184.     /*******************************************************************************************************************
  185.      *
  186.      * {@inheritDoc}
  187.      *
  188.      ******************************************************************************************************************/
  189.     @Override @Nonnull
  190.     public EXTENDED_FINDER from (@Nonnegative final int firstResult)
  191.       {
  192.         return clonedWith(new FinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
  193.       }

  194.     /*******************************************************************************************************************
  195.      *
  196.      * {@inheritDoc}
  197.      *
  198.      ******************************************************************************************************************/
  199.     @Override @Nonnull
  200.     public EXTENDED_FINDER max (@Nonnegative final int maxResults)
  201.       {
  202.         return clonedWith(new FinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
  203.       }

  204.     /*******************************************************************************************************************
  205.      *
  206.      * {@inheritDoc}
  207.      *
  208.      ******************************************************************************************************************/
  209.     @Override @Nonnull
  210.     public EXTENDED_FINDER withContext (@Nonnull final Object context)
  211.       {
  212.         final List<Object> contexts = concat(this.contexts, context);
  213.         return clonedWith(new FinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
  214.       }

  215.     /*******************************************************************************************************************
  216.      *
  217.      * {@inheritDoc}
  218.      *
  219.      ******************************************************************************************************************/
  220.     @Override @Nonnull
  221.     public <ANOTHER_TYPE> Finder<ANOTHER_TYPE> ofType (@Nonnull final Class<ANOTHER_TYPE> type)
  222.       {
  223.         throw new UnsupportedOperationException("Must be eventually implemented by subclasses.");
  224.       }

  225.     /*******************************************************************************************************************
  226.      *
  227.      * {@inheritDoc}
  228.      *
  229.      ******************************************************************************************************************/
  230.     @Override @Nonnull
  231.     public EXTENDED_FINDER sort (@Nonnull final SortCriterion criterion, @Nonnull final SortDirection direction)
  232.       {
  233.         if (criterion instanceof Finder.InMemorySortCriterion)
  234.           {
  235.             final List<Sorter<TYPE>> sorters = concat(this.sorters,
  236.                                                       new Sorter<>((InMemorySortCriterion<TYPE>)criterion, direction));
  237.             return clonedWith(new FinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
  238.           }

  239.         final String template = "%s does not implement %s - you need to subclass Finder and override sort()";
  240.         final String message = String.format(template, criterion, InMemorySortCriterion.class);
  241.         throw new UnsupportedOperationException(message);
  242.       }

  243.     /*******************************************************************************************************************
  244.      *
  245.      * {@inheritDoc}
  246.      *
  247.      ******************************************************************************************************************/
  248.     @Override @Nonnull
  249.     public final EXTENDED_FINDER sort (@Nonnull final SortCriterion criterion)
  250.       {
  251.         return sort(criterion, SortDirection.ASCENDING);
  252.       }

  253.     /*******************************************************************************************************************
  254.      *
  255.      * {@inheritDoc}
  256.      *
  257.      ******************************************************************************************************************/
  258.     @Override @Nonnull
  259.     public List<? extends TYPE> results()
  260.       {
  261.         return computeNeededResults();
  262.       }

  263.     /*******************************************************************************************************************
  264.      *
  265.      * {@inheritDoc}
  266.      *
  267.      ******************************************************************************************************************/
  268.     @Override @Nonnegative
  269.     public int count()
  270.       {
  271.         return computeNeededResults().size();
  272.       }

  273.     /*******************************************************************************************************************
  274.      *
  275.      * Subclasses can implement this method where *all* the raw results must be actually retrieved.
  276.      *
  277.      * @return  the unprocessed results
  278.      *
  279.      ******************************************************************************************************************/
  280.     // START SNIPPET: computeResults
  281.     @Nonnull
  282.     protected List<? extends TYPE> computeResults()
  283.     // END SNIPPET: computeResults
  284.       {
  285.         throw new UnsupportedOperationException("You must implement me!");
  286.       }

  287.     /*******************************************************************************************************************
  288.      *
  289.      * Subclasses can implement this method where *only the requested* raw results must be retrieved.
  290.      *
  291.      * @return  the unprocessed results
  292.      *
  293.      ******************************************************************************************************************/
  294.     // START SNIPPET: computeNeededResults
  295.     @Nonnull
  296.     protected List<? extends TYPE> computeNeededResults()
  297.     // END SNIPPET: computeNeededResults
  298.       {
  299.         log.trace("computeNeededResults() - {}", this);
  300.         List<? extends TYPE> results = computeResults();

  301.         // First sort and then extract the sublist
  302.         for (final Sorter<TYPE> sorter : sorters)
  303.           {
  304.             log.trace(">>>> sorting with {}...", sorter);
  305.             sorter.sort(results);
  306.           }

  307.         final int toIndex = (int)Math.min(results.size(), (long)firstResult + (long)maxResults);

  308.         if (firstResult > toIndex)
  309.           {
  310.             return new CopyOnWriteArrayList<>();
  311.           }

  312.         results = results.subList(firstResult, toIndex);

  313.         return results;
  314.       }

  315.     /*******************************************************************************************************************
  316.      *
  317.      * A utility method used by the copy constructor (see general documentation). If the override object is strictly
  318.      * of the specified type, it is returned; otherwise the other object is returned.
  319.      *
  320.      * @param <T>       the static type of the source
  321.      * @param type      the dynamic type of the source
  322.      * @param other     the other finder
  323.      * @param override  the holder object
  324.      * @return          the override or other
  325.      *
  326.      ******************************************************************************************************************/
  327.     @Nonnull
  328.     protected static <T> T getSource (final Class<T> type, @Nonnull final T other, @Nonnull final Object override)
  329.       {
  330.         return override.getClass().equals(type) ? type.cast(override) : other;
  331.       }

  332.     /*******************************************************************************************************************
  333.      *
  334.      *
  335.      ******************************************************************************************************************/
  336.     @Nonnull
  337.     private static <T> List<T> concat (@Nonnull final List<T> list, @Nonnull final T item)
  338.       {
  339.         final List<T> result = new ArrayList<>(list);
  340.         result.add(item);
  341.         return Collections.unmodifiableList(result);
  342.       }

  343.     /*******************************************************************************************************************
  344.      *
  345.      *
  346.      ******************************************************************************************************************/
  347.     @Nonnull
  348.     private Constructor<? extends FinderSupport> getCloneConstructor()
  349.       throws SecurityException, NoSuchMethodException
  350.       {
  351.         return getClass().getConstructor(getClass(), Object.class);
  352.       }

  353.     /*******************************************************************************************************************
  354.      *
  355.      *
  356.      ******************************************************************************************************************/
  357.     private void checkSubClass()
  358.       {
  359.         try
  360.           {
  361.             getCloneConstructor();
  362.           }
  363.         catch (SecurityException | NoSuchMethodException e)
  364.           {
  365.             throw new ExceptionInInitializerError(MESSAGE + e.getMessage());
  366.           }
  367.       }
  368.   }