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.spi; 27 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 import static it.tidalwave.util.CollectionUtils.concat; 43 44 /*************************************************************************************************************************************************************** 45 * 46 * A support class for implementing a {@link Finder}. Subclasses only need to implement the {@link #computeResults()} 47 * method where <i>raw</i> results are retrieved (with raw we mean that they shouldn't be filtered or sorted, as 48 * post-processing will be performed by this class) and a clone constructor. 49 * 50 * If you don't need to extend the {@link Finder} with extra methods, please use the simplified 51 * {@link SimpleFinderSupport}. 52 * 53 * @author Fabrizio Giudici 54 * @it.tidalwave.javadoc.draft 55 * 56 **************************************************************************************************************************************************************/ 57 @Slf4j @AllArgsConstructor(access = AccessLevel.PRIVATE) @ToString 58 public class HierarchicFinderSupport<T, F extends Finder<T>> implements Finder<T> 59 { 60 private static final long serialVersionUID = 2467809593956684L; 61 62 @RequiredArgsConstructor 63 static class Sorter<U> 64 { 65 @Nonnull 66 private final InMemorySortCriterion<U> sortCriterion; 67 68 @Nonnull 69 private final SortDirection sortDirection; 70 71 public void sort (@Nonnull final List<? extends U> results) 72 { 73 sortCriterion.sort(results, sortDirection); 74 } 75 } 76 77 private static final String MESSAGE = 78 "Since version 2.0, Implementations of Finder must have a clone constructor such as " 79 + "MyFinder(MyFinder other, Object override). This means that they can't be implemented by anonymous or inner, " 80 + "non static classes. See the javadoc for further information. Could not find constructor: "; 81 82 @Nonnull 83 private final String name; 84 85 @Nonnegative 86 protected final int firstResult; 87 88 @Nonnegative 89 protected final int maxResults; 90 91 @Nonnull @Getter(AccessLevel.PROTECTED) 92 private final List<Object> contexts; 93 94 @Nonnull 95 private final List<Sorter<T>> sorters; 96 97 private static final int DEFAULT_MAX_RESULTS = Integer.MAX_VALUE; 98 99 /*********************************************************************************************************************************************************** 100 * Creates an instance with the given name (that will be used for diagnostics). 101 * 102 * @param name the name 103 **********************************************************************************************************************************************************/ 104 protected HierarchicFinderSupport (@Nonnull final String name) 105 { 106 this.name = name; 107 this.firstResult = 0; 108 this.maxResults = DEFAULT_MAX_RESULTS; 109 this.sorters = new ArrayList<>(); 110 this.contexts = Collections.emptyList(); 111 checkSubClass(); 112 } 113 114 /*********************************************************************************************************************************************************** 115 * Default constructor. 116 **********************************************************************************************************************************************************/ 117 protected HierarchicFinderSupport() 118 { 119 this.name = getClass().getName(); 120 this.firstResult = 0; 121 this.maxResults = DEFAULT_MAX_RESULTS; 122 this.sorters = new ArrayList<>(); 123 this.contexts = Collections.emptyList(); 124 checkSubClass(); 125 } 126 127 /*********************************************************************************************************************************************************** 128 * Clone constructor for subclasses. 129 * 130 * @param other the other instance to clone 131 * @param holder the holder object 132 **********************************************************************************************************************************************************/ 133 protected HierarchicFinderSupport (@Nonnull final HierarchicFinderSupport<T, F> other, @Nonnull final Object holder) 134 { 135 log.trace("HierarchicFinderSupport({}, {})", other, holder); 136 final var source = getSource(HierarchicFinderSupport.class, other, holder); 137 this.name = source.name; 138 this.firstResult = source.firstResult; 139 this.maxResults = source.maxResults; 140 this.sorters = source.sorters; 141 this.contexts = source.contexts; // it's always unmodifiable 142 } 143 144 /*********************************************************************************************************************************************************** 145 * This method throws an exception since a {@code Finder} extending this class must be cloned with 146 * {@link #clonedWith(Object)}. 147 * 148 * @see #clonedWith(Object) 149 * @deprecated 150 **********************************************************************************************************************************************************/ 151 @Override @Nonnull 152 public final HierarchicFinderSupport<T, F> clone() 153 { 154 throw new UnsupportedOperationException("\"HierarchicFinderSupport.clone() no more supported"); 155 } 156 157 /*********************************************************************************************************************************************************** 158 * Create a clone of this object calling the special copy constructor by reflection. 159 * 160 * @param override the override object 161 * @return the clone 162 **********************************************************************************************************************************************************/ 163 @Nonnull 164 protected F clonedWith (@Nonnull final Object override) 165 { 166 try 167 { 168 final var constructor = getCloneConstructor(); 169 constructor.setAccessible(true); 170 return (F)constructor.newInstance(this, override); 171 } 172 catch (Exception e) 173 { 174 throw new RuntimeException(e); 175 } 176 } 177 178 /*********************************************************************************************************************************************************** 179 * Create a clone of this object calling the special clone constructor by reflection. 180 * 181 * @param override the override object 182 * @return the clone 183 * @deprecated Use {@link #clonedWith(Object)} instead. 184 **********************************************************************************************************************************************************/ 185 @Nonnull @Deprecated 186 protected F clone (@Nonnull final Object override) 187 { 188 return clonedWith(override); 189 } 190 191 /*********************************************************************************************************************************************************** 192 * {@inheritDoc} 193 **********************************************************************************************************************************************************/ 194 @Override @Nonnull 195 public F from (@Nonnegative final int firstResult) 196 { 197 return clonedWith(new HierarchicFinderSupport<T, F>(name, firstResult, maxResults, contexts, sorters)); 198 } 199 200 /*********************************************************************************************************************************************************** 201 * {@inheritDoc} 202 **********************************************************************************************************************************************************/ 203 @Override @Nonnull 204 public F max (@Nonnegative final int maxResults) 205 { 206 return clonedWith(new HierarchicFinderSupport<T, F>(name, firstResult, maxResults, contexts, sorters)); 207 } 208 209 /*********************************************************************************************************************************************************** 210 * {@inheritDoc} 211 **********************************************************************************************************************************************************/ 212 @Override @Nonnull 213 public F withContext (@Nonnull final Object context) 214 { 215 final var contexts = concat(this.contexts, context); 216 return clonedWith(new HierarchicFinderSupport<T, F>(name, firstResult, maxResults, contexts, sorters)); 217 } 218 219 /*********************************************************************************************************************************************************** 220 * {@inheritDoc} 221 **********************************************************************************************************************************************************/ 222 @Override @Nonnull 223 public <U> Finder<U> ofType (@Nonnull final Class<U> type) 224 { 225 throw new UnsupportedOperationException("Must be eventually implemented by subclasses."); 226 } 227 228 /*********************************************************************************************************************************************************** 229 * {@inheritDoc} 230 **********************************************************************************************************************************************************/ 231 @Override @Nonnull 232 public F sort (@Nonnull final SortCriterion criterion, @Nonnull final SortDirection direction) 233 { 234 if (criterion instanceof Finder.InMemorySortCriterion) 235 { 236 final var sorters = concat(this.sorters, new Sorter<>((InMemorySortCriterion<T>)criterion, direction)); 237 return clonedWith(new HierarchicFinderSupport<T, F>(name, firstResult, maxResults, contexts, sorters)); 238 } 239 240 final var template = "%s does not implement %s - you need to subclass Finder and override sort()"; 241 final var message = String.format(template, criterion, InMemorySortCriterion.class); 242 throw new UnsupportedOperationException(message); 243 } 244 245 /*********************************************************************************************************************************************************** 246 * {@inheritDoc} 247 **********************************************************************************************************************************************************/ 248 @Override @Nonnull 249 public final F sort (@Nonnull final SortCriterion criterion) 250 { 251 return sort(criterion, SortDirection.ASCENDING); 252 } 253 254 /*********************************************************************************************************************************************************** 255 * {@inheritDoc} 256 **********************************************************************************************************************************************************/ 257 @Override @Nonnull 258 public List<T> results() 259 { 260 return computeNeededResults(); 261 } 262 263 /*********************************************************************************************************************************************************** 264 * {@inheritDoc} 265 **********************************************************************************************************************************************************/ 266 @Override @Nonnegative 267 public int count() 268 { 269 return computeNeededResults().size(); 270 } 271 272 /*********************************************************************************************************************************************************** 273 * Subclasses can implement this method where *all* the raw results must be actually retrieved. 274 * 275 * @return the unprocessed results 276 **********************************************************************************************************************************************************/ 277 // START SNIPPET: computeResults 278 @Nonnull 279 protected List<T> computeResults() 280 // END SNIPPET: computeResults 281 { 282 throw new UnsupportedOperationException("You must implement me!"); 283 } 284 285 /*********************************************************************************************************************************************************** 286 * Subclasses can implement this method where *only the requested* raw results must be retrieved. 287 * 288 * @return the unprocessed results 289 **********************************************************************************************************************************************************/ 290 // START SNIPPET: computeNeededResults 291 @Nonnull 292 protected List<T> computeNeededResults() 293 // END SNIPPET: computeNeededResults 294 { 295 log.trace("computeNeededResults() - {}", this); 296 var results = computeResults(); 297 298 // First sort and then extract the sublist 299 for (final var sorter : sorters) 300 { 301 log.trace(">>>> sorting with {}...", sorter); 302 sorter.sort(results); 303 } 304 305 final var toIndex = (int)Math.min(results.size(), (long)firstResult + (long)maxResults); 306 307 if (firstResult > toIndex) 308 { 309 return new CopyOnWriteArrayList<>(); 310 } 311 312 results = results.subList(firstResult, toIndex); 313 314 return results; 315 } 316 317 /*********************************************************************************************************************************************************** 318 * A utility method used by the copy constructor (see general documentation). If the override object is strictly 319 * of the specified type, it is returned; otherwise the other object is returned. 320 * 321 * @param <U> the static type of the source 322 * @param type the dynamic type of the source 323 * @param other the other finder 324 * @param override the holder object 325 * @return the override or other 326 **********************************************************************************************************************************************************/ 327 @Nonnull 328 protected static <U> U getSource (@Nonnull final Class<? extends U> type, 329 @Nonnull final U other, 330 @Nonnull final Object override) 331 { 332 return override.getClass().equals(type) ? type.cast(override) : other; 333 } 334 335 /*********************************************************************************************************************************************************** 336 **********************************************************************************************************************************************************/ 337 @Nonnull 338 private Constructor<? extends HierarchicFinderSupport> getCloneConstructor() 339 throws SecurityException, NoSuchMethodException 340 { 341 return getClass().getConstructor(getClass(), Object.class); 342 } 343 344 /*********************************************************************************************************************************************************** 345 **********************************************************************************************************************************************************/ 346 private void checkSubClass () 347 { 348 try 349 { 350 getCloneConstructor(); 351 } 352 catch (SecurityException | NoSuchMethodException e) 353 { 354 throw new ExceptionInInitializerError(MESSAGE + e.getMessage()); 355 } 356 } 357 }