Skip to contentMethod: ofCloned(Collection)
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
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;
28:
29: import javax.annotation.Nonnegative;
30: import javax.annotation.Nonnull;
31: import java.util.Collection;
32: import java.util.Collections;
33: import java.util.Comparator;
34: import java.util.Iterator;
35: import java.util.List;
36: import java.util.Optional;
37: import java.util.OptionalInt;
38: import java.util.function.BiFunction;
39: import java.util.function.Consumer;
40: import java.util.function.Function;
41: import java.util.function.Supplier;
42: import java.util.stream.Stream;
43: import java.io.Serializable;
44: import it.tidalwave.util.impl.finder.ArrayListFinder;
45: import it.tidalwave.util.impl.finder.MappingFinder;
46: import it.tidalwave.util.impl.finder.ProviderFinder;
47: import it.tidalwave.util.impl.finder.SupplierFinder;
48: import lombok.AccessLevel;
49: import lombok.AllArgsConstructor;
50: import lombok.EqualsAndHashCode;
51: import lombok.RequiredArgsConstructor;
52: import lombok.ToString;
53:
54: /***********************************************************************************************************************
55: *
56: * A factory for providing results of a search. {@code Finder} implementations must be <em>immutable</em>.
57: *
58: * @author Fabrizio Giudici
59: * @it.tidalwave.javadoc.draft
60: *
61: **********************************************************************************************************************/
62: public interface Finder<T> extends Cloneable, Serializable
63: {
64: /*******************************************************************************************************************
65: *
66: * A tag interface to mark objects which are meaningful sort criteria that can be passed to
67: * {@link Finder#sort(it.tidalwave.util.Finder.SortCriterion)}. In general, a {@code SortCriterion} is just a
68: * behaviourless and methodless object, that should be specifically handled by concrete implementations of
69: * {@link Finder}. The only exceptions are {@link InMemorySortCriterion} objects.
70: *
71: ******************************************************************************************************************/
72: public static interface SortCriterion
73: {
74: public static final Class<SortCriterion> _SortCriterion_ = SortCriterion.class;
75:
76: /** A special {@link SortCriterion} which indicates that no sort has been performed. */
77: public static final SortCriterion UNSORTED = (InMemorySortCriterion<Object>)(results, sortDirection) -> {};
78:
79: public static final SortCriterion DEFAULT = UNSORTED;
80: }
81:
82: /*******************************************************************************************************************
83: *
84: * An interface that should be implemented by specific {@link SortCriterion} objects which are capable to implement
85: * by themselves the sorting of objects, by post-processing an existing collection of objects. While this is often
86: * convenient, it is possible for it to be inefficient in cases in which the original source of objects is capable
87: * to perform the sort in an optimized way (e.g. an SQL database by means of {@code ORDER BY}). The facility class
88: * {@link it.tidalwave.util.spi.HierarchicFinderSupport} supports {@code FilterSortCriterion} objects out of the box.
89: *
90: ******************************************************************************************************************/
91: public static interface InMemorySortCriterion<U> extends SortCriterion
92: {
93: /***************************************************************************************************************
94: *
95: * Performs the sort of results.
96: *
97: * @param results the list of objects to be sorted in place
98: *
99: **************************************************************************************************************/
100: public default void sort (@Nonnull final List<? extends U> results)
101: {
102: sort(results, SortDirection.ASCENDING);
103: }
104:
105: /***************************************************************************************************************
106: *
107: * Performs the sort of results.
108: *
109: * @param results the list of objects to be sorted in place
110: * @param sortDirection the sort direction
111: *
112: **************************************************************************************************************/
113: // START SNIPPET: sort
114: public void sort (@Nonnull List<? extends U> results, @Nonnull SortDirection sortDirection);
115: // END SNIPPET: sort
116:
117: /***************************************************************************************************************
118: *
119: * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
120: *
121: * @param <U> the type of the objects to compare
122: * @param comparator the {@code Comparator}
123: * @return the new {@code SortCriterion}
124: *
125: **************************************************************************************************************/
126: @Nonnull
127: public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator)
128: {
129: return of(comparator, comparator.getClass().getSimpleName());
130: }
131:
132: /***************************************************************************************************************
133: *
134: * Creates a new in-memory {@code SortCriterion} based on a {@link Comparator}.
135: *
136: * @param <U> the type of the objects to compare
137: * @param comparator the {@code Comparator}
138: * @param name a name
139: * @return the new {@code SortCriterion}
140: *
141: **************************************************************************************************************/
142: @Nonnull
143: public static <U> InMemorySortCriterion<U> of (@Nonnull final Comparator<? super U> comparator,
144: @Nonnull final String name)
145: {
146: return new DefaultInMemorySortCriterion<>(comparator, name);
147: }
148:
149: /***************************************************************************************************************
150: *
151: **************************************************************************************************************/
152: @AllArgsConstructor @ToString @EqualsAndHashCode
153: static class DefaultInMemorySortCriterion<U> implements Finder.InMemorySortCriterion<U>, Serializable
154: {
155: private static final long serialVersionUID = 76093596048395982L;
156:
157: @Nonnull
158: private final Comparator<? super U> comparator;
159:
160: @Nonnull
161: private final String name;
162:
163: @Override
164: public void sort (@Nonnull final List<? extends U> results, @Nonnull final SortDirection sortDirection)
165: {
166: results.sort((Comparator<U>)(o1, o2) -> comparator.compare(o1, o2) * sortDirection.intValue());
167: }
168: }
169: }
170:
171: /*******************************************************************************************************************
172: *
173: * An enumeration to define the direction of a sort (ascending or descending).
174: *
175: * @it.tidalwave.javadoc.stable
176: *
177: ******************************************************************************************************************/
178: @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
179: public static enum SortDirection
180: {
181: ASCENDING(+1), DESCENDING(-1);
182:
183: private final int intValue;
184:
185: /** @return +1 for ascending direction, -1 for descending */
186: public int intValue()
187: {
188: return intValue;
189: }
190: }
191:
192: /*******************************************************************************************************************
193: *
194: * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
195: *
196: * @param firstResult the index of the first result to return
197: * @return the {@code Finder}
198: *
199: ******************************************************************************************************************/
200: // START SNIPPET: from
201: @Nonnull
202: public Finder<T> from (@Nonnegative int firstResult);
203: // END SNIPPET: from
204:
205: /*******************************************************************************************************************
206: *
207: * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
208: *
209: * @param firstResult the index of the first result to return
210: * @return the {@code Finder}
211: * @since 3.2-ALPHA-19
212: *
213: ******************************************************************************************************************/
214: @Nonnull
215: public default Finder<T> from (@Nonnull final Optional<Integer> firstResult)
216: {
217: return firstResult.map(this::from).orElse(this);
218: }
219:
220: /*******************************************************************************************************************
221: *
222: * Tells the {@code Finder} that only a subset of found items will be returned, starting from the given position.
223: *
224: * @param firstResult the index of the first result to return
225: * @return the {@code Finder}
226: * @since 3.2-ALPHA-22
227: *
228: ******************************************************************************************************************/
229: @Nonnull
230: public default Finder<T> from (@Nonnull final OptionalInt firstResult)
231: {
232: return firstResult.isPresent() ? from(firstResult.getAsInt()) : this;
233: }
234:
235: /*******************************************************************************************************************
236: *
237: * Tells the {@code Finder} that only a maximum number of found items will be returned.
238: *
239: * @param maxResults the max number of results to return
240: * @return the {@code Finder}
241: *
242: ******************************************************************************************************************/
243: // START SNIPPET: max
244: @Nonnull
245: public Finder<T> max (@Nonnegative int maxResults);
246: // END SNIPPET: max
247:
248: /*******************************************************************************************************************
249: *
250: * Tells the {@code Finder} that only a maximum number of found items will be returned.
251: *
252: * @param maxResults the max number of results to return
253: * @return the {@code Finder}
254: * @since 3.2-ALPHA-19
255: *
256: ******************************************************************************************************************/
257: @Nonnull
258: public default Finder<T> max (@Nonnull final Optional<Integer> maxResults)
259: {
260: return maxResults.map(this::max).orElse(this);
261: }
262:
263: /*******************************************************************************************************************
264: *
265: * Tells the {@code Finder} that only a maximum number of found items will be returned.
266: *
267: * @param maxResults the max number of results to return
268: * @return the {@code Finder}
269: * @since 3.2-ALPHA-22
270: *
271: ******************************************************************************************************************/
272: @Nonnull
273: public default Finder<T> max (@Nonnull final OptionalInt maxResults)
274: {
275: return maxResults.isPresent() ? max(maxResults.getAsInt()) : this;
276: }
277:
278: /*******************************************************************************************************************
279: *
280: * Tells the {@code Finder} that results should be created with the given context. This method can be called
281: * multiple times; contexts are accumulated.
282: *
283: * @param context the context
284: * @return the {@code Finder}
285: *
286: ******************************************************************************************************************/
287: @Nonnull
288: public default Finder<T> withContext (@Nonnull final Object context)
289: {
290: throw new UnsupportedOperationException("Not implemented yet.");
291: }
292:
293: /*******************************************************************************************************************
294: *
295: * Tells the {@code Finder} that the specified type of results is expected.
296: *
297: * @param <U> the static type
298: * @param type the dynamic type
299: * @return the {@code Finder}
300: *
301: ******************************************************************************************************************/
302: @Nonnull
303: public default <U> Finder<U> ofType (@Nonnull final Class<U> type)
304: {
305: throw new UnsupportedOperationException("Not implemented yet.");
306: }
307:
308: /*******************************************************************************************************************
309: *
310: * Tells the {@code Finder} that results will be sorted according to the given criterion, in ascending direction.
311: *
312: * @param criterion the sort criterion
313: * @return the {@code Finder}
314: *
315: ******************************************************************************************************************/
316: @Nonnull
317: public default Finder<T> sort (@Nonnull final SortCriterion criterion)
318: {
319: return sort(criterion, SortDirection.ASCENDING);
320: }
321:
322:
323: /*******************************************************************************************************************
324: *
325: * Tells the {@code Finder} that results will be sorted according to the given criterion and direction.
326: *
327: * @param criterion the sort criterion
328: * @param direction the sort direction
329: * @return the {@code Finder}
330: *
331: ******************************************************************************************************************/
332: @Nonnull
333: public Finder<T> sort (@Nonnull SortCriterion criterion, @Nonnull SortDirection direction);
334:
335: /*******************************************************************************************************************
336: *
337: * Performs the search and returns the found items.
338: *
339: * @return the searched items
340: *
341: ******************************************************************************************************************/
342: // START SNIPPET: results
343: @Nonnull
344: public List<T> results();
345: // END SNIPPET: results
346:
347: /*******************************************************************************************************************
348: *
349: * Performs the search and returns the count of found items.
350: *
351: * @return the count of found items
352: *
353: ******************************************************************************************************************/
354: // START SNIPPET: count
355: @Nonnegative
356: public int count();
357: // END SNIPPET: count
358:
359: /*******************************************************************************************************************
360: *
361: * Performs the search assuming that it will return a single item and returns it. This method fails if the search
362: * returns more than one single item.
363: *
364: * @return the optional result
365: * @throws RuntimeException if the search returned more than one single item
366: *
367: * @since 3.2-ALPHA-1 (previously in Finder8)
368: *
369: ******************************************************************************************************************/
370: // START SNIPPET: optionalResult
371: @Nonnull
372: public default Optional<T> optionalResult()
373: // END SNIPPET: optionalResult
374: {
375: final var results = (List<T>)results();
376:
377: if (results.size() > 1)
378: {
379: throw new RuntimeException("" + results.size() + " results, expected only one");
380: }
381:
382: return results.stream().findFirst();
383: }
384:
385: /*******************************************************************************************************************
386: *
387: * Performs the search and returns only the first found item.
388: *
389: * @return the first result
390: * @since 3.2-ALPHA-1 (previously in Finder8)
391: *
392: ******************************************************************************************************************/
393: // START SNIPPET: optionalFirstResult
394: @Nonnull
395: public default Optional<T> optionalFirstResult()
396: // END SNIPPET: optionalFirstResult
397: {
398: return stream().findFirst();
399: }
400:
401: /*******************************************************************************************************************
402: *
403: * Returns a stream of results.
404: *
405: * @return the stream
406: * @since 3.2-ALPHA-1 (previously in Finder8)
407: *
408: ******************************************************************************************************************/
409: @Nonnull
410: public default Stream<T> stream()
411: {
412: return results().stream();
413: }
414:
415: /*******************************************************************************************************************
416: *
417: * Returns an iterator of results.
418: *
419: * @return the iterator
420: * @since 3.2-ALPHA-1 (previously in Finder8)
421: *
422: ******************************************************************************************************************/
423: @Nonnull
424: public default Iterator<T> iterator()
425: {
426: return stream().iterator();
427: }
428:
429: /*******************************************************************************************************************
430: *
431: * Iterates through results.
432: *
433: * @param consumer the consumer
434: * @since 3.2-ALPHA-22
435: *
436: ******************************************************************************************************************/
437: public default void forEach (@Nonnull final Consumer<? super T> consumer)
438: {
439: stream().forEach(consumer);
440: }
441:
442: /*******************************************************************************************************************
443: *
444: * Performs the search assuming that it will return a single item and returns it. This method fails if the search
445: * returns more than one single item.
446: *
447: * @return the found item
448: * @throws NotFoundException if the search didn't find anything
449: * @throws RuntimeException if the search returned more than one single item
450: * @deprecated Use {@link #optionalResult()} instead
451: *
452: ******************************************************************************************************************/
453: @Nonnull @Deprecated
454: public default T result()
455: throws NotFoundException, RuntimeException
456: {
457: return optionalResult().orElseThrow(NotFoundException::new);
458: }
459:
460: /*******************************************************************************************************************
461: *
462: * Performs the search and returns only the first found item.
463: *
464: * @return the first found item
465: * @throws NotFoundException if the search didn't find anything
466: * @deprecated Use {@link #optionalFirstResult()} instead
467: *
468: ******************************************************************************************************************/
469: @Nonnull @Deprecated
470: public default T firstResult()
471: throws NotFoundException
472: {
473: return optionalFirstResult().orElseThrow(NotFoundException::new);
474: }
475:
476: /*******************************************************************************************************************
477: *
478: * Returns an empty {@code Finder}.
479: *
480: * @param <U> the type of the {@code Finder}
481: * @return the empty {@code Finder}
482: * @since 3.2-ALPHA-1 (previously in HierarchicFinderSupport.emptyFinder())
483: *
484: ******************************************************************************************************************/
485: @Nonnull
486: public static <U> Finder<U> empty()
487: {
488: return ofCloned(Collections.emptyList());
489: }
490:
491: /*******************************************************************************************************************
492: *
493: * Returns a wrapped {@code Finder} on a given collection of elements. The collection is cloned and will be
494: * immutable.
495: * If you need to compute the collection on demand, use {@link #ofSupplier(Supplier)}.
496: * This method retrieves the full range of results that will be later segmented in compliance with the values
497: * specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results is already
498: * available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows
499: * to access the 'from' and 'max' parameter, so only the required items need to be provided.
500: *
501: * @param <U> the type of the {@code Finder}
502: * @param items the objects to wrap
503: * @return the wrapped {@code Finder}
504: * @see #ofSupplier(Supplier)
505: * @see #ofProvider(BiFunction)
506: * @since 3.2-ALPHA-1
507: *
508: ******************************************************************************************************************/
509: // START SNIPPET: ofCloned
510: @Nonnull
511: public static <U> Finder<U> ofCloned (@Nonnull final Collection<? extends U> items)
512: // END SNIPPET: ofCloned
513: {
514: return new ArrayListFinder<>(items);
515: }
516:
517: /*******************************************************************************************************************
518: *
519: * Returns a wrapped {@code Finder} on a given supplier. The collection will be cloned after being supplied.
520: * This method retrieves the full range of results that will be later segmented in compliance with the values
521: * specified by {@link #from(int)} and {@link #max(int)}; this is ok if the whole list of results is already
522: * available of if it is not expensive to compute. The alternate method {@link #ofProvider(BiFunction)} allows
523: * to access the 'from' and 'max' parameter, so only the required items need to be provided.
524: *
525: * @param <U> the type of the {@code Finder}
526: * @param supplier the supplier
527: * @return the wrapped {@code Finder}
528: * @see #ofCloned(Collection)
529: * @see #ofProvider(BiFunction)
530: * @since 3.2-ALPHA-15
531: *
532: ******************************************************************************************************************/
533: // START SNIPPET: ofsupplier
534: @Nonnull
535: public static <U> Finder<U> ofSupplier (@Nonnull final Supplier<? extends Collection<? extends U>> supplier)
536: // END SNIPPET: ofsupplier
537: {
538: return new SupplierFinder<>(supplier);
539: }
540:
541: /*******************************************************************************************************************
542: *
543: * Returns a wrapped {@code Finder} on a given function to provide results. The function receives the 'from' and
544: * 'max' arguments to select a subrange of the results. The collection will be cloned after being supplied.
545: *
546: * @param <U> the type of the {@code Finder}
547: * @param provider the function providing results
548: * @return the wrapped {@code Finder}
549: * @see #ofCloned(Collection)
550: * @see #ofSupplier(Supplier)
551: * @since 3.2-ALPHA-15
552: *
553: ******************************************************************************************************************/
554: // START SNIPPET: ofProvider
555: @Nonnull
556: public static <U> Finder<U> ofProvider (
557: @Nonnull final BiFunction<Integer, Integer, ? extends Collection<? extends U>> provider)
558: // END SNIPPET: ofProvider
559: {
560: return new ProviderFinder<>(provider);
561: }
562:
563: /*******************************************************************************************************************
564: *
565: * Returns a mapping {@code Finder} on a given delegate {@code Finder}. The mapper finder provides the same
566: * results as the delegate, transformed by a mapper function.
567: *
568: * @param <U> the type of the {@code Finder}
569: * @param <V> the type of the delegate {@code Finder}
570: * @param delegate the delegate finder
571: * @param mapper the mapper function
572: * @return the wrapped {@code Finder}
573: * @since 3.2-ALPHA-15
574: *
575: ******************************************************************************************************************/
576: // START SNIPPET: mapping
577: @Nonnull
578: public static <U, V> Finder<U> mapping (@Nonnull final Finder<V> delegate,
579: @Nonnull final Function<? super V, ? extends U> mapper)
580: // END SNIPPET: mapping
581: {
582: return new MappingFinder<>(delegate, mapper);
583: }
584: }