001 /*
002 Copyright (c) 1996-2011, 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 */
139 public static final class filtByCategory implements FilterIF
140 {
141 /**Unique serial ID. */
142 private static final long serialVersionUID = -2337407306687756258L;
143 private final String acceptableCategories[];
144 /**Accepts zero or more categories; an exhibit matching any of them is accepted.
145 */
146 public filtByCategory(final String args[]) { acceptableCategories = args.clone(); }
147 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
148 {
149 final CharSequence category = ExhibitName.getCategoryComponent(exhibitName);
150 for(int i = acceptableCategories.length; --i >= 0; )
151 { if(TextUtils.contentEquals(category, acceptableCategories[i])) { return(true); } }
152 return(false); // No match.
153 }
154 }
155
156
157 /**Accepts an exhibit with the given case-sensitive substring in its full pathname.
158 * This is no respector of syntax, or aliases.
159 * <p>
160 * Not fast and does not look at the data or meta-data.
161 * <p>
162 * As a special case this is publicly visible until expression
163 * parsing is implemented.
164 * <p>
165 * FIXME: add equals() and hashCode().
166 */
167 public static final class filtSimpleSubstringMatch implements FilterIF
168 {
169 /**Unique serial ID. */
170 private static final long serialVersionUID = -5197565900747314559L;
171 /**Takes exactly one argument; the case-sensitive substring to match.
172 */
173 public filtSimpleSubstringMatch(final String args[])
174 {
175 if((args.length != 1) /* || (args[0] == null) */)
176 { throw new IllegalArgumentException(); }
177 subString = args[0];
178 }
179 /**The substring to match. */
180 private final String subString;
181 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
182 { return(exhibitName.toString().indexOf(subString) != -1); } // FIXME: get rid of expensive String conversion
183 }
184
185 /**Accepts an that has an Estd location within the target area; possibly within the specified area.
186 */
187 public static final class filtByEstdLocationCentre implements FilterIF
188 {
189 /**Unique serial ID. */
190 private static final long serialVersionUID = 1246204565122236630L;
191 /**Particular location to filter by.
192 * If non-null, item must fall within the area delimited by this
193 * location to be considered.
194 */
195 private final Location.Estd area;
196
197 /**Takes zero or one arguments; the (optional) area and bounds to filter else any Estd location is acceptable.
198 */
199 public filtByEstdLocationCentre(final String args[])
200 {
201 if((args != null) || (args.length >= 0))
202 { throw new Error("NOT IMPLEMENTED"); }
203 area = null;
204 }
205
206 /**Takes area and bounds to filter for; if null any Estd location is acceptable.
207 */
208 public filtByEstdLocationCentre(final Location.Estd filterArea)
209 {
210 area = filterArea;
211 }
212
213 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
214 {
215 final Location.Base l = aep.getLocation(exhibitName);
216 if(!(l instanceof Location.Estd))
217 { return(false); } // Wrong type or no location info.
218
219 // Filter on area if so requested.
220 if(area != null)
221 {
222 final Location.Estd el = (Location.Estd) l;
223 if(!area.containsCentre(el))
224 { return(false); } // Centre not in specified area.
225 }
226
227 return(true); // Seems OK.
228 }
229
230 /**Filters are equal if area objects are (or are both null). */
231 @Override
232 public boolean equals(final Object obj)
233 {
234 if(!(obj instanceof filtByEstdLocationCentre)) { return(false); }
235 final filtByEstdLocationCentre other = (filtByEstdLocationCentre) obj;
236 if(area == null) { return(other.area == null); }
237 return(area.equals(other.area));
238 }
239
240 /**Filter has hashcode of its area; 0 if none. */
241 @Override
242 public int hashCode()
243 {
244 if(area == null) { return(0); }
245 return(area.hashCode());
246 }
247 }
248
249
250
251 /**Accepts an exhibit with the given case-sensitive substring starting its final (file / short-name) component.
252 * This is not much of a respector of syntax.
253 * <p>
254 * Very fast and does not look at the data or meta-data.
255 * <p>
256 * As a special case this is publicly visible until expression
257 * parsing is implemented.
258 * <p>
259 * FIXME: add equals() and hashCode().
260 */
261 public static final class filtPrefixMatch implements FilterIF
262 {
263 /**Unique serial ID. */
264 private static final long serialVersionUID = 8580496714859968839L;
265 /**Takes exactly one argument; the case-sensitive substring to match. */
266 public filtPrefixMatch(final String args[])
267 {
268 if((args.length != 1) /* || (args[0] == null) */)
269 { throw new IllegalArgumentException(); }
270 prefix = args[0];
271 }
272 /**The substring to match. */
273 private final String prefix;
274 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
275 {
276 final ExhibitShort shortName = exhibitName.getShortName();
277 return(TextUtils.startsWith(shortName, prefix));
278 // return(shortName.toString().startsWith(prefix));
279 }
280 }
281
282 /**Accepts an exhibit with the given case-sensitive substring ending its final component.
283 * This is not much of a respector of syntax.
284 * <p>
285 * Very fast and does not look at the data or meta-data.
286 * <p>
287 * As a special case this is publicly visible until expression
288 * parsing is implemented.
289 * <p>
290 * FIXME: add equals() and hashCode().
291 */
292 public static final class filtSimpleSuffixMatch implements FilterIF
293 {
294 /**Unique serial ID. */
295 private static final long serialVersionUID = -6968644372319333517L;
296 /**Takes exactly one argument; the case-sensitive substring to match.
297 */
298 public filtSimpleSuffixMatch(final String args[])
299 {
300 if((args.length != 1) /* || (args[0] == null) */)
301 { throw new IllegalArgumentException(); }
302 suffix = args[0];
303 }
304 /**The substring to match. */
305 private final String suffix;
306 public boolean accept(final AllExhibitProperties aep, final Name.ExhibitFull exhibitName)
307 {
308 return(TextUtils.endsWith(exhibitName, suffix));
309 // return(ExhibitName.getFileComponent(exhibitName).toString().endsWith(suffix));
310 }
311 }
312
313
314 /**Reverses the existing order of exhibits in situ.
315 * Takes no parameters, so all instances of this class are equal.
316 */
317 public static final class sortReverse implements SortIF
318 {
319 /**Unique ID. */
320 private static final long serialVersionUID = -3097396087855039990L;
321 /**Ignores its arguments. */
322 public sortReverse(final String args[]) { this(); }
323 /**Programmatic constructor. */
324 public sortReverse() { }
325 /**All instances of this class are identical. */
326 @Override
327 public boolean equals(final Object obj) { return((obj instanceof sortReverse)); }
328 /**Hash is constant. */
329 @Override
330 public int hashCode() { return(-99); }
331
332 /**Reverses the order of its arguments in situ. */
333 public ExhibitFull[] sort(final AllExhibitProperties aep,
334 final Name.ExhibitFull[] exhibitNames)
335 {
336 Collections.reverse(Arrays.asList(exhibitNames));
337 return(exhibitNames);
338 }
339 }
340
341 /**Selects N exhibits at random and randomises their order.
342 * If the number of exhibits is less than N then this returns
343 * all the exhibits.
344 * <p>
345 * The first value is the unsigned, positive, base-ten integer value
346 * of N.
347 * <p>
348 * If the second argument is missing or null then the random number
349 * sequence used is taken differently each time we run, else the
350 * hash of the second string is taken as the random number seed.
351 */
352 public static final class sortRandomN implements SortIF
353 {
354 /**Unique serial ID. */
355 private static final long serialVersionUID = 2078921830149773203L;
356
357 /**Takes exactly one or two arguments.
358 * The first argument is the strictly-positive N.
359 * <p>
360 * The second argument, if absent or null, indicates that
361 * a different random number sequence is used on each run.
362 * If present and non-null then its hash is used to seed a
363 * random number generator to give repeatable results.
364 */
365 public sortRandomN(final String args[])
366 {
367 this(Integer.parseInt(args[0]),
368 ((args.length > 1) ? args[1] : null));
369 // if((args.length < 1) || (args.length > 2))
370 // { throw new IllegalArgumentException(); }
371 }
372
373 /**More efficient programmatic constructor.
374 * Takes count as the first (int) argument and
375 * a String to drive the hash or null for a totally random selection.
376 */
377 public sortRandomN(final int count, final String hashSrc)
378 {
379 if(count <= 0) { throw new IllegalArgumentException(); }
380 n = count;
381 seed = hashSrc;
382 }
383
384 /**The upper bound on result size. */
385 private final int n;
386 /**The seed; if null we use a non-repeatable source. */
387 private final String seed;
388
389 public ExhibitFull[] sort(final AllExhibitProperties aep, final Name.ExhibitFull[] exhibitNames)
390 {
391 // Set up our random-number source.
392 final Random rnd = (seed == null) ?
393 Rnd.fastRnd : (new Random(seed.hashCode()));
394
395 Name.ExhibitFull result[];
396 if(exhibitNames.length <= n)
397 {
398 // We don't need to discard any of our input set...
399 result = exhibitNames;
400 }
401 else
402 {
403 // We need to pick n items at random
404 // from our input set, with no duplicates.
405 // We'll make a boolean array logically beside
406 // our input array, and set a slot true when
407 // we've used the input value at the same index...
408 final boolean used[] = new boolean[exhibitNames.length];
409
410 // Output array...
411 result = new Name.ExhibitFull[n];
412
413 // This will work badly where n is large and just
414 // smaller than the number of available exhibits.
415 // We should possibly reverse the mechanism in
416 // this case, and mark values not to use...
417 for(int i = n; --i >= 0; )
418 {
419 // Go on looking for an input slot that we have not yet found...
420 for( ; ; )
421 {
422 // Pick an input slot at random...
423 final int slot = rnd.nextInt(exhibitNames.length);
424 // If we've already used the value in this slot, try again...
425 if(used[slot]) { continue; }
426 // Whopee! A new one; capture this one and break out...
427 result[i] = exhibitNames[slot];
428 used[slot] = true;
429 break;
430 }
431 }
432 }
433
434 // Randomise the result set...
435 Collections.shuffle(Arrays.asList(result), rnd);
436
437 return(result);
438 }
439
440 /**Equality depends on identical seeds and result size.
441 * In other words this checks equivalence rather than the
442 * results being identical each time.
443 */
444 @Override
445 public boolean equals(final Object obj)
446 {
447 if(!(obj instanceof sortRandomN)) { return(false); }
448 final sortRandomN other = (sortRandomN) obj;
449
450 if(n != other.n) { return(false); }
451
452 // If exactly the same seed (eg a static final), then equal.
453 if(seed == other.seed) { return(true); }
454
455 // If we are null (and therefore the other is not), then not equal.
456 if(seed == null) { return(false); }
457
458 // Check seeds for equality.
459 return(seed.equals(other.seed));
460 }
461
462 /**Hash depends on the seed and the result size limit. */
463 @Override
464 public int hashCode()
465 {
466 return(n + ((seed == null) ? 0 : seed.hashCode()));
467 }
468 }
469
470
471 /**Sorts exhibits by (ascending) timestamp, ie oldest first.
472 * Takes no parameters, so all instances of this class are equal.
473 */
474 public static final class sortByTimestamp implements SortIF
475 {
476 /**Unique serial ID. */
477 private static final long serialVersionUID = -8775508119553092181L;
478
479 /**Ignores its arguments.
480 */
481 public sortByTimestamp(final String args[])
482 { }
483
484 /**Programmatic constructor.
485 */
486 public sortByTimestamp()
487 { }
488
489 /**Sorts its arguments in situ by ascending timestamp (oldest first).
490 * Ties are broken by full name.
491 */
492 public ExhibitFull[] sort(final AllExhibitProperties aep,
493 final Name.ExhibitFull[] exhibitNames)
494 {
495 // Get all ESA values in one go, sort in place, then put back the names.
496 final int length = exhibitNames.length;
497 final ExhibitStaticAttr[] esas = new ExhibitStaticAttr[length];
498 for(int i = length; --i >= 0; )
499 { esas[i] = aep.aeid.getStaticAttr(exhibitNames[i]); }
500
501 Arrays.sort(esas,
502 new Comparator<ExhibitStaticAttr>(){
503 public final int compare(final ExhibitStaticAttr e1, final ExhibitStaticAttr e2)
504 {
505 if(e1.timestamp < e2.timestamp) { return(-1); } // Correct.
506 if(e1.timestamp > e2.timestamp) { return(+1); } // Wrong.
507 // Break ties unconditionally by name...
508 return(e1.compareTo(e2));
509 }
510 });
511
512 // Copy back the names...
513 for(int i = length; --i >= 0; )
514 { exhibitNames[i] = esas[i].getExhibitFullName(); }
515
516 return(exhibitNames);
517 }
518
519 /**All instances of this class are identical.
520 */
521 @Override
522 public boolean equals(final Object obj)
523 {
524 if(!(obj instanceof sortByTimestamp)) { return(false); }
525 return(true);
526 }
527
528 /**Hash is constant. */
529 @Override
530 public int hashCode()
531 {
532 return(0);
533 }
534 }
535
536
537 /**Sorts exhibits by (descending) goodness, ie best first.
538 * Takes no parameters, so all instances of this class are equal.
539 * <p>
540 * Cannot supply all the data needed to recompute a full version,
541 * so works with what has already been computed or may force an
542 * approximate ("fast") computation.
543 * <p>
544 * This permits 'stale' values since forcing full recomputation may be very slow
545 * or simply be impossible for some exhibits.
546 */
547 public static final class sortByGoodness implements SortIF
548 {
549 /**Unique serial ID. */
550 private static final long serialVersionUID = -5596318067630417407L;
551
552 /**Ignores its arguments.
553 */
554 public sortByGoodness(final String args[])
555 {
556 nMax = 0;
557 }
558
559 /**Programmatic constructor.
560 * @param nMax if positive, is the maximum number of exhibits returned
561 */
562 public sortByGoodness(final int nMax)
563 {
564 this.nMax = nMax;
565 }
566
567 /**If positive, is the maximum number of exhibits returned. */
568 private final int nMax;
569
570 /**Minimum number of values worth attempting to filter in parallel; strictly positive.
571 * This plays off parallelism overhead vs available concurrency
572 * but should not be especially critical an order of magnitude either way.
573 */
574 private static final int MIN_SIZE_FOR_CONCURRENCY = 256;
575
576 /**Sorts its arguments in situ by descending goodness.
577 * Ties are broken by full name.
578 */
579 public ExhibitFull[] sort(final AllExhibitProperties aep,
580 final Name.ExhibitFull[] exhibitNames)
581 {
582 // Dummy/default for quick EPCM approximation.
583 final GenProps gp = new GenProps();
584
585 // Compute/fetch EPCM (int) goodness *once* for each entry, possibly in parallel.
586 final int length = exhibitNames.length;
587
588 // Don't attempt to parallelise for small data sets nor on uni-processor JVMs.
589 // Assume that computing (fast-approximation) EPCM values is mainly CPU-bound.
590 final boolean parallelise = ((ThreadUtils.AVAILABLE_PROCESSORS > 1) && (length > MIN_SIZE_FOR_CONCURRENCY));
591
592 final Map<Name.ExhibitFull,Integer> goodness = parallelise ?
593 new ConcurrentHashMap<Name.ExhibitFull,Integer>(length, 0.5f, 2*ThreadUtils.AVAILABLE_PROCESSORS) /* Safe to post updates concurrently. */ :
594 new HashMap<Name.ExhibitFull,Integer>(length);
595
596 if(!parallelise)
597 {
598 // Simple single-threaded method.
599 for(final Name.ExhibitFull name : exhibitNames)
600 { goodness.put(name, aep.getExhibitPropsComputableMutable(name, true, gp, null, null).getGoodness()); }
601 }
602 else
603 {
604 // Have the number of parallel tasks go up slowly (with the log of the work size)
605 // and capped by the number of available CPUs.
606 final int nTasks = Math.min(ThreadUtils.AVAILABLE_PROCESSORS, GenUtils.log2Approx(length));
607 assert(nTasks > 1);
608 assert(nTasks < length);
609
610 // Size of each partition: one partition/task will have any extra residue.
611 final int partitionSize = length / nTasks;
612 assert(partitionSize > 0);
613
614 // Create the tasks.
615 final Collection<Callable<Object>> tasks = new ArrayList<Callable<Object>>(nTasks);
616 int taskEnd = length;
617 for(int i = nTasks; --i >= 0; taskEnd -= partitionSize)
618 {
619 // Note that task 0 may be slightly larger than the other tasks.
620 final int end = taskEnd; // Exclusive end in array.
621 final int start = (i == 0) ? 0 : (taskEnd - partitionSize); // Inclusive start in array.
622 tasks.add(new Callable<Object>(){
623 public Object call()
624 {
625 // Directly accumulate results in the thread-safe map.
626 for(int i = end; --i >= start; )
627 {
628 final Name.ExhibitFull name = exhibitNames[i];
629 goodness.put(name, aep.getExhibitPropsComputableMutable(name, true, gp, null, null).getGoodness());
630 }
631 return(null);
632 }
633 });
634 }
635 // Execute them all, as concurrently as possible.
636 try { ThreadUtils.computeIntensiveThreadPool.invokeAll(tasks); }
637 // If we were interrupted or an exception was thrown, re-throw here.
638 catch(final Exception e) { throw new RuntimeException("unexpected exception", e); }
639 }
640
641 // TODO: replace this with a heap of the first nMax items for efficiency.
642 Arrays.sort(exhibitNames,
643 new Comparator<Name.ExhibitFull>(){
644 public final int compare(final Name.ExhibitFull n1, final Name.ExhibitFull n2)
645 {
646 // Sort best items first.
647 final int g1 = goodness.get(n1);
648 final int g2 = goodness.get(n2);
649 if(g1 > g2) { return(-1); } // Correct.
650 if(g1 < g2) { return(+1); } // Wrong.
651
652 // Break ties (not resolved by goodness).
653 // Sort newer items first
654 // as potentially more interesting
655 // and so that they will get looked at!
656 final ExhibitStaticAttr e1 = aep.aeid.getStaticAttr(n1);
657 final ExhibitStaticAttr e2 = aep.aeid.getStaticAttr(n2);
658 if(e1.timestamp > e2.timestamp) { return(-1); }
659 if(e1.timestamp < e2.timestamp) { return(+1); }
660
661 // Break tie unconditionally by name.
662 // Should almost never be used.
663 // Only here to guarantee a total ordering.
664 return(TextUtils.compare(e1.getCharSequence(), e2.getCharSequence()));
665 }
666 });
667
668 // If we are limiting the result length,
669 // then return the "creme de la creme" here!
670 if((nMax > 0) && (nMax < length))
671 {
672 // Return a subset of the items; the best/first ones in fact...
673 return(Arrays.copyOf(exhibitNames, nMax));
674 }
675
676 return(exhibitNames);
677 }
678
679 /**All instances of this class are identical where their nMax is.
680 */
681 @Override
682 public boolean equals(final Object obj)
683 {
684 if(!(obj instanceof sortByGoodness)) { return(false); }
685 final sortByGoodness other = (sortByGoodness) obj;
686 return(nMax == other.nMax);
687 }
688
689 /**Hash is constant. */
690 @Override
691 public int hashCode()
692 {
693 return(nMax);
694 }
695 }
696
697 /**Simple sort of exhibits by name.
698 * By default sorts in natural String order,
699 * but can take a Comparator argument to use
700 * (eg to use a 'smart' ordering(s) for human benefit).
701 */
702 public static final class sortByName implements SortIF
703 {
704 /**Unique serial ID. */
705 private static final long serialVersionUID = 9042557392754106067L;
706
707 /**Simple sort; default String lexical ordering; can use null instead. */
708 public static final String SIMPLE = "simple";
709
710 /**Simple smart sort; essentially case-insensitive ordering on file component. */
711 public static final String SIMPLE_SMART = "simpleSmart";
712
713 /**Full smart sort; gets current version of comparator during sort().
714 * This uses the data-dependent sort
715 * (dependent on the set of attribute words)
716 * that comes with the data so that it is always current.
717 */
718 public static final String SMART = "smart";
719
720 /**Sort Comparator; null for default (simple) sort. */
721 private final String sortType;
722
723 /**Call with argument giving sort type (or null for natural ordering).
724 * Recognised values are:
725 * <ul>
726 * <li>simple (default String lexical ordering)
727 * <li>simpleSmart (essentially case-insensitive ordering on file component)
728 * <li>smart (fully-smart case-insensitive sort using attribute words)
729 * </ul>
730 */
731 public sortByName(final String sortType)
732 {
733 this.sortType = sortType;
734 }
735
736
737 /**Call with no args or single argument giving sort type.
738 * Recognised values are:
739 * <ul>
740 * <li>simple (default String lexical ordering)
741 * <li>simpleSmart (essentially case-insensitive ordering on file component)
742 * <li>smart (fully-smart case-insensitive sort using attribute words)
743 * </ul>
744 */
745 public sortByName(final String args[])
746 {
747 if((args != null) && (args.length > 0))
748 {
749 sortType = args[0];
750 }
751 else
752 { sortType = null; } // Default sort.
753 }
754
755 public ExhibitFull[] sort(final AllExhibitProperties aep, final Name.ExhibitFull[] exhibitNames)
756 {
757 // Simple smart order: not dependent on attribute words.
758 if(sortType.equals(SIMPLE_SMART))
759 { Arrays.sort(exhibitNames, ExhibitName.SIMPLE_SMART_ORDER); }
760
761 // Fully-smart sort: dependent on attribute words.
762 else if(sortType.equals(SMART))
763 { Arrays.sort(exhibitNames, ExhibitAttrUtils.getAttrWords().SMART_ORDER); }
764
765 // Default case (eg if comp is SIMPLE or null or unknown).
766 // Simple sort in String order: fast but not human-friendly.
767 // Not dependent on attribute words.
768 else
769 { Arrays.sort(exhibitNames); }
770 return(exhibitNames);
771 }
772
773 /**The hashcode of the sort, or null for the default sort. */
774 @Override
775 public int hashCode()
776 {
777 return(sortType == null ? 91 : sortType.hashCode());
778 }
779
780 /**Tests equality between two sortByName objects.
781 * For this to be the case the two comparator objects must
782 * be == (eg both be null), or compare equals().
783 */
784 @Override
785 public boolean equals(final Object obj)
786 {
787 //System.err.println("sortByName.equals() ...");
788
789 // Always equal to myself.
790 if(obj == this) { return(true); }
791
792 if(!(obj instanceof sortByName)) { return(false); }
793 final sortByName other = (sortByName) obj;
794
795 // If exactly the same sort type (eg a static final), then equal.
796 if(other.sortType == sortType) { return(true); }
797
798 // If type is null (and therefore the other is not), then not equal.
799 if(sortType == null) { return(false); }
800
801 // Compare sort types for equality.
802 if(!sortType.equals(other.sortType)) { return(false); }
803
804 //System.err.println("sortByName.equals() = true");
805
806 return(true); // Equivalent sorts.
807 }
808 }
809
810 }