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.hd.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 }