Skip to content

Package: HierarchicFinderSupport$Sorter

HierarchicFinderSupport$Sorter

nameinstructionbranchcomplexitylinemethod
sort(List)
M: 0 C: 7
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%

Coverage

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