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.io.IOException;
032    import java.io.Serializable;
033    import java.lang.ref.SoftReference;
034    import java.lang.ref.WeakReference;
035    import java.util.ArrayList;
036    import java.util.Arrays;
037    import java.util.Collection;
038    import java.util.Collections;
039    import java.util.Date;
040    import java.util.List;
041    import java.util.Observable;
042    import java.util.Observer;
043    import java.util.Timer;
044    import java.util.TimerTask;
045    
046    import org.hd.d.pg2k.svrCore.AllExhibitImmutableData;
047    import org.hd.d.pg2k.svrCore.GenUtils;
048    import org.hd.d.pg2k.svrCore.MemoryTools;
049    import org.hd.d.pg2k.svrCore.Name;
050    import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
051    import org.hd.d.pg2k.svrCore.Rnd;
052    
053    import ORG.hd.d.IsDebug;
054    
055    /**
056     * Created by IntelliJ IDEA.
057     * User: Damon Hart-Davis
058     * Date: 24-May-2003
059     * Time: 17:42:56
060     */
061    
062    /**JavaBean that can filter/sort and cache selections of exhibits.
063     * Designed to be used as a bean in a JSP or servlet, and can cache
064     * filter/sort results, for example, at application level or in a
065     * user's session.
066     * <p>
067     * This applies its filter to the input List (or entire exhibit set)
068     * that it is provided with; deriving classes may make all or part
069     * of the result directly or indirectly available.
070     * <p>
071     * This silently recomputes its results if they may have been affected by
072     * any update in the exhibit data (or when select() is called differently
073     * from the last call to the same instance).  The cached state is not persisted
074     * if the bean is serialised, but the filter description is.  This is
075     * only meant to be persisted, for example, to be passed between
076     * multiple instances of a distributable WAR and not to be stored for lengthy
077     * periods on disc, so does not use some of the fancier support for
078     * serialisation.
079     * <p>
080     * This uses but does not cache a DataSourceBean which can be supplied
081     * explicitly or recovered implicitly from a servlet context
082     * (at application scope), and can take the whole set of exhibits as
083     * input or can use the output of another filter.  In all cases the output
084     * is only recomputed if the DataSourceBean.exhibitDataVersionHash()
085     * method result changes (so any randomised or conditional element had better be the
086     * last stage of the chain and only have (say) page or request scope),
087     * or if select() is called with different args to the last call on this
088     * instance (so, if for example, a different List argument is passed;
089     * select does a test on the hash code of the select() List arg).
090     * <p>
091     * This is thread-safe and as concurrent as possible, with the intention
092     * that it may be involved in many users' activity simultaneously.
093     * <p>
094     * The expression must be set (ideally exactly once) before any attempt
095     * is made to fetch results.  For JSPs this set can be done in
096     * the body of the JSP so that it is done only on creation of the bean
097     * as a small efficiency gain.
098     * <p>
099     * If the expression is changed and is not equals() to the previous value,
100     * the cache is cleared.
101     * <p>
102     * The result returned by any select() operation is an immutable, thread-safe,
103     * RandomAccess List (eg like ArrayList).  (This cannot be enforced on
104     * derived classes, but is part of the general contract.)
105     * <p>
106     * The expression is of the form:
107     * <pre>
108     *     expr = expr sortExpr
109     *          | expr filterExpr
110     *          | ALL
111     *     sortExpr   = sort(args)
112     *     filterExpr = filter(args)
113     *                | filterExpr filterExpr &amp;&amp;
114     *                | filterExpr filterExpr ||
115     *                | filterExpr !
116     * </pre>
117     * ie a filter on its own is applied to all the available exhibits,
118     * or can be negated or combined (ANDed or ORed) with other filter
119     * expressions to make more complex filter expressions.
120     * A complete expression is a series of filter expressions and/or
121     * sorts applied in a pipeline to the result of some filtering.
122     * <p>
123     * The expression syntax is reverse Polish amd dataflow is from left
124     * to right.
125     * <p>
126     * This class implements the Observer interface and when it receives
127     * a notification its immediately clears its cache by default.
128     * This is in addition to the normal automatic cache discard in select()
129     * when the imput has changed, and is an optimisation; behvaiour is correct
130     * without it.
131     * This mechanism helps discard stale data quickly and thus make better use of memory.
132     * <p>
133     * This can also be set to expire automatically from time to time,
134     * for example to allow for slowly mutating/changing data
135     * such as ExhibitPropsComputableMutable items.
136     * The default is not to expire periodically.
137     * <p>
138     * This Serializable so as to be able to be stored in
139     * a servlet session; nothing especially long-lived or sensitive.
140     * <p>
141     * TODO: Should do validation on deserialisation.
142     */
143    public abstract class AbstractFilterBean implements Serializable
144        {
145        /**If true, become an Observer of the DataSourceBean so that we can eagerly discard a stale cache.
146         * Observing the DataSourceBean should not cause a deadlock
147         * as we do so only indirectly/asynchronously.
148         */
149        private static final boolean OBSERVE_DATASOURCEBEAN = true;
150    
151        /**The (filter/sorter) expression, or null if not yet set.
152         * Accessed under the instance lock.
153         * <p>
154         * This is persisted if the object is serialised.
155         */
156        private Expr expr;
157    
158        /**If true then led cached value go if we run short of memory, eg hold it via a SoftReference.
159         * This is useful for infrequently-accessed and not-too-expensive
160         * filters.
161         * <p>
162         * This can be set or reset at any time but is only effective when
163         * the cache is filled, so is probably done once, before the first
164         * set of values is retrieved.
165         * <p>
166         * By default this value is true and the cache is memory sensitive,
167         * ie won't cause the system to run out of memory.
168         * <p>
169         * Accessed under the instance lock.
170         * <p>
171         * This is persisted if the object is serialised.
172         */
173        private boolean memorySensitiveCache = true;
174    
175    
176    
177        /**Expiry interval in ms, zero (the default) means no automatic expiry; non-negative.
178         * The data may expire in a little less than this.
179         * <p>
180         * Is volatile so as to allow access without a lock.
181         */
182        private int expiryInterval;
183    
184        /**Get expiry interval in ms, zero (the default) means no automatic expiry; non-negative.
185         * The data may expire in a little less than this.
186         */
187        public int getExpiryInterval()
188            { return(expiryInterval); }
189    
190        /**Set expiry interval in ms, zero means no automatic expiry; non-negative.
191         * The data may expire in a little less than this
192         * at random to spread out expiries and recalculations,
193         * or may expire more slowly if the system is short of power
194         * or otherwise under stress to help reduce system load.
195         */
196        public void setExpiryInterval(final int expiryInterval)
197            {
198            if(expiryInterval < 0) { throw new IllegalArgumentException(); }
199            this.expiryInterval = expiryInterval;
200            }
201    
202    
203    
204    
205    
206        /**Get exhibitDataHash. */
207        private int getExhibitDataHash()
208            { return(exhibitDataHash); }
209    
210        /**Get exhibitDataHash. */
211        private void setExhibitDataHash(final int exhibitDataHash)
212            { this.exhibitDataHash = exhibitDataHash; }
213    
214        /**The exhibit data hash when the cache was computed.
215         * If this changes we should regard the cache as invalid.
216         * <p>
217         * Is volatile so that it can be accessed from multiple
218         * threads without taking a lock.
219         * <p>
220         * Should be accessed only by getExhibitDataHash() and
221         * setExhibitDataHash().
222         * <p>
223         * Zero when the cache is clear/invalid (the initial state).
224         */
225        private transient volatile int exhibitDataHash;
226    
227        /**The hashCode of the last List arg to select or 0 if none.
228         * This is used to try to cheaply ensure that we discard our
229         * cache if our calling pattern changes (which should not
230         * happen, but this may save us from poor usage).
231         * <p>
232         * Accessed under the instance lock and is private to
233         * _select() and clearCache().
234         * <p>
235         * Zero when the cache is clear.
236         */
237        private transient int lastListArgHashCode;
238    
239        /**The cached results (or null if not yet computed).
240         * This consists of an immutable RandomAccess List,
241         * not sorted unless the expression sorts it,
242         * of the accepted results;
243         * no slots in the List are duplicates nor nulls.
244         * <p>
245         * If the exhibit data hash (or select()-arg hash)
246         * changes then we should regard the cache as invalid.
247         * <p>
248         * This is null only if not yet computed; it is zero length
249         * if the result set was empty.
250         * <p>
251         * Accessed under the instance lock and is private to
252         * _select() and clearCache().
253         * <p>
254         * Is either a List or a SoftReference wrapper around one.
255         */
256        private transient Object _basicCache;
257    
258        /**Embedded observer object.
259         * Designed to break link between DataSourceBean and our class
260         * to allow us to be GCed even if we are recorded as an observer.
261         * <p>
262         * This inner object has only a SoftReference to us.
263         */
264        private final MyObserver observer = new MyObserver(this);
265    
266    
267        /**Callback for 'emergency free' from MemoryTools; never null after registration.
268         * We create and register this when we first call _select()
269         * and re-register it when it is called back.
270         * <p>
271         * When it is called it unguards any SoftReferences
272         * allowing memory to be freed rather than an OutOfMemoryException
273         * getting thrown somewhere in the application.
274         * <p>
275         * It is assumed that MemoryTools holds this via a WeakReference
276         * so that it will not inhibit this instance being GCed.
277         */
278        private Runnable emergencyFreeHook;
279    
280    
281        /**Name for this instance; by default null. */
282        private String name;
283    
284        /**Get the name for this instance; by default null. */
285        public synchronized String getName() { return(name); }
286    
287        /**Set the name for this instance; by default null. */
288        public synchronized void setName(final String n) { name = n; }
289    
290        /**Get human-readable summary; never null. */
291        @Override public synchronized String toString()
292            {
293            final StringBuilder sb = new StringBuilder(64);
294            final String className = getClass().getSimpleName();
295            sb.append("".equals(className) ? "AbstractFilterBean" : className);
296            final String n = name;
297            if(null != n) { sb.append(':').append(n); }
298            return(sb.toString());
299            }
300    
301    
302        /**Set the `memory-sensitive' flag for the cache.
303         * This is useful for infrequently-accessed and not-too-expensive
304         * filters; if set true then the cache is automatically discarded
305         * if we run short of heap space.
306         * <p>
307         * This can be set or reset at any time but is only effective when
308         * the cache is filled, so is probably best done once, before the first
309         * set of values is retrieved.
310         * <p>
311         * By default this value is true and the cache is not memory sensitive.
312         * <p>
313         * This is persisted if the object is serialised.
314         */
315        public synchronized void setMemorySensitiveCache(final boolean f)
316            { memorySensitiveCache = f; }
317    
318        /**Get the `memory-sensitive' flag for the cache.
319         */
320        public synchronized boolean getMemorySensitiveCache()
321            { return(memorySensitiveCache); }
322    
323        /**Set the simple single (input) filter/sort expression directly.
324         * If the expression is set to a new value not equals() to the old,
325         * then the cache is cleared.
326         *
327         * @throws java.lang.IllegalArgumentException  if the argument is null
328         */
329        public synchronized void setExpr(final Expr _expr)
330            throws IllegalArgumentException, IllegalStateException
331            {
332            if(_expr == null) { throw new IllegalArgumentException(); }
333            if(!_expr.equals(expr)) { clearCache(); }
334            expr = _expr;
335            }
336    
337        /**Get the simple single (input) filter/sort expression directly.
338         * Has protected scope to prevent every Tom Dick and Harry fiddling with
339         * a possibly-mutable expression.
340         * <p>
341         * This can be overridden to provide some extra filtering or sorting, for example,
342         * in derived classes.
343         *
344         * @return the expression or null if none yet set
345         */
346        protected synchronized Expr getExpr()
347            {
348            return(expr);
349            }
350    
351        /**Set the filter/sorter expression as a textual expression.
352         * Will clear the cache if the expression changes.
353         *
354         * @throws java.lang.IllegalArgumentException  if the argument is null or unparseable
355         */
356        public synchronized void setExpr(final String _expr)
357            throws IllegalArgumentException, IllegalStateException
358            {
359            if(_expr == null) { throw new IllegalArgumentException(); }
360    
361            // PARSE THE EXPESSION.
362            throw new RuntimeException("NOT IMPLEMENTED");
363            }
364    
365        /**Clears the cache of the basic set of filtered/sorted items.
366         * Replaces any cached item with null and
367         * resets the internal hashes of the data and argument to null.
368         * <p>
369         * A deriving class can override this to clear any extra state it maintains,
370         * but must chain to this first in order to clear the underlying caches
371         * and free other resources.
372         * <p>
373         * This routine is synchronized and overriding methods
374         * almost certainly should be so too.
375         */
376        protected synchronized void clearCache()
377            {
378            _basicCache = null;
379            setExhibitDataHash(0);
380            lastListArgHashCode = 0;
381    
382            // Clear retained references entirely, if any.
383            _retainedCacheRefs = null;
384    
385            // Kill off any timer task(s) associated with this cache.
386            final TimerTask ttExpiry = _ttExpiry;
387            if(ttExpiry != null)
388                {
389                _ttExpiry = null;
390                ttExpiry.cancel();
391                }
392            final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
393            if(ttPurgeRetainedRefs != null)
394                {
395                _ttPurgeRetainedRefs = null;
396                ttPurgeRetainedRefs.cancel();
397                }
398            }
399    
400        /**Invalidate the cache.
401         * Can be called by anyone;
402         * may force the cache to be invalidated immediately or before next use.
403         * <p>
404         * May need to grab locks.
405         */
406        public void invalidate()
407            { clearCache(); }
408    
409        /**Timer task if any, to eagerly force expiry and release of resources, associated with this cache.
410         * Marked transient since we couldn't persist this
411         * but in any case after deserialisation would expect it to be set up again if needed.
412         * <p>
413         * Accessed under the instance lock.
414         */
415        private transient TimerTask _ttExpiry;
416    
417        /**Expiry time of the cache (ie its content is to be considered invalid after this).
418         * Private to _isValid and accessed in the scope of its lock (instance lock).
419         * May also be peeked at by the ExpiryTask under the lock.
420         * <p>
421         * Transient to force an immediate recomputation if deserialised.
422         */
423        private transient long expiryTime;
424    
425        /**Returns cache reference if the cache still seems to be valid.
426         * If null, then the cache needs to be rebuilt.
427         * As a side-effect, this will clear an invalid cache.
428         * <p>
429         * Parameters as for _select(),
430         * return value as for _select() or null if invalid.
431         * <p>
432         * Cheaper than calling _select() to force the cache to be invalidated
433         * since it does not attempt to rebuild the cache.
434         */
435        @SuppressWarnings("unchecked")
436        protected synchronized List<Name.ExhibitFull> _isValid(final DataSourceBean ds,
437                                                               final List<Name.ExhibitFull> inputSet)
438            throws IOException
439            {
440            // Cache is (1) null or (2) a List or (3) a SoftReference to a List.
441            final Object _bc = _basicCache;
442            final List<Name.ExhibitFull> currentCache = (_bc instanceof SoftReference)
443              ? ((List<Name.ExhibitFull>) (((SoftReference) _bc).get()))
444              : ((List<Name.ExhibitFull>) _bc);
445    
446            final long now;
447            final boolean isInvalid =
448               (currentCache == null) ||
449               ((expiryInterval != 0) && (expiryTime < (now = System.currentTimeMillis())) &&
450                   // Postpone expiry somewhat when the system (temporarily) needs to conserve resources...
451                   ((!GenUtils.mustConservePower()) || (expiryTime+expiryInterval < now))) ||
452               (lastListArgHashCode != _computeListArgHashCode(inputSet)) ||
453               (getExhibitDataHash() != ds.exhibitDataVersionHash());
454    
455            if(isInvalid)
456                {
457                // Abolish old cached values.
458                clearCache();
459    
460                return(null);
461                }
462    
463            return(currentCache);
464            }
465    
466        /**Compute the hash over our List argument, used by _isValid() and _select().
467         * Note that the identityHashCode() is used.
468         */
469        private static int _computeListArgHashCode(final List<Name.ExhibitFull> inputSet)
470            { return((inputSet == null) ? 0 : System.identityHashCode(inputSet)); }
471    
472        /**Records the time at which _select() was last started; 0 if not yet called (or if deserialised).
473         * This effectively (conservatively) notes when the cached value was last requested/used,
474         * as well as providing a baseline to measure how long value (re)generation took.
475         * <p>
476         * Only written to by _select(), under the instance lock.
477         */
478        private transient long _select_startTime;
479    
480        /**Records the time taken to complete _select() and descendants; non-negative and 0 if not yet called (or if deserialised).
481         * This is based on when the last request was made
482         * to hold a strong reference to internal state.
483         * <p>
484         * May not be maintained if the cache is not memory sensitive.
485         * <p>
486         * TODO: May be used to give the cached state a new lease of life on each access.
487         * <p>
488         * Only written to by _retainCacheRef(), under the instance lock.
489         */
490        private transient long _select_runTime;
491    
492        /**Internal method that computes the result of accepted exhibits.
493         * The result is an immutable list of full exhibit names.
494         * <p>
495         * The ds argument must not be null.
496         * <p>
497         * If the inputSet argument is null the whole set of
498         * exhibits is used, else the supplied set is used.
499         * This must be a List of String items with no nulls nor
500         * duplicates.
501         * <p>
502         * This will (re)compute its results if necessary, ie:
503         * <ul>
504         * <li>there are no cached results,
505         * <li>the exhibit hash has changed,
506         * <li>the List argument identity hashCode has changed.
507         * </ul>
508         * otherwise this routine is relatively fast and returns the cached List.
509         * <p>
510         * This never returns null.
511         * <p>
512         * Null names and names that do not represent items currently
513         * in the exhibit set are silently dropped.
514         * <p>
515         * This routine may register this instance with the data source
516         * as an Observer of exhibit set changes in order to be able to quickly
517         * invalidate/release our (then-stale) cache upon any changes.
518         *
519         * @param inputSet  null, or a list of full exhibit names
520         *
521         * @throws IllegalStateException  if the filter has not been set
522         */
523        protected synchronized List<ExhibitFull> _select(final DataSourceBean ds,
524                                                         final List<ExhibitFull> inputSet)
525            throws IOException, IllegalStateException
526            {
527            if(expr == null) { throw new IllegalStateException(); }
528    
529            // Note when we started this call (and any computation work).
530            _select_startTime = System.currentTimeMillis();
531    
532            // Cache is (1) null or (2) a List or (3) a SoftReference to a List.
533            List<Name.ExhibitFull> currentCache = _isValid(ds, inputSet);
534    
535            // Hash over all exhibits...
536            final int currentHash = ds.exhibitDataVersionHash();
537    
538            // If the cache is invalid, (re)build it.
539            // The _isValid() call will have cleared the cache already if invalid.
540            if(currentCache == null)
541                {
542                // Warn against possible misuse of a filter, dumping a stack trace.
543                if(lastListArgHashCode != 0)
544                    {
545                    final Error e = new Error("WARNING: FilterBean List argument changed between non-null input List items which is probably a programming error; please check");
546                    e.printStackTrace();
547                    }
548    
549                // Recompute the results...
550    //if(ORG.hd.d.IsDebug.isDebug) { System.err.println("[Recomputing AbstractFilterBean result for: " + this + ".]"); }
551    
552                final AllExhibitImmutableData aeid = ds.getAllExhibitImmutableData(-1);
553    
554                // Create a new (empty) retained-references collection.
555                // This must dumbly add all the values given without examining them,
556                // eg like a List rather than a Set.
557                _retainedCacheRefs = new ArrayList<Object>();
558                // Kill any pending task to clear the refs (from a previous run).
559                final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
560                if(ttPurgeRetainedRefs != null)
561                    {
562                    _ttPurgeRetainedRefs = null;
563                    ttPurgeRetainedRefs.cancel();
564                    }
565    
566                // Use the input exhibit set if supplied,
567                // else examine the entire extant exhibit set...
568                List<Name.ExhibitFull> inputNames;
569                if(inputSet == null) // No input set; start with all names.
570                    { inputNames = aeid.getAllExhibitNamesSorted(); }
571                else
572                    {
573                    // Extract String[] copy of input List.
574                    //
575                    // We should consider verifying no duplicate inputs
576                    // in debug mode...
577                    inputNames = new ArrayList<Name.ExhibitFull>(inputSet);
578                    // Quick check for gross problems in copy...
579                    if((inputNames.size() > 0) && (inputNames.get(0) == null))
580                        { throw new IllegalArgumentException("problem copying inputSet"); }
581                    }
582    
583                // We can optionally Observe the DataSourceBean
584                // now that we've taken some state from it
585                // so as to discard our cache when the AEP changes.
586                // We avoid a potential deadlock hazard by
587                // registering our observer and having it call back invalidate()
588                // immediately but asynchronously via our timer.
589                if(OBSERVE_DATASOURCEBEAN)
590                    {
591                    lifetimeManager.schedule((new TimerTask() {
592                        @Override public void run() { ds.addObserver(observer); }
593                        }), 0); // ASAP.
594                    }
595    
596                // Apply the expression to the input set...
597                final Name.ExhibitFull[] rawResult = getExpr().eval(ds.getAllExhibitProperties(-1L), inputNames.toArray(new Name.ExhibitFull[inputNames.size()])); // FIXME: avoid expensive copying
598                // ...and prepare to cache an unmodifiable (and thread-safe) random-access copy.
599                currentCache = Collections.unmodifiableList(Arrays.asList(rawResult));
600    
601                // Cache the data, with a strong or soft reference as appropriate.
602                if(memorySensitiveCache)
603                    {
604                    _basicCache = new SoftReference<List<Name.ExhibitFull>>(currentCache);
605    
606                    // Allow premature/emergency expiry of cache
607                    // (though only effective for memory-sensitive instances)
608                    // if called back critically short of memory.
609                    if(null == emergencyFreeHook)
610                        {
611                        final MemoryTools.RecurrentEmergencyFreeHandle r = new MemoryTools.RecurrentEmergencyFreeHandle() {
612                            public void run() { _clearRetainedRefs(); }// Allow retained refs to be freed, without blocking.
613                            };
614                        emergencyFreeHook = r;
615                        MemoryTools.registerRecurrentEmergencyFreeHandle(r); // Initial registration.
616                        }
617                    }
618                else
619                    { _basicCache = currentCache; }
620                // Always force basic computation-time calculation.
621                // If memory-sensitive, protect this from premature expiry.
622                _retainCacheRef(currentCache);
623    
624                // We use the hash we got before recomputing the results
625                // so that we recompute again later if the cached
626                // data changed while we were in this function, ie
627                // we err on the side of caution.
628                setExhibitDataHash(currentHash);
629    
630                // We store the hash of the current arg list,
631                // so if it changes we are likely to notice and
632                // will recompute the cached value.
633                lastListArgHashCode = _computeListArgHashCode(inputSet);
634    
635                // Set new expiry time if on automatic expiry.
636                // No more than specified expiry interval;
637                // may be as little as half that.
638                if(expiryInterval > 0)
639                    {
640                    expiryTime = System.currentTimeMillis() +
641                            ((expiryInterval/2)|1) + Rnd.fastRnd.nextInt((expiryInterval/2)|1);
642    
643                    // Use our timer to help us eagerly free up resources soon after expiry.
644                    // We do this regardless of general memory sensitivity of this cache.
645                    assert(null == _ttExpiry) : "we don't expect there to be an extant expiry task at this point";
646                    lifetimeManager.schedule(_ttExpiry = (new ExpiryTask(this)), new Date(expiryTime));
647                    }
648                }
649    
650            // Refresh the cache to give it a little more life if memory sensitive.
651            _retainCacheRef(null);
652    
653            // Return immutable list...
654            return(currentCache);
655            }
656    
657        /**Unique Serialisation class ID generated by http://random.hd.org/. */
658        private static final long serialVersionUID = -4409526925504499328L;
659    
660    
661        /**Task to actively expire a filter and release its memory.
662         * Only retains a weak reference to the filter bean
663         * so as not to prevent the bean being GCed.
664         */
665        private static final class ExpiryTask extends TimerTask
666            {
667            /**Weak ref to the bean; never null though the referent may be. */
668            private final WeakReference<AbstractFilterBean> beanWR;
669            ExpiryTask(final AbstractFilterBean afb) { beanWR = new WeakReference<AbstractFilterBean>(afb); }
670            @Override public void run()
671                {
672                final AbstractFilterBean bean = beanWR.get();
673                if(null == bean)
674                    {
675    if(IsDebug.isDebug) { System.out.println("AbstractFilterBean expired before we got there..."); }
676                    return;
677                    }
678    
679                synchronized(bean)
680                    {
681                    // Reduce the possibility of a race with other invalidation/recreation
682                    // by vetoing the invalidation if the instance seems no longer 'expired'.
683                    final long expiryTime = bean.expiryTime;
684                    if(expiryTime == 0) { return; }
685                    if(expiryTime > System.currentTimeMillis()) { return; }
686                    // OK, release the resources.
687                    bean.invalidate();
688                    }
689    if(IsDebug.isDebug) { System.out.println("INFO: eagerly expired FilterBean cache... "+bean); }
690                }
691            }
692    
693        /**Task to weaken retained references to soft from strong.
694         * Only retains a weak reference to the filter bean
695         * so as not to prevent the bean being GCed.
696         */
697        private static final class WeakenerTask extends TimerTask
698            {
699            /**Weak ref to the bean; never null though the referent may be. */
700            private final WeakReference<AbstractFilterBean> beanWR;
701    
702            /**Size of the set of retained refs when we were scheduled; non-negative. */
703            private final int sizeWhenScheduled;
704    
705            private WeakenerTask(final AbstractFilterBean afb, final int sizeWhenScheduled)
706                {
707                beanWR = new WeakReference<AbstractFilterBean>(afb);
708                this.sizeWhenScheduled = sizeWhenScheduled;
709                }
710    
711            /**Weaken retained refs to soft ref under instance lock, eg so as to prevent clear before select() finished. */
712            @Override public void run()
713                {
714                final AbstractFilterBean bean = beanWR.get();
715                if(null == bean)
716                    {
717    if(IsDebug.isDebug) { System.out.println("AbstractFilterBean expired while in the ref-weakening queue..."); }
718                    return;
719                    }
720    
721                synchronized(bean)
722                    {
723                    final Object rrLater = bean._retainedCacheRefs;
724                    if(rrLater == null) { return; } // Nothing to do.
725                    if(rrLater instanceof SoftReference) { return; } // Nothing to do.
726                    final Collection<Object> rr = (Collection<Object>) rrLater;
727                    if(rr.size() != sizeWhenScheduled) { return; } // We were superseded.
728                    // Weaken refs collection to soft reference itself...
729                    bean._retainedCacheRefs = new SoftReference<Collection<Object>>(rr);
730    if(IsDebug.isDebug && bean.memorySensitiveCache) { System.out.println("INFO: "+toString()+": weakened retained references, count="+sizeWhenScheduled); }
731                    }
732    
733                // Take this opportunity to do a tiny bit of housekeeping
734                // and clear up any cancelled tasks on the timer queue.
735                // May in incur O(n^2) cost total for n live AFB instances.
736                lifetimeManager.purge(); // Potentially help along GC a little...
737                }
738            }
739    
740        /**This object has only a WeakReference to the outer class to allow GC.
741         * This way it remains fairly safe for us to register with a DataSourceBean
742         * even if the outer class may be short-lived and churned in huge numbers.
743         * <p>
744         * FIXME: should find way to stop ourself from observing any DataSourceBean
745         *     (there can be at most one)
746         *     when the outer object is GCed or maybe when the cache is cleared.
747         *     Else, lots of redundant (if small) MyObserver objects may
748         *     accumulate in the DataSourceBean until the exhibit set changes.
749         */
750        private static final class MyObserver implements Observer
751            {
752            /**Capture a SoftReference to the outer class.
753             * @param outer  non-null reference to outer class.
754             */
755            MyObserver(final AbstractFilterBean outer)
756                { beanWR = new WeakReference<AbstractFilterBean>(outer); }
757    
758            /**WeakReference to outer class; never null. */
759            private final WeakReference<AbstractFilterBean> beanWR;
760    
761            /**This causes the cache to be cleared immediately.
762             * If [Object] arg is an Integer whose value is same as the value that
763             * we hold for the DataSourceBean.ds.exhibitDataVersionHash()
764             * then we return immediately on the assumption that this indicates
765             * that we still hold an up-to-date cache.
766             * This avoids extra builds and clears of the cache when the FilterBean
767             * is accessed frequently, and in particular between the underlying
768             * data changing and a call being made sometime later to this routine
769             * to clear all potentially-expired caches.
770             * <p>
771             * We use [Observable] o to deregister ourself from the Observable
772             * on the grounds that our next select() operation will (re-)register
773             * us with whatever DataSourceBean we are then using.
774             * This removal avoids leaving references to us
775             * from multiple DataSourceBeans or after we have otherwise expired,
776             * which would otherwise make GC harder.
777             * <p>
778             * The cache-clearing code is synchronized so the caller may be briefly
779             * blocked and there may be a small risk of deadlocks if clearCache()
780             * does something foolish.
781             */
782            public final void update(final Observable o, final Object arg)
783                {
784                final AbstractFilterBean bean = beanWR.get();
785                if(bean == null)
786                    {
787                    // Outer class has been GCed,
788                    // so we don't want to be an Observer any more!
789                    o.deleteObserver(this);
790                    return;
791                    }
792    
793                // We take a lock only to perform the clearCache()
794                // at a slight risk of a race with a resulting
795                // extra or missed cache clear.
796    
797                // If the argument indicates that we are in fact still up to date
798                // then return immediately.
799                if((arg instanceof Integer) &&
800                    (((Integer) arg).intValue() == bean.getExhibitDataHash()))
801                    { return; }
802    
803                o.deleteObserver(this); // No more updates until the next select().
804                // Avoid any chance of deadlock by doing the invalidation asynchronously.
805                lifetimeManager.schedule(new TimerTask(){
806                    @Override public void run() { bean.invalidate(); } // FIXME: small possibility of race with other invalidation.
807                    }, 0); // Schedule ASAP.
808                }
809            }
810    
811    
812        /**Collection of all references to internal cache objects requested to be retained; null when none present.
813         * This is used to prevent otherwise-soft cache references being cleared too soon.
814         * <p>
815         * Intended primarily for memory-sensitive caches
816         * though will do no harm will occur if references to internal cached state are always posted here.
817         * <p>
818         * Erased/dropped entirely when the cache is invalidated.
819         * <p>
820         * Cleared or switched to being themselves held via a soft reference
821         * whenever the initial lifetime is over,
822         * but not until _select() and its descendants have finished their computation.
823         * May then be switched back to being a strong reference for a while
824         * on each read access of not already expired, to provide another memory-insensitive period.
825         * <p>
826         * Created in _select() when a new value is computed.
827         * Each derived class should post its recomputed cached values (under the instance lock)
828         * after (re)computing them, to prevent them expiring too soon.
829         * <p>
830         * Accessed under the instance lock
831         * except marked volatile so that it may safely be cleared without a lock by _clearRetainedRefs().
832         */
833        private transient volatile Object _retainedCacheRefs;
834    
835    
836        /**Fixed dead SoftReference to use to clear _retainedCacheRefs without allocating.
837         * May be null during class initialisation.
838         */
839        private static final SoftReference<Collection<Object>> NO_REFS = new SoftReference<Collection<Object>>(null);
840    
841        /**Clears retained references without taking any locks.
842         * Suitable for use in an emergency, eg when memory is very low.
843         */
844        private void _clearRetainedRefs()
845            {
846            SoftReference<Collection<Object>> nr = NO_REFS;
847            if(null == nr) { nr = new SoftReference<Collection<Object>>(null); }
848            _retainedCacheRefs = nr;
849            }
850    
851        /**Add to the retained refs an cached internal object that must be maintained to avoid the cache expiring.
852         * This should only be called by descendants/callers of _select()
853         * and under the same instance lock.
854         * <p>
855         * Automatically creates/replaces the timer task to clear this.
856         * <p>
857         * We go through the motions even if not currently a memory-sensitive cache
858         * since that status may be changed at any time.
859         * <p>
860         * @param newRef  reference to retain, or null to revive existing references if possible
861         */
862        protected synchronized void _retainCacheRef(final Object newRef)
863            {
864            final long started = _select_startTime;
865            if(started == 0) { throw new IllegalStateException("Called from wrong place?"); }
866    
867            final Object rrRaw = _retainedCacheRefs;
868            final boolean isSoftRef = (rrRaw instanceof SoftReference);
869            final Collection<Object> rr = (!isSoftRef) ? (Collection<Object>) rrRaw :
870                ((SoftReference<Collection<Object>>)rrRaw).get();
871            if(rr == null)
872                {
873                // Note that GC races may get some cached state cleared separately from the rest
874                // and thus trying to repost its references here with a cleared reference holder....
875                if((newRef != null)  && !isSoftRef)
876                    { throw new IllegalStateException("Called from the wrong place?"); }
877    if(IsDebug.isDebug) { System.out.println("INFO: "+toString()+": refs already gone"); }
878                return; // References expired harmlessly?
879                }
880    if(IsDebug.isDebug && memorySensitiveCache && (newRef == null) && isSoftRef) { System.out.println("INFO: "+toString()+": revived references: "+rr.size()); }
881            // In case currently held via a soft reference, we force it back to a strong reference now.
882            _retainedCacheRefs = rr; // Force back strong ref.
883    
884            // Add any new reference provided.
885            if(newRef != null) { rr.add(newRef); }
886    
887            // Purge any existing task...
888            final TimerTask ttPurgeRetainedRefs = _ttPurgeRetainedRefs;
889            if(ttPurgeRetainedRefs != null)
890                {
891                _ttPurgeRetainedRefs = null;
892                ttPurgeRetainedRefs.cancel();
893                }
894            // If this is a new reference being posted
895            // then continue to compute/extend our notion of how long the computation is taking
896            // else base the computation on the previous run, if any.
897            // If (temporarily) conserving energy then attempt to avoid premature timer wakeup and timer firing.
898            final int minRetentionMs = GenUtils.mustConservePower() ? 1000 : 100;
899            final long computeTime;
900            if(newRef != null)
901                {
902                computeTime = System.currentTimeMillis() - started;
903                _select_runTime = computeTime; // Save the computed run time.
904                }
905            else
906                {
907                computeTime = _select_runTime;
908                }
909            // Create a new task delayed enough to allow a minimum life for this state
910            // that also helps ensure that a maximum fraction of wall-clock time is spent (re)building it.
911            // Will only weaken/clear the refs if the retained set has not grown since this this task ran,
912            // ensuring that one or more premature expiring tasks from inner parts of the select()
913            // piled up waiting to grab the lock and get in are simply ignored.
914            // Only get the longer lifetime if there's lots of spare memory.
915            // Maximum strong-retention life time is capped to limit task queue lifetime, etc.
916            long minimumRetentionMs = Math.min(MAX_STRONG_RETENTION_MS,
917                Math.max(minRetentionMs, computeTime *
918                    (!MemoryTools.lotsFree() ? BUILD_TIME_LIVE_RATIO_MEM_STRESS : BUILD_TIME_LIVE_RATIO)));
919            // Note that if there's an expiryTime then we cap the retention time to that.
920            if(expiryInterval > 0) { minimumRetentionMs = Math.min(minimumRetentionMs, expiryInterval+1); }
921            final int sizeWhenScheduled = rr.size();
922            lifetimeManager.schedule(_ttPurgeRetainedRefs = (new WeakenerTask(AbstractFilterBean.this, sizeWhenScheduled)), minimumRetentionMs);
923    if(IsDebug.isDebug && memorySensitiveCache && (minimumRetentionMs > 30000)) { System.out.println("INFO: "+toString()+": compute time ms="+computeTime+", minimum retention ms="+minimumRetentionMs+", retained ref count="+sizeWhenScheduled+", lifetime extension: "+(newRef==null)); }
924            }
925    
926        /**A task for clearing the _retainedCacheRefs; ephemeral and usually null.
927         * Cleared when computing a new value in _select() and descendants,
928         * also when the cache is invalidated,
929         * and when this task itself completes,
930         * and killed and recreated as each new item is added to _retainedCacheRefs
931         * to reflect the time taken to compute the values so far on this run.
932         * <p>
933         * Helps retain the cache for a minimum lifetime.
934         */
935        private transient TimerTask _ttPurgeRetainedRefs;
936    
937        /**Minimum multiple of build time for which we attempt to keep a memory-sensitive cache live; strictly positive.
938         * Increasing this value reduces the fraction of wall-clock time
939         * that we may have to spend rebuilding,
940         * but increases the risk of causing memory starvation elsewhere.
941         * <p>
942         * TODO: may replace with dynamic calculation of ratio large enough to keep amortised build time to <~100ms per _select().
943         * <p>
944         * A reasonable value is probably in the range 5--1000;
945         * a power of two may prove marginally more efficient.
946         */
947        protected static final int BUILD_TIME_LIVE_RATIO = 256;
948    
949        /**Minimum multiple of build time for which we attempt to keep a memory-sensitive cache live when memory may be short; strictly positive.
950         * Should be (much) smaller than BUILD_TIME_LIVE_RATIO.
951         * <p>
952         * A reasonable value is probably in the range 2--32;
953         * a power of two may prove marginally more efficient.
954         */
955        protected static final int BUILD_TIME_LIVE_RATIO_MEM_STRESS = 16;
956    
957        /**Maximum strong-ref retention time in ms; strictly positive.
958         * This helps bound the length of the task queues, etc.
959         * <p>
960         * Should be a very long time in computing terms, eg minutes through to hours.
961         */
962        protected static final int MAX_STRONG_RETENTION_MS = 3636001 + Rnd.fastRnd.nextInt(1800*1000);
963    
964        /**Timer used to help manage the cache lifetimes for memory-sensitive caches.
965         * Is a daemon to avoid keeping the JVM alive.
966         * <p>
967         * Should never be cancelled (will be GCed in due course if this class is).
968         * <p>
969         * It is very important that any tasks not due to run immediately
970         * should only hold onto AFB references weakly
971         * (and thus must be static classed for example)
972         * to avoid retaining garbage and compounding the problem that is intended to solve!
973         */
974        private static final Timer lifetimeManager = new Timer("AbstractFilterBean lifetimeManager", true);
975        }