1 /* 2 * ************************************************************************************************************************************************************* 3 * 4 * TheseFoolishThings: Miscellaneous utilities 5 * http://tidalwave.it/projects/thesefoolishthings 6 * 7 * Copyright (C) 2009 - 2025 by Tidalwave s.a.s. (http://tidalwave.it) 8 * 9 * ************************************************************************************************************************************************************* 10 * 11 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 12 * You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 17 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 18 * 19 * ************************************************************************************************************************************************************* 20 * 21 * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src 22 * git clone https://github.com/tidalwave-it/thesefoolishthings-src 23 * 24 * ************************************************************************************************************************************************************* 25 */ 26 package it.tidalwave.util; 27 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 }