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.svrCore.vars;
030    
031    import java.io.IOException;
032    import java.io.InvalidObjectException;
033    import java.io.ObjectInputStream;
034    import java.io.ObjectInputStream.GetField;
035    import java.io.ObjectInputValidation;
036    import java.io.ObjectOutputStream;
037    import java.io.Serializable;
038    import java.util.ArrayList;
039    import java.util.Arrays;
040    import java.util.Collections;
041    import java.util.Comparator;
042    import java.util.List;
043    import java.util.Map;
044    
045    import org.hd.d.pg2k.svrCore.GenUtils;
046    import org.hd.d.pg2k.svrCore.MemoryTools;
047    import org.hd.d.pg2k.svrCore.Name;
048    import org.hd.d.pg2k.svrCore.TextUtils;
049    
050    // FIXME: recheck that validateObject() is comprehensive.
051    
052    // TODO: ensure that we maximise compression here and in EVVB when using a compact form.
053    
054    /**Immutable value of a local or global system event variable.
055     * This contains the type/definition plus a total event count
056     * and a Map of event values.
057     * <p>
058     * To retrieve the very latest value that was set a SimpleVariableValue
059     * should be retrieved instead.
060     * <p>
061     * This is serialisable to enable it to be sent over network connections,
062     * persisted, etc.
063     * <p>
064     * Note that the interval number is strictly positive,
065     * the interval number multiplied by the period is the starting time of the interval in ms,
066     * and the starting time is always representable as a positive long.
067     * <p>
068     * May hold state in a compacted form internally to reduce memory footprint.
069     * In particular, String values may be held as more compact CharSequence forms,
070     * though are returned, matched and serialised as String.
071     */
072    public final class EventVariableValue implements Serializable,
073                                                     ObjectInputValidation
074        {
075        /**Construct a single variable value.
076         * This type-checks the value, vetoing construction if invalid.
077         * <p>
078         * May attempt to compact and/or intern() key values to minimise memory consumption
079         * at the cost of some performance at construction/deserialisation/access.
080         *
081         * @param isAuthoritative  if true, this is authoritative data
082         *     from the master data store
083         * @param def  the variable definition; never null and must be an event
084         * @param period  the event period for this sample; never null
085         * @param intervalNumber  the period number; strictly positive
086         * @param totalEventCount  the total event count; non-negative
087         * @param values  event values in decreasing order of count;
088         *     null == empty, not containing duplicates, same length as counts[] argument
089         * @param counts  event counts for each item in values[],
090         *     in non-increasing order, with a sum no more than total-event count;
091         *     null == empty, not containing negative values, same length as values[]
092         *
093         * @throws IllegalArgumentException  if the arguments are invalid
094         */
095        public EventVariableValue(final boolean isAuthoritative,
096                                  final SimpleVariableDefinition def,
097                                  final EventPeriod period,
098                                  final long intervalNumber,
099                                  final int totalEventCount,
100                                  final Object values[],
101                                  final int counts[])
102            throws IllegalArgumentException
103            {
104            // We will try to intern()/compress suitable values
105            // to save space in memory and when serialised.
106            this(isAuthoritative, def, period, intervalNumber, totalEventCount, values, counts, true);
107            }
108    
109        /**Construct a single variable value.
110         * This type-checks the value, vetoing construction if invalid.
111         * <p>
112         * May attempt to effectively intern() key values to minimise memory consumption
113         * at the cost of some performance at construction/deserialisation.
114         * <p>
115         * Package-visible so that only classes in this package (that we trust)
116         * can avoid having their values intern()ed.
117         *
118         * @param isAuthoritative  if true, this is authoritative data
119         *     from the master data store
120         * @param def  the variable definition; never null and must be an event
121         * @param period  the event period for this sample; never null
122         * @param intervalNumber  the period number; strictly positive
123         * @param totalEventCount  the total event count; non-negative
124         * @param valuesIn  event values in decreasing order of count;
125         *     null == empty, not containing duplicates, same length as counts[] argument
126         * @param countsIn  event counts for each item in values[],
127         *     in non-increasing order, with a sum no more than total-event count;
128         *     null == empty, not containing negative values, same length as values[]
129         * @param tryIntern  if false then we do not try hard to intern()/compress any of the values
130         *
131         * @throws IllegalArgumentException  if the arguments are invalid
132         */
133        EventVariableValue(final boolean isAuthoritative,
134                                  final SimpleVariableDefinition def,
135                                  final EventPeriod period,
136                                  final long intervalNumber,
137                                  final int totalEventCount,
138                                  final Object valuesIn[],
139                                  final int countsIn[],
140                                  final boolean tryIntern)
141            {
142            this.def = def;
143            this.period = period;
144            this.intervalNumber = intervalNumber;
145            this.totalEventCount = totalEventCount;
146            authoratitive = isAuthoritative;
147    
148            // Canonicalise empty values to save memory.
149            values = ((valuesIn == null) || (valuesIn.length == 0)) ? NO_VALUES : ((Object[]) valuesIn.clone());
150            counts = ((countsIn == null) || (countsIn.length == 0)) ? NO_COUNTS : ((int[]) countsIn.clone());
151    
152            // If being keen then sort and compact the values now.
153            if(!LAZY_SORT)
154                { _compactAndSort(def, tryIntern); }
155            else
156                {
157                // Even if generally lazy,
158                // if the input values need compacting then force a sort/compact immediately.
159                if(allowCompactStringValues(def))
160                    {
161                    for(final Object v : values)
162                        {
163                        if(v instanceof String)
164                            {
165                            _compactAndSort(def, tryIntern);
166    //if(IsDebug.isDebug) { System.out.println("INFO: EventVariableValue: forced to do eager/early compact"); }
167                            break;
168                            }
169                        }
170                    }
171                }
172    
173            try { validateObject(); }
174            catch(final InvalidObjectException e)
175                { throw new IllegalArgumentException(e.getMessage(), e); }
176            }
177    
178        /**Creates _valuesSortedRank[] and compacts values[] as a side-effect if needed.
179         */
180        private void _compactAndSort(final SimpleVariableDefinition def,
181                final boolean tryIntern)
182            {
183            // Compact/intern() all the values that it seems sensible to.
184            final int vLen = values.length;
185            if(vLen != counts.length) // One early bit of sanity checking...
186                { throw new IllegalArgumentException("counts and values array must be the same size"); }
187            final boolean tryStringCompression = allowCompactStringValues(def);
188    
189            // Create the sorted version of the values (and matching ranks),
190            // handling compactable state (and indeed doing the compacting as a side-effect).
191            if(tryStringCompression)
192                {
193                CharSequence vSorted[] = null;
194                // Create a (case-sensitive) sorted version of values[]
195                // to maximise the chance of prefix sharing.
196                // Note that we quietly accept Name in place of String if using a compact internal form.
197                vSorted = new CharSequence[vLen];
198                for(int i = vLen; --i >= 0; ) { vSorted[i] = (CharSequence) values[i]; }
199                Arrays.sort(vSorted, SV_COMPARATOR);
200                for(int i = 0; i < vLen; ++i)
201                    {
202                    // We may wish to avoid attempting possibly-futile and expensive recompression.
203                    if(AVOID_RECOMPRESSING && (vSorted[i] instanceof Name))
204                        { continue; }
205                    // Can't share a prefix if no previous item,
206                    // or previous item not a Name,
207                    // or no shared prefix/suffix at all.
208                    final boolean noShare = (i == 0) ||
209                                            (vSorted[i-1].getClass() != Name.class) ||
210                                            (vSorted[i-1].length() == 0) ||
211                                            (vSorted[i].length() == 0) ||
212                        ((vSorted[i-1].charAt(0) != vSorted[i].charAt(0)) &&
213                            (vSorted[i-1].charAt(vSorted[i-1].length()-1) != vSorted[i].charAt(vSorted[i].length()-1)));
214                    // Attempt to replace this value in situ with a 'better' representation.
215                    vSorted[i] = Name.createOrStringFallback(vSorted[i], noShare ? null : (Name) vSorted[i-1]);
216    //System.out.println("vSorted["+i+"] class "+vSorted[i].getClass().getName());
217                    }
218    //            valuesSorted = vSorted;
219                // Now fill in the ranks.
220                final short vsr[] = new short[vLen];
221                for(int rank = vLen; --rank >= 0; )
222                    {
223                    final int sortedPos = Arrays.binarySearch((CharSequence[]) vSorted, (CharSequence)values[rank], SV_COMPARATOR);
224                    vsr[sortedPos] = (short) rank;
225                    assert(TextUtils.contentEquals((CharSequence) values[rank], vSorted[sortedPos]));
226                    values[rank] = vSorted[sortedPos];
227                    }
228                valuesSortedRank = vsr;
229                }
230            else // Generic case (natural sort order of uniformly-typed values).
231                {
232                final Object vSorted[] = values.clone(); // Assume all values are Comparable.
233                Arrays.sort(vSorted);
234    //            valuesSorted = vSorted;
235                // Now fill in the ranks.
236                final short vsr[] = new short[vLen];
237                for(int rank = vLen; --rank >= 0; )
238                    {
239                    final int sortedPos = Arrays.binarySearch(vSorted, vSorted[rank]);
240                    vsr[sortedPos] = (short) rank;
241                    assert(values[rank].equals(vSorted[sortedPos]));
242                    // Intern the value if appropriate.
243                    // Replace original with intern()ed value to reduce footprint.
244                    if(shouldIntern(def))
245                        { values[rank] = vSorted[sortedPos] = MemoryTools.intern(vSorted[sortedPos]); }
246                    }
247                valuesSortedRank = vsr;
248                }
249    
250            assert((valuesSortedRank != null) && (valuesSortedRank.length == values.length));
251            }
252    
253        /**Case-sensitive comparator for nominally String-type values, even mixed compact and raw String items; not null. */
254        static final Comparator<CharSequence> SV_COMPARATOR = TextUtils.CASE_SENSITIVE_ORDER;
255    
256        /**If true then Name is a valid value type (in lieu of String).
257         * This may also be used by EventVariableValueBuffer
258         * to optimise its internal state and interactions with this class;
259         * thus package-visible.
260         */
261        static boolean allowCompactStringValues(final SimpleVariableDefinition def)
262            {
263            return(COMPRESS_INTERNAL_STATE && (def.getType() == SimpleVariableDefinition.TYPE_STRING));
264            }
265    
266        /**Private shared (immutable) value meaning no values, to save memory. */
267        private static final Object[] NO_VALUES = new Object[0];
268    
269        /**Private shared (immutable) value meaning no counts, to save memory. */
270        private static final int[] NO_COUNTS = new int[0];
271    
272    
273        /**Returns true if we should probably automatically intern() the value.
274         * We will do this if the value is likely to be bulky (eg is a String),
275         * or may be long-lived (eg persistent) or in multiple copies.
276         */
277        static boolean shouldIntern(final SimpleVariableDefinition def)
278            {
279            return((def.getType() == SimpleVariableDefinition.TYPE_STRING) ||
280                   def.isPersistent());
281            }
282    
283        /**Return a (new) authoritative value with the same content as the current value; never null.
284         * This instance is unaltered,
285         * and it and the value returned remain immutable,
286         * though they may or may not share state.
287         * <p>
288         * (If this instance is already authoritative,
289         * then "this" is returned to avoid wasting memory.)
290         *
291         * @return instance with "authoritative" flag set
292         */
293        public EventVariableValue makeAuthoritative()
294            {
295            if(authoratitive) { return(this); }
296            return(new EventVariableValue(true, def, period, intervalNumber, totalEventCount, values, counts));
297            }
298    
299    
300        /**The variable definition; never null.
301         * @serial
302         */
303        private /* final */ SimpleVariableDefinition def;
304    
305        /**Get the variable definition; never null. */
306        public SimpleVariableDefinition getDef() { return(def); }
307    
308        /**The event period; never null.
309         * @serial
310         */
311        private /* final */ EventPeriod period;
312    
313        /**Get the event period; never null. */
314        public EventPeriod getPeriod() { return(period); }
315    
316        /**The interval number; strictly positive. */
317        private /* final */ long intervalNumber;
318    
319        /**Get the interval number; strictly positive. */
320        public long getIntervalNumber() { return(intervalNumber); }
321    
322        /**If true, this variable has come from the authoritative end-point.
323         * Due to an old typo now fossilised in old data,
324         * the spelling of this should remain "authoratitive" even though wrong!
325         * Thank goodness for encapsulation!
326         *
327         * @serial
328         */
329        private /* final */ boolean authoratitive;
330    
331        /**Returns true if this variable has come from the authoritative end-point. */
332        public boolean isAuthoritative() { return(authoratitive); }
333    
334        /**Total event count; non-negative. */
335        private /* final */ int totalEventCount;
336    
337        /**Get the total event count; non-negative. */
338        public int getTotalEventCount() { return(totalEventCount); }
339    
340        /**Counts of the different (non-null) values in non-increasing-count order (ie best first); never null.
341         * All counts are non-negative.
342         * <p>
343         * Same length as values[].
344         *
345         * @serial
346         */
347        private /* final */ int counts[];
348    
349        /**Values of the different (non-null) events in non-increasing-count order (ie best first); never null.
350         * All values are non-null and valid values as determined by the def
351         * though note that some values may be substituted with compact alternatives.
352         * <p>
353         * Same length as counts[].
354         * <p>
355         * The general case is that a value's rank is its position in this array.
356         *
357         * @serial
358         */
359        private /* final */ Object values[];
360    
361        /**Rank of each item in valuesSorted[]; CURRENTLY never null after construction.
362         * All access should be via getValuesSortedRank()
363         * (with the value set by _compactAndSort())
364         * so that this can be lazily created in future.
365         * <p>
366         * Marked volatile for thread-safe lock-free access.
367         */
368        private transient volatile short valuesSortedRank[];
369    
370        /**Get rank of each values[] item in sorted order; never null.
371         * Will do sorting and compaction if not already done.
372         */
373        private short[] getValuesSortedRank()
374            {
375            short[] result = valuesSortedRank;
376            if(!LAZY_SORT)
377                {
378                assert(null != result); // Must already have been done if not lazy.
379                return(result);
380                }
381    
382            // Already computed.
383            if(null != result) { return(result); }
384    
385            // Do any actual update under the instance lock to avoid duplicated effort.
386            synchronized(this)
387                {
388                // If still null with the lock held then recompute now.
389                while(null == (result = valuesSortedRank))
390                    { _compactAndSort(def, true); }
391                }
392    
393            assert(null != result);
394            return(result);
395            }
396    
397        /**Generate human-readable summary of important state. */
398        @Override
399        public String toString()
400            {
401            return(def.getName() + ":auth="+authoratitive+ ":period="+period+ ":intNum="+intervalNumber+ ":count="+totalEventCount);
402            }
403    
404    
405        /**Immutable info collected about one particular value of an event. */
406        public static final class ValueInfo implements Serializable, ObjectInputValidation
407            {
408            /**Construct an instance.
409             * Only needs to be constructed locally, so this need not be public.
410             */
411            ValueInfo(final int pos, final int cnt)
412                {
413                rank = pos;
414                count = cnt;
415                try { validateObject(); } catch(final InvalidObjectException e) { throw new IllegalArgumentException(e.getMessage()); }
416                }
417            /**Rank: 0 means top, ie highest count; non-negative. */
418            public final int rank;
419            /**Count is count of this value; strictly positive. */
420            public final int count;
421            @Override public boolean equals(final Object o)
422                {
423                if(!(o instanceof ValueInfo)) { return(false); }
424                final ValueInfo other = (ValueInfo) o;
425                return((rank == other.rank) && (count == other.count));
426                }
427            @Override public int hashCode() { return(rank ^ (count << 16)); }
428            /**Deserialise: use constructor for validation, defensive copying, etc. */
429            protected Object readResolve() // throws ObjectStreamException
430                { return(new ValueInfo(rank, count)); }
431            /**Check that the object state is consistent and legal. */
432            public void validateObject() throws InvalidObjectException
433                { if((rank < 0) || (count <= 0)) { throw new InvalidObjectException("bad object"); } }
434            /**Unique Serialisation class ID generated by http://random&#46;hd&#46;org/. */
435            private static final long serialVersionUID = 887928932975973213L;
436            };
437    
438        /**Return the index in a sorted version of values[] of the given value/key; -negative if not present.
439         * This works indirectly via the valuesSortedRank() array.
440         */
441        @SuppressWarnings("unchecked")
442        private int getIndexInSortedValues(final Object key)
443            {
444            final short[] vsr = getValuesSortedRank();
445            final int size = getTotalDistinctValues();
446            final List<Object> distinctValuesSorted = (new GenUtils.AbstractRandomAccessList<Object>() {
447                @Override public Object get(final int index) { return(getValueByRank(vsr[index])); }
448                @Override public int size() { return(size); }
449                });
450            return(allowCompactStringValues(def) ?
451                Collections.binarySearch(distinctValuesSorted, key, (Comparator) SV_COMPARATOR) :
452                Collections.binarySearch((List) distinctValuesSorted, key));
453            }
454    
455    
456    //  /**Make a ValueInfo record for the value of specified rank; never null. */
457    //  private ValueInfo makeValueInfo(final int rank)
458    //      { return new ValueInfo(rank, counts[rank]); }
459    
460    //    /**Get immutable Map of info on all events counted; never null.
461    //     * Map from the event value to the rank and count for that event.
462    //     * <p>
463    //     * May be expensive and may consume lots of space,
464    //     * but is softly cached to minimise harm from either.
465    //     */
466    //    public Map<Object, ValueInfo> getInfo()
467    //        {
468    //        // Optimisation for when actually empty...
469    //        if(0 == getTotalDistinctValues()) { return(Collections.emptyMap()); }
470    //
471    //        // Make lightweight Map wrapper.
472    //        //
473    //        // Should be almost instant to construct and with a tiny footprint,
474    //        // O(1) for both.
475    //        //
476    //        // Converts compacted values on demand back to primary type when necessary.
477    //        // Backed by our internal data with no copying.
478    //        // The iteration order is over the sorted values (ie like a SortedMap)
479    //        // which means we can do lookups such as get() in O(log(n)) time.
480    //        //
481    //        // Note that we can avoid converting any compact value form to primary form
482    //        // when doing the lookup for get() and containsKey().
483    //        // (This also means that we could silently accept a compact form as the key.)
484    //        //
485    //        // Only methods thought important/common in actual use have been optimised,
486    //        // especially get().
487    //        final Map<Object, ValueInfo> lwm = new AbstractMap<Object, ValueInfo>(){
488    //
489    //            @Override public boolean containsKey(final Object key) { return(getIndexInSortedValues(key) >= 0); }
490    //
491    //            @Override public ValueInfo get(final Object key)
492    //                {
493    //                final int si = getIndexInSortedValues(key);
494    //                if(si >= 0) { return(makeValueInfo(getValuesSortedRank()[si])); }
495    //                return(null);
496    //                }
497    //
498    //            @Override public Set<Object> keySet()
499    //                { return(new HashSet<Object>(getDistinctValuesInRankOrder())); }
500    //
501    //            @Override public Set<Map.Entry<Object, ValueInfo>> entrySet()
502    //                {
503    //                return(new AbstractSet<Map.Entry<Object, ValueInfo>>(){
504    //                    @Override public Iterator<Map.Entry<Object, ValueInfo>> iterator()
505    //                        {
506    //                        return(new Iterator<Map.Entry<Object, ValueInfo>>(){
507    //                            private int currentPos = 0;
508    //
509    //                            public Map.Entry<Object, ValueInfo> next()
510    //                                {
511    //                                final int index = currentPos++;
512    //                                return(new Map.Entry<Object, ValueInfo>(){
513    //                                    public Object getKey()
514    //                                        { return(getValueByRank(getValuesSortedRank()[index])); }
515    //                                    public ValueInfo getValue()
516    //                                        {
517    //                                        final short rank = getValuesSortedRank()[index];
518    //                                        return(makeValueInfo(rank));
519    //                                        }
520    //                                    public ValueInfo setValue(final ValueInfo value)
521    //                                        { throw new UnsupportedOperationException(); }
522    //
523    //                                    @SuppressWarnings("unchecked")
524    //                                    @Override public boolean equals(final Object o)
525    //                                        {
526    //                                        if (!(o instanceof Map.Entry)) { return(false); }
527    //                                        final Map.Entry other = (Map.Entry)o;
528    //                                        final Object k1 = getKey();
529    //                                        final Object k2 = other.getKey();
530    //                                        if((k1 == k2) || ((k1 != null) && k1.equals(k2)))
531    //                                            {
532    //                                            final Object v1 = getValue();
533    //                                            final Object v2 = other.getValue();
534    //                                            if((v1 == v2) || ((v1 != null) && v1.equals(v2))) { return(true); }
535    //                                            }
536    //                                        return(false);
537    //                                        }
538    //                                    /**Just use the value hash and not the key at all so as to avoid an expensive possible key value conversion and hash. */
539    //                                    @Override public final int hashCode()
540    //                                        { return(getValue().hashCode()); }
541    //                                    });
542    //                                }
543    //
544    //                            public boolean hasNext() { return(currentPos < getTotalDistinctValues()-1); }
545    //                            public void remove() { throw new UnsupportedOperationException(); }
546    //                            });
547    //                        }
548    //
549    //                    @Override public int size() { return(getTotalDistinctValues()); }
550    //                    });
551    //                }
552    //            };
553    //
554    //        return(lwm);
555    //        }
556    
557    
558        /**Get total number of distinct event values recorded; non-negative. */
559        public int getTotalDistinctValues()
560            { return(values.length); }
561    
562        /**Get an immutable RandomAccess List of the distinct values in rank order; never null but may be empty.
563         * This is very lightweight,
564         * and preferable to (say) getDistinctValuesSorted() if only the values are required.
565         */
566        public List<Object> getDistinctValuesInRankOrder()
567            {
568            // This converts values to primary form before being returned if need be.
569            return(new GenUtils.AbstractRandomAccessList<Object>() {
570                @Override public Object get(final int index) { return(getValueByRank(index)); }
571                @Override public int size() { return(getTotalDistinctValues()); }
572                });
573            }
574    
575        /**Get an immutable RandomAccess List of the distinct values in value-sorted order; never null but may be empty.
576         * This is fairly lightweight,
577         * and preferable to (say) getInfo().keySet() if only the values are required.
578         */
579        public List<Object> getDistinctValuesSorted()
580            {
581            // This converts values to primary form before being returned if need be.
582            return(new GenUtils.AbstractRandomAccessList<Object>() {
583                @Override public Object get(final int index) { return(getValueByRank(getValuesSortedRank()[index])); }
584                @Override public int size() { return(getTotalDistinctValues()); }
585                });
586            }
587    
588        /**Get count of this event value; never negative.
589         * A result of zero may indicate either that
590         * no events with the specified value were seen,
591         * or that they were seen but were later displaced due to capacity limits.
592         *
593         * @throws IllegalArgumentException  if the argument is null
594         *     or not a legal type/value
595         */
596        public int getCount(final Object value)
597            throws IllegalArgumentException
598            {
599            checkExternalLookupValueTypeAndNonNull(value);
600    
601            final int si = getIndexInSortedValues(value);
602            if(si < 0) { return(0); }
603            return(counts[getValuesSortedRank()[si]]);
604            }
605    
606        /**Checks that the supplied value is non-null and a legitimate value of the definition.
607         * Where internal value compression is in place
608         * then this may accept the compact form where appropriate.
609         */
610        private void checkExternalLookupValueTypeAndNonNull(final Object value)
611            throws IllegalArgumentException
612            {
613            if(value == null)
614                { throw new IllegalArgumentException("null not permitted"); }
615            // Note that we accept any CharSequence to lookup String values here...
616            if(!def.checkType(value) && !(allowCompactStringValues(def) && (value instanceof CharSequence)))
617                { throw new IllegalArgumentException("def="+def+", type of argument="+value.getClass().getName()); }
618            }
619    
620        /**Get rank/ranking of this event value; never negative.
621         * Zero means the highest count of all values seen,
622         * one means the next highest, etc.
623         * So low values are good.
624         * <p>
625         * If we have no count recorded for this value, ie no ranking,
626         * the routine returns Integer.MAX_VALUE.
627         *
628         * @throws IllegalArgumentException  if the argument is null
629         *     or not a legal type/value
630         */
631        public int getRank(final Object value)
632            throws IllegalArgumentException
633            {
634            checkExternalLookupValueTypeAndNonNull(value);
635    
636            final int si = getIndexInSortedValues(value);
637            if(si < 0) { return(Integer.MAX_VALUE); }
638            return(getValuesSortedRank()[si]);
639            }
640    
641        /**Convert value to primary form. */
642        private Object convertValueToPrimaryForm(final Object o)
643            {
644            if(allowCompactStringValues(def))
645                {
646                // Where internal String value may be compressed (eg as Name),
647                // convert it to standard external (String) form, intern()ed.
648                assert(o != null);
649                if(o.getClass() == String.class) { return(o); } // Return as-is.
650                return(MemoryTools.intern(o.toString())); // Convert back to String and avoid dups.
651                }
652            return(o);
653            }
654    
655        /**Get value by rank.
656         * @param  rank of value to select in range [0,getTotalDistinctValues()-1]
657         * @throws ArrayIndexOutOfBoundsException  if the rank is out of bounds
658         */
659        public Object getValueByRank(final int rank)
660            { return(convertValueToPrimaryForm(values[rank])); }
661    
662        /**Get count by rank; strictly positive.
663         * @throws ArrayIndexOutOfBoundsException  if the rank is out of bounds
664         */
665        public int getCountByRank(final int rank) { return(counts[rank]); }
666    
667    
668        /**Equality is based on the definition, period and interval number, but not the event values.
669         */
670        @Override
671        public boolean equals(final Object obj)
672            {
673            if(!(obj instanceof EventVariableValue)) { return(false); }
674            final EventVariableValue o = (EventVariableValue) obj;
675    
676            if(authoratitive != o.authoratitive) { return(false); }
677            if(!def.equals(o.def)) { return(false); }
678            if(!period.equals(period)) { return(false); }
679            if(intervalNumber != o.intervalNumber) { return(false); }
680    
681            // All relevant fields seem to match, so objects are equal.
682            return(true);
683            }
684    
685        /**The hash is built on a subset of the fields.
686         * This implementation uses the hash of the definition and the interval number and period values.
687         */
688        @Override
689        public int hashCode()
690            { return(def.hashCode() ^ ((int)intervalNumber) ^ period.getIntervalMs()); }
691    
692    
693        /**Customise how we save state when serialising.
694         */
695        private void writeObject(final ObjectOutputStream oos)
696            throws IOException
697            {
698            // Capture just the fields that we want to persist.
699            final ObjectOutputStream.PutField fields = oos.putFields();
700            fields.put("authoratitive", authoratitive); // NOTE: misspelling in historical serial data.
701            fields.put("counts", counts);
702            fields.put("def", def);
703            fields.put("intervalNumber", intervalNumber);
704            fields.put("period", period);
705            fields.put("totalEventCount", totalEventCount);
706            fields.put("values", values);
707            oos.writeFields();
708            }
709    
710        /**Deserialise.
711         * This explicitly recovers state for fields by name.
712         * <p>
713         * This does not attempt to build some of the transient state assembled by the constructor,
714         * nor do defensive copying or significant validation.
715         */
716        private void readObject(final ObjectInputStream ois)
717            throws IOException, ClassNotFoundException
718            {
719            // Read all fields explicitly, including any legacy ones.
720            final GetField fields = ois.readFields();
721    
722            // Get current fields.
723            // This means that they cannot be final...
724            authoratitive = fields.get("authoratitive", false); // NOTE: misspelling in historical serial data.
725            def = (SimpleVariableDefinition) fields.get("def", null);
726            intervalNumber = fields.get("intervalNumber", 0L);
727            period = (EventPeriod) fields.get("period", null);
728            totalEventCount = fields.get("totalEventCount", 0);
729    
730            counts = (int[]) fields.get("counts", null);
731            values = (Object[]) fields.get("values", null);
732    
733            // Handle legacy fields...
734            Map<Object, ValueInfo> oldInfo = null;
735            try { oldInfo = (Map<Object, ValueInfo>) fields.get("info", null); } // Legacy Map<Object,Value> info.
736            catch(final IllegalArgumentException e) { /* Field not present at all. */ }
737    
738    
739            // If we have the old format of object,
740            // ie without the new values/counts but with the internal info Map,
741            // then convert to the new format here.
742            if(values == null) // Assume that this implies counts == null and info != null.
743                {
744                if(!((counts == null) && (oldInfo != null)))
745                    { throw new InvalidObjectException("inconsistent format (info vs counts/values)"); }
746    
747                // Convert info data to sortable form...
748                final List<EventVariableValueBuffer.Count> cs = new ArrayList<EventVariableValueBuffer.Count>(oldInfo.size());
749                for(final Object key : oldInfo.keySet())
750                    {
751                    final EventVariableValueBuffer.Count count = new EventVariableValueBuffer.Count(key);
752                    count.count = oldInfo.get(key).count;
753                    cs.add(count);
754                    }
755                // Get counts ordered to get rankings...
756                Collections.sort(cs);
757                // Should be highest-count first...
758                assert((cs.size() < 2) || (cs.get(0).count >= cs.get(1).count)) : "array out of order";
759    
760                // Create list of values and counts...
761                final Object newValues[] = new Object[cs.size()];
762                final int newCounts[] = new int[cs.size()];
763                for(int i = cs.size(); --i >= 0; )
764                    {
765                    final EventVariableValueBuffer.Count count = cs.get(i);
766                    newValues[i] = count.value;
767                    newCounts[i] = count.count;
768                    }
769    
770                // Keep synthesised values.
771                values = newValues;
772                counts = newCounts;
773                }
774    
775            // Instance should now be valid except for 'sorted' structures built in the constructor
776            // unless we were fed dud data such as duplicate values.
777            }
778    
779        /**Deserialise: use constructor for validation, defensive copying, conversion from old formats, etc.
780         * Also allows us to intern() keys, etc, to conserve memory.
781         */
782        protected Object readResolve()
783            // throws ObjectStreamException
784            {
785            return(new EventVariableValue(authoratitive, def, period, intervalNumber, totalEventCount, values, counts));
786            }
787    
788        /**Check that the object state is consistent and legal. */
789        public void validateObject()
790            throws InvalidObjectException
791            {
792            if(def == null)
793                { throw new InvalidObjectException("bad object: def must not be null"); }
794            if(!def.isEvent())
795                { throw new InvalidObjectException("bad object: def not an event type"); }
796    
797            if(period == null)
798                { throw new InvalidObjectException("bad object: period must not be null"); }
799    
800            if(totalEventCount < 0)
801                { throw new InvalidObjectException("bad object: negative totalEventCount"); }
802    
803            if(intervalNumber < 0)
804                { throw new InvalidObjectException("bad object: negative intervalNumber"); }
805            if(Long.MAX_VALUE / period.getIntervalMs() < intervalNumber)
806                { throw new InvalidObjectException("bad object: intervalNumber too large"); }
807    
808    
809            // Check counts and values are valid.
810            if((counts == null) || (values == null))
811                { throw new InvalidObjectException("bad object: counts[] or values[] is null"); }
812            final int diffVals = values.length;
813            if(counts.length != diffVals)
814                { throw new InvalidObjectException("bad object: counts.length != values.length"); }
815            if((diffVals > totalEventCount) || (diffVals > def.getMaxDiffEventCount()))
816                { throw new InvalidObjectException("bad object: too many distinct event values"); }
817            // Check that counts are non-negative and non-increasing
818            // and that the sum is not too large.
819            int countsSum = 0;
820            for(int i = diffVals; --i >= 0; )
821                {
822                final int c = counts[i];
823                if(c < 0)
824                    { throw new InvalidObjectException("bad object: negative count"); }
825                countsSum += c;
826                if((i > 0) && (c > counts[i-1]))
827                    { throw new InvalidObjectException("bad object: counts increasing"); }
828                }
829            if(countsSum > totalEventCount)
830                { throw new InvalidObjectException("bad object: counts[] sum > totalEventCount"); }
831            // Check that values are non-null and type-check.
832            for(final Object o : values)
833                {
834                if(o == null)
835                    { throw new InvalidObjectException("bad object: values[] entry is null"); }
836                if(!def.checkType(o) && !(allowCompactStringValues(def) && (Name.class == o.getClass())))
837                    { throw new InvalidObjectException("bad object: values[] entry has invalid type"); }
838                }
839    
840            // TODO: verify whatever sorted state is present.
841            }
842    
843        /**If true then defer (expensive) sorting where possible.
844         * Even if we are compressing internal state,
845         * if all values appear to be in a compressed form already,
846         * then we can postpone sorting until the first direct use of the sorted state.
847         */
848        static boolean LAZY_SORT = true;
849    
850        /**If true then try to compress the internal in-memory state to reduce footprint.
851         * All APIs and the serialised form remain unchanged.
852         * <p>
853         * This flag may also be used by EventVariableValueBuffer
854         * to optimise its internal state and interactions with this class;
855         * thus package-visible.
856         */
857        static boolean COMPRESS_INTERNAL_STATE = true;
858    
859        /**If true then avoid attempting to re-compact already nominally compact data.
860         * This may lose a little potential compression, but save a great deal of time.
861         */
862        static boolean AVOID_RECOMPRESSING = true;
863    
864        /**Unique Serialisation class ID. */
865        private static final long serialVersionUID = 5094323684152972705L;
866        }