Skip to contentMethod: clone()
1: /*
2: * *********************************************************************************************************************
3: *
4: * TheseFoolishThings: Miscellaneous utilities
5: * http://tidalwave.it/projects/thesefoolishthings
6: *
7: * Copyright (C) 2009 - 2023 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<TYPE, EXTENDED_FINDER extends Finder<TYPE>> implements Finder<TYPE>
60: {
61: private static final long serialVersionUID = 2467809593956684L;
62:
63: @RequiredArgsConstructor
64: static class Sorter<Type>
65: {
66: @Nonnull
67: private final InMemorySortCriterion<Type> sortCriterion;
68:
69: @Nonnull
70: private final SortDirection sortDirection;
71:
72: public void sort (@Nonnull final List<? extends Type> 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: private final int firstResult;
88:
89: @Nonnegative
90: private final int maxResults;
91:
92: @Nonnull @Getter(AccessLevel.PROTECTED)
93: private final List<Object> contexts;
94:
95: @Nonnull
96: private final List<Sorter<TYPE>> 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<TYPE, EXTENDED_FINDER> other, @Nonnull final Object holder)
141: {
142: log.trace("HierarchicFinderSupport({}, {})", other, holder);
143: final HierarchicFinderSupport<TYPE, EXTENDED_FINDER> 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<TYPE, EXTENDED_FINDER> 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 EXTENDED_FINDER clonedWith (@Nonnull final Object override)
176: {
177: try
178: {
179: final Constructor<? extends HierarchicFinderSupport> constructor = getCloneConstructor();
180: constructor.setAccessible(true);
181: return (EXTENDED_FINDER)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 EXTENDED_FINDER clone (@Nonnull final Object override)
200: {
201: return clonedWith(override);
202: }
203:
204: /*******************************************************************************************************************
205: *
206: * {@inheritDoc}
207: *
208: ******************************************************************************************************************/
209: @Override @Nonnull
210: public EXTENDED_FINDER from (@Nonnegative final int firstResult)
211: {
212: return clonedWith(new HierarchicFinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
213: }
214:
215: /*******************************************************************************************************************
216: *
217: * {@inheritDoc}
218: *
219: ******************************************************************************************************************/
220: @Override @Nonnull
221: public EXTENDED_FINDER max (@Nonnegative final int maxResults)
222: {
223: return clonedWith(new HierarchicFinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
224: }
225:
226: /*******************************************************************************************************************
227: *
228: * {@inheritDoc}
229: *
230: ******************************************************************************************************************/
231: @Override @Nonnull
232: public EXTENDED_FINDER withContext (@Nonnull final Object context)
233: {
234: final List<Object> contexts = concat(this.contexts, context);
235: return clonedWith(new HierarchicFinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
236: }
237:
238: /*******************************************************************************************************************
239: *
240: * {@inheritDoc}
241: *
242: ******************************************************************************************************************/
243: @Override @Nonnull
244: public <ANOTHER_TYPE> Finder<ANOTHER_TYPE> ofType (@Nonnull final Class<ANOTHER_TYPE> 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 EXTENDED_FINDER sort (@Nonnull final SortCriterion criterion, @Nonnull final SortDirection direction)
256: {
257: if (criterion instanceof Finder.InMemorySortCriterion)
258: {
259: final List<Sorter<TYPE>> sorters = concat(this.sorters,
260: new Sorter<>((InMemorySortCriterion<TYPE>)criterion, direction));
261: return clonedWith(new HierarchicFinderSupport<TYPE, EXTENDED_FINDER>(name, firstResult, maxResults, contexts, sorters));
262: }
263:
264: final String template = "%s does not implement %s - you need to subclass Finder and override sort()";
265: final String message = String.format(template, criterion, InMemorySortCriterion.class);
266: throw new UnsupportedOperationException(message);
267: }
268:
269: /*******************************************************************************************************************
270: *
271: * {@inheritDoc}
272: *
273: ******************************************************************************************************************/
274: @Override @Nonnull
275: public final EXTENDED_FINDER sort (@Nonnull final SortCriterion criterion)
276: {
277: return sort(criterion, SortDirection.ASCENDING);
278: }
279:
280: /*******************************************************************************************************************
281: *
282: * {@inheritDoc}
283: *
284: ******************************************************************************************************************/
285: @Override @Nonnull
286: public List<? extends TYPE> results()
287: {
288: return computeNeededResults();
289: }
290:
291: /*******************************************************************************************************************
292: *
293: * {@inheritDoc}
294: *
295: ******************************************************************************************************************/
296: @Override @Nonnegative
297: public int count()
298: {
299: return computeNeededResults().size();
300: }
301:
302: /*******************************************************************************************************************
303: *
304: * Subclasses can implement this method where *all* the raw results must be actually retrieved.
305: *
306: * @return the unprocessed results
307: *
308: ******************************************************************************************************************/
309: // START SNIPPET: computeResults
310: @Nonnull
311: protected List<? extends TYPE> computeResults()
312: // END SNIPPET: computeResults
313: {
314: throw new UnsupportedOperationException("You must implement me!");
315: }
316:
317: /*******************************************************************************************************************
318: *
319: * Subclasses can implement this method where *only the requested* raw results must be retrieved.
320: *
321: * @return the unprocessed results
322: *
323: ******************************************************************************************************************/
324: // START SNIPPET: computeNeededResults
325: @Nonnull
326: protected List<? extends TYPE> computeNeededResults()
327: // END SNIPPET: computeNeededResults
328: {
329: log.trace("computeNeededResults() - {}", this);
330: List<? extends TYPE> results = computeResults();
331:
332: // First sort and then extract the sublist
333: for (final Sorter<TYPE> sorter : sorters)
334: {
335: log.trace(">>>> sorting with {}...", sorter);
336: sorter.sort(results);
337: }
338:
339: final int toIndex = (int)Math.min(results.size(), (long)firstResult + (long)maxResults);
340:
341: if (firstResult > toIndex)
342: {
343: return new CopyOnWriteArrayList<>();
344: }
345:
346: results = results.subList(firstResult, toIndex);
347:
348: return results;
349: }
350:
351: /*******************************************************************************************************************
352: *
353: * A utility method used by the copy constructor (see general documentation). If the override object is strictly
354: * of the specified type, it is returned; otherwise the other object is returned.
355: *
356: * @param <T> the static type of the source
357: * @param type the dynamic type of the source
358: * @param other the other finder
359: * @param override the holder object
360: * @return the override or other
361: *
362: ******************************************************************************************************************/
363: @Nonnull
364: protected static <T> T getSource (final Class<T> type, @Nonnull final T other, @Nonnull final Object override)
365: {
366: return override.getClass().equals(type) ? type.cast(override) : other;
367: }
368:
369: /*******************************************************************************************************************
370: *
371: *
372: ******************************************************************************************************************/
373: @Nonnull
374: private Constructor<? extends HierarchicFinderSupport> getCloneConstructor()
375: throws SecurityException, NoSuchMethodException
376: {
377: return getClass().getConstructor(getClass(), Object.class);
378: }
379:
380: /*******************************************************************************************************************
381: *
382: *
383: ******************************************************************************************************************/
384: private void checkSubClass()
385: {
386: try
387: {
388: getCloneConstructor();
389: }
390: catch (SecurityException | NoSuchMethodException e)
391: {
392: throw new ExceptionInInitializerError(MESSAGE + e.getMessage());
393: }
394: }
395: }