001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029 package org.hd.d.pg2k.webSvr.exhibit;
030
031 import java.util.ArrayList;
032 import java.util.Arrays;
033 import java.util.Collection;
034 import java.util.Collections;
035 import java.util.Comparator;
036 import java.util.HashMap;
037 import java.util.Map;
038 import java.util.Random;
039 import java.util.concurrent.Callable;
040 import java.util.concurrent.ConcurrentHashMap;
041
042 import org.hd.d.pg2k.svrCore.AllExhibitProperties;
043 import org.hd.d.pg2k.svrCore.ExhibitAttrUtils;
044 import org.hd.d.pg2k.svrCore.ExhibitName;
045 import org.hd.d.pg2k.svrCore.ExhibitStaticAttr;
046 import org.hd.d.pg2k.svrCore.GenUtils;
047 import org.hd.d.pg2k.svrCore.Name;
048 import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
049 import org.hd.d.pg2k.svrCore.Name.ExhibitShort;
050 import org.hd.d.pg2k.svrCore.Rnd;
051 import org.hd.d.pg2k.svrCore.TextUtils;
052 import org.hd.d.pg2k.svrCore.ThreadUtils;
053 import org.hd.d.pg2k.svrCore.location.Location;
054 import org.hd.d.pg2k.svrCore.props.GenProps;
055
056 /**Built-in filters and sorters that can be looked up by name.
057 * The filters are inner classes and not publicly visible
058 * so that by-and-large they do have to be looked up by name.
059 * There is an internal registry so that reflection doesn't
060 * have to be used and packagers such as dashO should have
061 * any easier time working out what's going on...
062 * <p>
063 * All the filters/sorters are constructed with a String array or arguments.
064 * This argument list is never null, though may be zero length if there are
065 * no arguments for a particular filter/sorter. None of the arguments
066 * itself will be null.
067 * <p>
068 * The exhibit names are full names, ie including the leading directory component.
069 */
070 public final class BuiltInFilters
071 {
072 /**No one can instantiate this. */
073 private BuiltInFilters() { }
074
075 /**Accepts all exhibits.
076 */
077 public static final class filtAll implements FilterIF
078 {
079 /**Unique serial ID. */
080 private static final long serialVersionUID = -3538216625320264998L;
081 /**Ignores any arguments.
082 */
083 public filtAll(final String args[]) { }
084 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
085 { return(true); }
086
087 /**Hash is constant. */
088 @Override
089 public int hashCode() { return(0); }
090 /**All instances are equal (could be a singleton). */
091 @Override
092 public boolean equals(final Object obj) { return(obj instanceof filtAll); } }
093
094 /**Rejects all exhibits.
095 */
096 public static final class filtNothing implements FilterIF
097 {
098 /**Unique serial ID. */
099 private static final long serialVersionUID = 7375589684054049955L;
100 /**Ignores any arguments.
101 */
102 public filtNothing(final String args[]) { }
103 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
104 { return(false); }
105
106 /**Hash is constant. */
107 @Override
108 public int hashCode() { return(0); }
109 /**All instances are equal (could be a singleton). */
110 @Override
111 public boolean equals(final Object obj) { return(obj instanceof filtNothing); }
112 }
113
114 /**Filter by author.
115 * FIXME: add equals() and hashCode().
116 */
117 public static final class filtByAuthor implements FilterIF
118 {
119 /**Unique serial ID. */
120 private static final long serialVersionUID = 1926524023966666518L;
121 private final String acceptableAuthors[];
122 /**Accepts zero or more author (initials); an exhibit matching any of them is accepted.
123 */
124 public filtByAuthor(final String args[]) { acceptableAuthors = args.clone(); }
125 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
126 {
127 final CharSequence initials = ExhibitName.getAuthorComponent(exhibitName);
128 for(int i = acceptableAuthors.length; --i >= 0; )
129 { if(TextUtils.contentEquals(initials, acceptableAuthors[i])) { return(true); } }
130 return(false); // No match.
131 }
132 }
133
134
135
136 /**Filter by category.
137 * FIXME: add equals() and hashCode().
138 * <p>
139 * Responsible for ~2% of all normal (non-array) allocations (from ExhibitName.getCategoryComponent(exhibitName)) as of 2012/03/16.
140 */
141 public static final class filtByCategory implements FilterIF
142 {
143 /**Unique serial ID. */
144 private static final long serialVersionUID = -2337407306687756258L;
145 private final String acceptableCategories[];
146 /**Accepts zero or more categories; an exhibit matching any of them is accepted. */
147 public filtByCategory(final String args[]) { acceptableCategories = args.clone(); }
148 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
149 {
150 final int nl = exhibitName.length();
151 //final CharSequence category = ExhibitName.getCategoryComponent(exhibitName);
152 byCategory: for(int i = acceptableCategories.length; --i >= 0; )
153 {
154 //if(TextUtils.contentEquals(category, acceptableCategories[i])) { return(true); }
155 // Avoid memory churn implied by making wrapper with CharSequence category = ExhibitName.getCategoryComponent(exhibitName),
156 // and avoid recursion from futile matches on early characters of Name.ExhibitFull if terminating '/' not present in right place,
157 // and check the chars in reverse order.
158 final String acceptableCategory = acceptableCategories[i];
159 final int cl = acceptableCategory.length();
160 if(nl < cl + ExhibitName.MIN_FILENAME_LENGTH) { continue; }
161 if(exhibitName.charAt(cl) != ExhibitName.DIR_SEP) { continue; }
162 //if(TextUtils.regionMatches(acceptableCategory, 0, exhibitName, 0, cl)) { return(true); }
163 for(int j = cl; --j >= 0; )
164 { if(exhibitName.charAt(j) != acceptableCategory.charAt(j)) { continue byCategory; } }
165 return(true); // All matches.
166 }
167 return(false); // No match.
168 }
169 }
170
171
172 /**Accepts an exhibit with the given case-sensitive substring in its full pathname.
173 * This is no respecter of syntax, or aliases.
174 * <p>
175 * Not fast and does not look at the data or meta-data.
176 * <p>
177 * As a special case this is publicly visible until expression
178 * parsing is implemented.
179 * <p>
180 * FIXME: add equals() and hashCode().
181 */
182 public static final class filtSimpleSubstringMatch implements FilterIF
183 {
184 /**Unique serial ID. */
185 private static final long serialVersionUID = -5197565900747314559L;
186 /**Takes exactly one argument; the case-sensitive substring to match.
187 */
188 public filtSimpleSubstringMatch(final String args[])
189 {
190 if((args.length != 1) /* || (args[0] == null) */)
191 { throw new IllegalArgumentException(); }
192 subString = args[0];
193 }
194 /**The substring to match. */
195 private final String subString;
196 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
197 { return(exhibitName.toString().indexOf(subString) != -1); } // FIXME: get rid of expensive String conversion
198 }
199
200 /**Accepts an that has an Estd location within the target area; possibly within the specified area.
201 */
202 public static final class filtByEstdLocationCentre implements FilterIF
203 {
204 /**Unique serial ID. */
205 private static final long serialVersionUID = 1246204565122236630L;
206 /**Particular location to filter by.
207 * If non-null, item must fall within the area delimited by this
208 * location to be considered.
209 */
210 private final Location.Estd area;
211
212 /**Takes zero or one arguments; the (optional) area and bounds to filter else any Estd location is acceptable.
213 */
214 public filtByEstdLocationCentre(final String args[])
215 {
216 if((args != null) || (args.length >= 0))
217 { throw new Error("NOT IMPLEMENTED"); }
218 area = null;
219 }
220
221 /**Takes area and bounds to filter for; if null any Estd location is acceptable.
222 */
223 public filtByEstdLocationCentre(final Location.Estd filterArea)
224 {
225 area = filterArea;
226 }
227
228 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
229 {
230 final Location.Base l = aep.getLocation(exhibitName);
231 if(!(l instanceof Location.Estd))
232 { return(false); } // Wrong type or no location info.
233
234 // Filter on area if so requested.
235 if(area != null)
236 {
237 final Location.Estd el = (Location.Estd) l;
238 if(!area.containsCentre(el))
239 { return(false); } // Centre not in specified area.
240 }
241
242 return(true); // Seems OK.
243 }
244
245 /**Filters are equal if area objects are (or are both null). */
246 @Override
247 public boolean equals(final Object obj)
248 {
249 if(!(obj instanceof filtByEstdLocationCentre)) { return(false); }
250 final filtByEstdLocationCentre other = (filtByEstdLocationCentre) obj;
251 if(area == null) { return(other.area == null); }
252 return(area.equals(other.area));
253 }
254
255 /**Filter has hashcode of its area; 0 if none. */
256 @Override
257 public int hashCode()
258 {
259 if(area == null) { return(0); }
260 return(area.hashCode());
261 }
262 }
263
264
265
266 /**Accepts an exhibit with the given case-sensitive substring starting its final (file / short-name) component.
267 * This is not much of a respector of syntax.
268 * <p>
269 * Very fast and does not look at the data or meta-data.
270 * <p>
271 * As a special case this is publicly visible until expression
272 * parsing is implemented.
273 * <p>
274 * FIXME: add equals() and hashCode().
275 */
276 public static final class filtPrefixMatch implements FilterIF
277 {
278 /**Unique serial ID. */
279 private static final long serialVersionUID = 8580496714859968839L;
280 /**Takes exactly one argument; the case-sensitive substring to match. */
281 public filtPrefixMatch(final String args[])
282 {
283 if((args.length != 1) /* || (args[0] == null) */)
284 { throw new IllegalArgumentException(); }
285 prefix = args[0];
286 }
287 /**The substring to match. */
288 private final String prefix;
289 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
290 {
291 final ExhibitShort shortName = exhibitName.getShortName();
292 return(TextUtils.startsWith(shortName, prefix));
293 // return(shortName.toString().startsWith(prefix));
294 }
295 }
296
297 /**Accepts an exhibit with the given case-sensitive substring ending its final component.
298 * This is not much of a respector of syntax.
299 * <p>
300 * Very fast and does not look at the data or meta-data.
301 * <p>
302 * As a special case this is publicly visible until expression
303 * parsing is implemented.
304 * <p>
305 * FIXME: add equals() and hashCode().
306 */
307 public static final class filtSimpleSuffixMatch implements FilterIF
308 {
309 /**Unique serial ID. */
310 private static final long serialVersionUID = -6968644372319333517L;
311 /**Takes exactly one argument; the case-sensitive substring to match.
312 */
313 public filtSimpleSuffixMatch(final String args[])
314 {
315 if((args.length != 1) /* || (args[0] == null) */)
316 { throw new IllegalArgumentException(); }
317 suffix = args[0];
318 }
319 /**The substring to match. */
320 private final String suffix;
321 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
322 {
323 return(TextUtils.endsWith(exhibitName, suffix));
324 // return(ExhibitName.getFileComponent(exhibitName).toString().endsWith(suffix));
325 }
326 }
327
328
329 /**Reverses the existing order of exhibits in situ.
330 * Takes no parameters, so all instances of this class are equal.
331 */
332 public static final class sortReverse implements SortIF
333 {
334 /**Unique ID. */
335 private static final long serialVersionUID = -3097396087855039990L;
336 /**Ignores its arguments. */
337 public sortReverse(final String args[]) { this(); }
338 /**Programmatic constructor. */
339 public sortReverse() { }
340 /**All instances of this class are identical. */
341 @Override
342 public boolean equals(final Object obj) { return((obj instanceof sortReverse)); }
343 /**Hash is constant. */
344 @Override
345 public int hashCode() { return(-99); }
346
347 /**Reverses the order of its arguments in situ. */
348 public ExhibitFull[] sort(final AllExhibitProperties aep,
349 final Name.ExhibitFull[] exhibitNames)
350 {
351 Collections.reverse(Arrays.asList(exhibitNames));
352 return(exhibitNames);
353 }
354 }
355
356 /**Selects N exhibits at random and randomises their order.
357 * If the number of exhibits is less than N then this returns
358 * all the exhibits.
359 * <p>
360 * The first value is the unsigned, positive, base-ten integer value
361 * of N.
362 * <p>
363 * If the second argument is missing or null then the random number
364 * sequence used is taken differently each time we run, else the
365 * hash of the second string is taken as the random number seed.
366 */
367 public static final class sortRandomN implements SortIF
368 {
369 /**Unique serial ID. */
370 private static final long serialVersionUID = 2078921830149773203L;
371
372 /**Takes exactly one or two arguments.
373 * The first argument is the strictly-positive N.
374 * <p>
375 * The second argument, if absent or null, indicates that
376 * a different random number sequence is used on each run.
377 * If present and non-null then its hash is used to seed a
378 * random number generator to give repeatable results.
379 */
380 public sortRandomN(final String args[])
381 {
382 this(Integer.parseInt(args[0]),
383 ((args.length > 1) ? args[1] : null));
384 // if((args.length < 1) || (args.length > 2))
385 // { throw new IllegalArgumentException(); }
386 }
387
388 /**More efficient programmatic constructor.
389 * Takes count as the first (int) argument and
390 * a String to drive the hash or null for a totally random selection.
391 */
392 public sortRandomN(final int count, final String hashSrc)
393 {
394 if(count <= 0) { throw new IllegalArgumentException(); }
395 n = count;
396 seed = hashSrc;
397 }
398
399 /**The upper bound on result size. */
400 private final int n;
401 /**The seed; if null we use a non-repeatable source. */
402 private final String seed;
403
404 public ExhibitFull[] sort(final AllExhibitProperties aep, final Name.ExhibitFull[] exhibitNames)
405 {
406 // Set up our random-number source.
407 final Random rnd = (seed == null) ?
408 Rnd.fastRnd : (new Random(seed.hashCode()));
409
410 Name.ExhibitFull result[];
411 if(exhibitNames.length <= n)
412 {
413 // We don't need to discard any of our input set...
414 result = exhibitNames;
415 }
416 else
417 {
418 // We need to pick n items at random
419 // from our input set, with no duplicates.
420 // We'll make a boolean array logically beside
421 // our input array, and set a slot true when
422 // we've used the input value at the same index...
423 final boolean used[] = new boolean[exhibitNames.length];
424
425 // Output array...
426 result = new Name.ExhibitFull[n];
427
428 // This will work badly where n is large and just
429 // smaller than the number of available exhibits.
430 // We should possibly reverse the mechanism in
431 // this case, and mark values not to use...
432 for(int i = n; --i >= 0; )
433 {
434 // Go on looking for an input slot that we have not yet found...
435 for( ; ; )
436 {
437 // Pick an input slot at random...
438 final int slot = rnd.nextInt(exhibitNames.length);
439 // If we've already used the value in this slot, try again...
440 if(used[slot]) { continue; }
441 // Whopee! A new one; capture this one and break out...
442 result[i] = exhibitNames[slot];
443 used[slot] = true;
444 break;
445 }
446 }
447 }
448
449 // Randomise the result set...
450 Collections.shuffle(Arrays.asList(result), rnd);
451
452 return(result);
453 }
454
455 /**Equality depends on identical seeds and result size.
456 * In other words this checks equivalence rather than the
457 * results being identical each time.
458 */
459 @Override
460 public boolean equals(final Object obj)
461 {
462 if(!(obj instanceof sortRandomN)) { return(false); }
463 final sortRandomN other = (sortRandomN) obj;
464
465 if(n != other.n) { return(false); }
466
467 // If exactly the same seed (eg a static final), then equal.
468 if(seed == other.seed) { return(true); }
469
470 // If we are null (and therefore the other is not), then not equal.
471 if(seed == null) { return(false); }
472
473 // Check seeds for equality.
474 return(seed.equals(other.seed));
475 }
476
477 /**Hash depends on the seed and the result size limit. */
478 @Override
479 public int hashCode()
480 {
481 return(n + ((seed == null) ? 0 : seed.hashCode()));
482 }
483 }
484
485
486 /**Sorts exhibits by (ascending) timestamp, ie oldest first.
487 * Takes no parameters, so all instances of this class are equal.
488 */
489 public static final class sortByTimestamp implements SortIF
490 {
491 /**Unique serial ID. */
492 private static final long serialVersionUID = -8775508119553092181L;
493
494 /**Ignores its arguments.
495 */
496 public sortByTimestamp(final String args[])
497 { }
498
499 /**Programmatic constructor.
500 */
501 public sortByTimestamp()
502 { }
503
504 /**Sorts its arguments in situ by ascending timestamp (oldest first).
505 * Ties are broken by full name.
506 */
507 public ExhibitFull[] sort(final AllExhibitProperties aep,
508 final Name.ExhibitFull[] exhibitNames)
509 {
510 // Get all ESA values in one go, sort in place, then put back the names.
511 final int length = exhibitNames.length;
512 final ExhibitStaticAttr[] esas = new ExhibitStaticAttr[length];
513 for(int i = length; --i >= 0; )
514 { esas[i] = aep.aeid.getStaticAttr(exhibitNames[i]); }
515
516 Arrays.sort(esas,
517 new Comparator<ExhibitStaticAttr>(){
518 public final int compare(final ExhibitStaticAttr e1, final ExhibitStaticAttr e2)
519 {
520 if(e1.timestamp < e2.timestamp) { return(-1); } // Correct.
521 if(e1.timestamp > e2.timestamp) { return(+1); } // Wrong.
522 // Break ties unconditionally by name...
523 return(e1.compareTo(e2));
524 }
525 });
526
527 // Copy back the names...
528 for(int i = length; --i >= 0; )
529 { exhibitNames[i] = esas[i].getExhibitFullName(); }
530
531 return(exhibitNames);
532 }
533
534 /**All instances of this class are identical.
535 */
536 @Override
537 public boolean equals(final Object obj)
538 {
539 if(!(obj instanceof sortByTimestamp)) { return(false); }
540 return(true);
541 }
542
543 /**Hash is constant. */
544 @Override
545 public int hashCode()
546 {
547 return(0);
548 }
549 }
550
551
552 /**Sorts exhibits by (descending) goodness, ie best first.
553 * Takes no parameters, so all instances of this class are equal.
554 * <p>
555 * Cannot supply all the data needed to recompute a full version,
556 * so works with what has already been computed or may force an
557 * approximate ("fast") computation.
558 * <p>
559 * This permits 'stale' values since forcing full recomputation may be very slow
560 * or simply be impossible for some exhibits.
561 */
562 public static final class sortByGoodness implements SortIF
563 {
564 /**Unique serial ID. */
565 private static final long serialVersionUID = -5596318067630417407L;
566
567 /**Ignores its arguments.
568 */
569 public sortByGoodness(final String args[])
570 {
571 nMax = 0;
572 }
573
574 /**Programmatic constructor.
575 * @param nMax if positive, is the maximum number of exhibits returned
576 */
577 public sortByGoodness(final int nMax)
578 {
579 this.nMax = nMax;
580 }
581
582 /**If positive, is the maximum number of exhibits returned. */
583 private final int nMax;
584
585 /**Minimum number of values worth attempting to filter in parallel; strictly positive.
586 * This plays off parallelism overhead vs available concurrency
587 * but should not be especially critical an order of magnitude either way.
588 */
589 private static final int MIN_SIZE_FOR_CONCURRENCY = 256;
590
591 /**Sorts its arguments in situ by descending goodness.
592 * Ties are broken by full name.
593 */
594 public ExhibitFull[] sort(final AllExhibitProperties aep,
595 final Name.ExhibitFull[] exhibitNames)
596 {
597 // Dummy/default for quick EPCM approximation.
598 final GenProps gp = new GenProps();
599
600 // Compute/fetch EPCM (int) goodness *once* for each entry, possibly in parallel.
601 final int length = exhibitNames.length;
602
603 // Don't attempt to parallelise for small data sets nor on uni-processor JVMs.
604 // Assume that computing (fast-approximation) EPCM values is mainly CPU-bound.
605 final boolean parallelise = ((ThreadUtils.AVAILABLE_PROCESSORS > 1) && (length > MIN_SIZE_FOR_CONCURRENCY));
606
607 final Map<Name.ExhibitFull,Integer> goodness = parallelise ?
608 new ConcurrentHashMap<Name.ExhibitFull,Integer>(length, 0.5f, 2*ThreadUtils.AVAILABLE_PROCESSORS) /* Safe to post updates concurrently. */ :
609 new HashMap<Name.ExhibitFull,Integer>(length);
610
611 if(!parallelise)
612 {
613 // Simple single-threaded method.
614 for(final Name.ExhibitFull name : exhibitNames)
615 { goodness.put(name, aep.getExhibitPropsComputableMutable(name, true, gp, null, null).getGoodness()); }
616 }
617 else
618 {
619 // Have the number of parallel tasks go up slowly (with the log of the work size)
620 // and capped by the number of available CPUs.
621 final int nTasks = Math.min(ThreadUtils.AVAILABLE_PROCESSORS, GenUtils.log2Approx(length));
622 assert(nTasks > 1);
623 assert(nTasks < length);
624
625 // Size of each partition: one partition/task will have any extra residue.
626 final int partitionSize = length / nTasks;
627 assert(partitionSize > 0);
628
629 // Create the tasks.
630 final Collection<Callable<Object>> tasks = new ArrayList<Callable<Object>>(nTasks);
631 int taskEnd = length;
632 for(int i = nTasks; --i >= 0; taskEnd -= partitionSize)
633 {
634 // Note that task 0 may be slightly larger than the other tasks.
635 final int end = taskEnd; // Exclusive end in array.
636 final int start = (i == 0) ? 0 : (taskEnd - partitionSize); // Inclusive start in array.
637 tasks.add(new Callable<Object>(){
638 public Object call()
639 {
640 // Directly accumulate results in the thread-safe map.
641 for(int i = end; --i >= start; )
642 {
643 final Name.ExhibitFull name = exhibitNames[i];
644 goodness.put(name, aep.getExhibitPropsComputableMutable(name, true, gp, null, null).getGoodness());
645 }
646 return(null);
647 }
648 });
649 }
650 // Execute them all, as concurrently as possible.
651 try { ThreadUtils.computeIntensiveThreadPool.invokeAll(tasks); }
652 // If we were interrupted or an exception was thrown, re-throw here.
653 catch(final Exception e) { throw new RuntimeException("unexpected exception", e); }
654 }
655
656 // TODO: replace this with a heap of the first nMax items for efficiency.
657 Arrays.sort(exhibitNames,
658 new Comparator<Name.ExhibitFull>(){
659 public final int compare(final Name.ExhibitFull n1, final Name.ExhibitFull n2)
660 {
661 // Sort best items first.
662 final int g1 = goodness.get(n1);
663 final int g2 = goodness.get(n2);
664 if(g1 > g2) { return(-1); } // Correct.
665 if(g1 < g2) { return(+1); } // Wrong.
666
667 // Break ties (not resolved by goodness).
668 // Sort newer items first
669 // as potentially more interesting
670 // and so that they will get looked at!
671 final ExhibitStaticAttr e1 = aep.aeid.getStaticAttr(n1);
672 final ExhibitStaticAttr e2 = aep.aeid.getStaticAttr(n2);
673 if(e1.timestamp > e2.timestamp) { return(-1); }
674 if(e1.timestamp < e2.timestamp) { return(+1); }
675
676 // Break tie unconditionally by name.
677 // Should almost never be used.
678 // Only here to guarantee a total ordering.
679 return(TextUtils.compare(e1.getCharSequence(), e2.getCharSequence()));
680 }
681 });
682
683 // If we are limiting the result length,
684 // then return the "creme de la creme" here!
685 if((nMax > 0) && (nMax < length))
686 {
687 // Return a subset of the items; the best/first ones in fact...
688 return(Arrays.copyOf(exhibitNames, nMax));
689 }
690
691 return(exhibitNames);
692 }
693
694 /**All instances of this class are identical where their nMax is.
695 */
696 @Override
697 public boolean equals(final Object obj)
698 {
699 if(!(obj instanceof sortByGoodness)) { return(false); }
700 final sortByGoodness other = (sortByGoodness) obj;
701 return(nMax == other.nMax);
702 }
703
704 /**Hash is constant. */
705 @Override
706 public int hashCode()
707 {
708 return(nMax);
709 }
710 }
711
712 /**Simple sort of exhibits by name.
713 * By default sorts in natural String order,
714 * but can take a Comparator argument to use
715 * (eg to use a 'smart' ordering(s) for human benefit).
716 */
717 public static final class sortByName implements SortIF
718 {
719 /**Unique serial ID. */
720 private static final long serialVersionUID = 9042557392754106067L;
721
722 /**Simple sort; default String lexical ordering; can use null instead. */
723 public static final String SIMPLE = "simple";
724
725 /**Simple smart sort; essentially case-insensitive ordering on file component. */
726 public static final String SIMPLE_SMART = "simpleSmart";
727
728 /**Full smart sort; gets current version of comparator during sort().
729 * This uses the data-dependent sort
730 * (dependent on the set of attribute words)
731 * that comes with the data so that it is always current.
732 */
733 public static final String SMART = "smart";
734
735 /**Sort Comparator; null for default (simple) sort. */
736 private final String sortType;
737
738 /**Call with argument giving sort type (or null for natural ordering).
739 * Recognised values are:
740 * <ul>
741 * <li>simple (default String lexical ordering)
742 * <li>simpleSmart (essentially case-insensitive ordering on file component)
743 * <li>smart (fully-smart case-insensitive sort using attribute words)
744 * </ul>
745 */
746 public sortByName(final String sortType)
747 {
748 this.sortType = sortType;
749 }
750
751
752 /**Call with no args or single argument giving sort type.
753 * Recognised values are:
754 * <ul>
755 * <li>simple (default String lexical ordering)
756 * <li>simpleSmart (essentially case-insensitive ordering on file component)
757 * <li>smart (fully-smart case-insensitive sort using attribute words)
758 * </ul>
759 */
760 public sortByName(final String args[])
761 {
762 if((args != null) && (args.length > 0))
763 {
764 sortType = args[0];
765 }
766 else
767 { sortType = null; } // Default sort.
768 }
769
770 public ExhibitFull[] sort(final AllExhibitProperties aep, final Name.ExhibitFull[] exhibitNames)
771 {
772 // Simple smart order: not dependent on attribute words.
773 if(sortType.equals(SIMPLE_SMART))
774 { Arrays.sort(exhibitNames, ExhibitName.SIMPLE_SMART_ORDER); }
775
776 // Fully-smart sort: dependent on attribute words.
777 else if(sortType.equals(SMART))
778 { Arrays.sort(exhibitNames, ExhibitAttrUtils.getAttrWords().SMART_ORDER); }
779
780 // Default case (eg if comp is SIMPLE or null or unknown).
781 // Simple sort in String order: fast but not human-friendly.
782 // Not dependent on attribute words.
783 else
784 { Arrays.sort(exhibitNames); }
785 return(exhibitNames);
786 }
787
788 /**The hashcode of the sort, or null for the default sort. */
789 @Override
790 public int hashCode()
791 {
792 return(sortType == null ? 91 : sortType.hashCode());
793 }
794
795 /**Tests equality between two sortByName objects.
796 * For this to be the case the two comparator objects must
797 * be == (eg both be null), or compare equals().
798 */
799 @Override
800 public boolean equals(final Object obj)
801 {
802 //System.err.println("sortByName.equals() ...");
803
804 // Always equal to myself.
805 if(obj == this) { return(true); }
806
807 if(!(obj instanceof sortByName)) { return(false); }
808 final sortByName other = (sortByName) obj;
809
810 // If exactly the same sort type (eg a static final), then equal.
811 if(other.sortType == sortType) { return(true); }
812
813 // If type is null (and therefore the other is not), then not equal.
814 if(sortType == null) { return(false); }
815
816 // Compare sort types for equality.
817 if(!sortType.equals(other.sortType)) { return(false); }
818
819 //System.err.println("sortByName.equals() = true");
820
821 return(true); // Equivalent sorts.
822 }
823 }
824
825 }