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