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