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;
030    
031    import java.io.IOException;
032    import java.io.InvalidObjectException;
033    import java.io.ObjectInputStream;
034    import java.io.ObjectInputValidation;
035    import java.io.ObjectOutputStream;
036    import java.io.Serializable;
037    import java.util.ArrayList;
038    import java.util.Arrays;
039    import java.util.Collections;
040    import java.util.Comparator;
041    import java.util.Hashtable;
042    import java.util.List;
043    import java.util.Set;
044    import java.util.SortedMap;
045    import java.util.SortedSet;
046    import java.util.TreeMap;
047    import java.util.TreeSet;
048    
049    import org.hd.d.pg2k.svrCore.Name.ExhibitFull;
050    
051    /**Immutable set of names and ExhibitStaticAttr for all exhibits.
052     * This provides a set of all exhibits, mapped from the full name
053     * to the static/immutable attributes for an exhibit (ie the parts
054     * that will not change unless the exhibit itself is changed).
055     * <p>
056     * Also provides a mapping from the final file component (short name) of an exhibit to its
057     * full name within the collection.  All exhibits should be unique in this last component;
058     * if not then only the alphabetically first full-name value will be kept.
059     * <p>
060     * Also contains the time of last change to exhibit set when this
061     * snapshot was taken; this is verified simply to be non-negative
062     * and may for example be some combination of timestamp and hash over
063     * the exhibit content to best catch changes in the exhibit-set
064     * composition or it may be a pure hash.  If a combination then
065     * usually the most significant bits would be those of the newest
066     * exhibit and the least significant would be the hash.  If the
067     * exhibit set changes then this value should change, however
068     * the timestamp may not monotonically increase as new exhibits
069     * are added (or existing ones are altered).
070     * <p>
071     * Designed to be efficient `on-the-wire'; for serialisation
072     * we send a count and the ExhibitStaticAttr objects in sorted
073     * order by name instead of the map, which should be much less bulky than
074     * our internal representation (which is designed for lookup speed),
075     * and contains all the required information.  On deserialising
076     * we simply reconstruct the map.
077     * We still let default serialisation deal with any other fields
078     * (the timestamp).
079     * <p>
080     * (It looks like HashMap's serialisation must be quite good since the
081     * saving in uncompressed size is only about 6%.)
082     * <p>
083     * The hashCode() is based on a hash of the timestamp,
084     * and equality is based on the sets of exhibits
085     * and global and per-exhibit attributes being equal.
086     * <p>
087     * TODO: lazily compute dups and short-to-full lookup, etc, when conserving resources.
088     */
089    public final class AllExhibitImmutableData implements Serializable,
090                                                          ObjectInputValidation
091        {
092        /**Construct an empty, zero-timestamp snapshot.
093         */
094        public AllExhibitImmutableData()
095            { this(Collections.<ExhibitStaticAttr>emptySet(), 0); }
096    
097        /**Sort order used for (full and short) exhibit names and static attrs containing them; non-null. */
098        public static final Comparator<CharSequence> SORT_ORDER = TextUtils.CASE_SENSITIVE_ORDER;
099    
100        /**In-order set of static-attribute instances; never null nor changed after construction/deserialisation. */
101        private transient /* final */ ExhibitStaticAttr orderedEsa[];
102    
103        /**Construct a new snapshot.
104         * The input set is checked for uniqueness by full name,
105         * and the data may be reconstructed internally for efficiency.
106         * <p>
107         * The timestamp must be zero if the map is empty,
108         * else it must be strictly positive.
109         *
110         * @throws IllegalArgumentException  if the timestamp or input data
111         *     are found to be malformed.
112         */
113        public AllExhibitImmutableData(final Set<ExhibitStaticAttr> staticAttrs,
114                                       final long lastExhibitChangeTimestamp)
115            throws IllegalArgumentException
116            {
117            if((staticAttrs == null) ||
118               (lastExhibitChangeTimestamp < 0))
119                { throw new IllegalArgumentException(); }
120    
121            // We construct a defensive sorted set copy of the inputs,
122            // and check that there were no duplicates (rejecting the input if so).
123            final SortedSet<ExhibitStaticAttr> sorted = new TreeSet<ExhibitStaticAttr>(staticAttrs);
124            final int nExhibits = sorted.size();
125            if(nExhibits != staticAttrs.size()) { throw new IllegalArgumentException("input contained duplicates"); }
126            final ExhibitStaticAttr[] sortedArr = sorted.toArray(new ExhibitStaticAttr[nExhibits]);
127            assert(sortedArr != null);
128            assert(sortedArr.length == nExhibits);
129    
130            // Now attempt to compact each value by sharing a prefix with the preceding one.
131            for(int i = 1; i < nExhibits; ++i)
132                {
133                final CharSequence prevName = sortedArr[i-1].getCharSequence();
134                if(!(prevName instanceof Name.ExhibitFull)) { continue; }
135                final ExhibitStaticAttr extant = sortedArr[i];
136                sortedArr[i] = new ExhibitStaticAttr(Name.ExhibitFull.create(extant.getCharSequence(), (Name.ExhibitFull)prevName), extant.length, extant.timestamp);
137                }
138    
139            orderedEsa = sortedArr;
140            length = orderedEsa.length;
141            timestamp = lastExhibitChangeTimestamp;
142    
143            // Build our short-to-full name map...
144            _buildShortToFull();
145    
146            optionalEagerWarmup(); // Do eager warm-up if system not too stressed.
147    
148            // Verify what we've been given.
149            try { validateObject(); }
150            catch(final InvalidObjectException e)
151                { throw new IllegalArgumentException(e.getMessage()); }
152            }
153    
154        /**Can be called from constructor or at end of deserialisation.
155         * Must not pass the possibly-incomplete object reference out.
156         */
157        private void optionalEagerWarmup()
158            {
159            // If we're not temporarily short of resources
160            // then precompute some useful results.
161            // With luck, when deserialising for example,
162            // this will happen while more data is streaming up the wire to us
163            // and thus not actually delay other work but instead give better I/O overlap.
164            if(!GenUtils.mustConservePower() && !MemoryTools.isMemoryStressed())
165                {
166                getAllExhibitNamesSorted();
167                _computePKeys();
168                }
169            }
170    
171        /**Timestamp of last exhibit update when this snapshot was taken.
172         * Strictly positive unless map is empty, in which case it will be zero.
173         * <p>
174         * This information is accessible without holding any locks.
175         */
176        public final long timestamp;
177    
178        /**Exhibit count, with lock-free access; non-negative.
179         * The information in this is redundant
180         * but is small and useful for speed and an integrity check, etc.
181         * <p>
182         * This emulates the length field of an array
183         * while size() emulates the behaviour of collections...
184         * <p>
185         * This information is accessible without holding any locks.
186         */
187        public final int length;
188    
189        /**Sorted logically-immutable array of full names with duplicate short names found by _buildShortToFull; never null. */
190        private transient /* final */ Name.ExhibitFull[] shortNameDups;
191    
192        /**Sorted logically-immutable set of unique short names; never null.
193         * Can implicitly be used to look up full names via getFullName().
194         * <p>
195         * Can be used to do look-up with other CharSequence implementations such as String.
196         */
197        private transient /* final */ Name.ExhibitShort[] uniqueShortNames;
198    
199        /**Sorted immutable Map of short-name persistable keys to full names; never null.
200         * Can implicitly be used to look up full names via getFullName().
201         * <p>
202         * Can be used to do look-up with other CharSequence implementations such as String,
203         * ie the Comparator works with generic CharSequence values.
204         * <p>
205         * Volatile for lock-free read access.
206         * <p>
207         * Write access is under the instance lock and this map is created on first use.
208         */
209        private transient volatile SortedMap<CharSequence,Name.ExhibitFull> pKeysToFull;
210    
211        /**Builds shortToFull and shortNameDups from orderedEsa.
212         * We try to do this in a memory-efficient way.
213         */
214        private void _buildShortToFull()
215            {
216            // Working set of (unique) short names.
217            // Implicitly a map back to the full names via the getFullName() method.
218            // Uses the case-sensitive CharSequence comparator to sort.
219            // Using CharSequence to allow lookup by other concrete types such as String.
220            final SortedSet<Name.ExhibitShort> uSN = new TreeSet<Name.ExhibitShort>(SORT_ORDER);
221    
222            // Full names that contain duplicate short (file) components.
223            // Will not contain duplicates since all full names in orderedEsa should be unique.
224            final List<Name.ExhibitFull> dups = new ArrayList<Name.ExhibitFull>();
225    
226            for(final ExhibitStaticAttr esa : orderedEsa)
227                {
228                final Name.ExhibitFull fullName = esa.getExhibitFullName();
229                final Name.ExhibitShort shortName = fullName.getShortName();
230    
231                if(!uSN.add(shortName))
232                    { dups.add(fullName); }
233               }
234    
235            // Store (sorted) list of duplicates.
236            final Name.ExhibitFull sND[] = dups.toArray(new Name.ExhibitFull[dups.size()]);
237            Arrays.sort(sND);
238            shortNameDups = sND;
239    
240            // Atomically store mapping when everything else is complete.
241            uniqueShortNames = uSN.toArray(new Name.ExhibitShort[uSN.size()]);
242            }
243    
244        /**Gets (sorted) array of full names that have duplicate short names; never null nor containing nulls but may be zero-length.
245         * This returns a copy that can be safely tampered with by the caller.
246         */
247        public Name.ExhibitFull[] getFullNamesWithDuplicateShortNames()
248            { return(shortNameDups.clone()); }
249    
250        /**Gets (sorted) array of all short exhibit names; never null nor containing nulls but may be zero-length.
251         * This returns a copy that can be safely tampered with by the caller.
252         */
253        public Name.ExhibitShort[] getAllExhibitShortNamesArraySorted()
254            { return(uniqueShortNames.clone()); }
255    
256        /**Gets the full name of an exhibit from its short-name persistable key; null if none. */
257        public Name.ExhibitFull getFullNameFromPeristableKey(final CharSequence cs)
258            {
259            if(null == cs) { throw new IllegalArgumentException(); }
260    
261            // Get the cached map (or prepare to create one on first use if need be).
262            SortedMap<CharSequence, ExhibitFull> m;
263            while(null == (m = pKeysToFull))
264                {
265                // Optimisation: verify that arg is in fact a legitimate persistable key before expensive map set-up work.
266                if(cs.length() != Name.ExhibitShort.PERSISTABLE_NAME_LENGTH) { return(null); }
267                if(cs.charAt(0) != Name.ExhibitShort.PERSISTABLE_NAME_LEADING_CHAR) { return(null); }
268    
269                // Take a lock so that only one thread tries to compute/store the new map.
270                synchronized(this)
271                    {
272                    // Create and populate map on first use if not yet done by another thread.
273                    if(null == pKeysToFull)
274                        { _computePKeys(); }
275                    }
276                }
277            final ExhibitFull exhibitFull = m.get(cs);
278            assert((null == exhibitFull) || (cs.charAt(1) == exhibitFull.getShortName().charAt(0))) : "decoded name fails sanity check";
279            return(exhibitFull);
280            }
281    
282        /**Computes mapping from persistable keys back to full name.
283         * Should be called under the instance lock for efficiency,
284         * or when there is no possibility of multi-threading
285         * such as from the constructor or readObject().
286         */
287        private void _computePKeys()
288            {
289            // Make new map with CharSequence comparator
290            // so that any CharSequence can be used with get().
291            final SortedMap<CharSequence, ExhibitFull> m = new TreeMap<CharSequence, ExhibitFull>(SORT_ORDER);
292    
293            // Insert all the persistable keys.
294            for(final ExhibitStaticAttr esa : orderedEsa)
295                {
296                final ExhibitFull exhibitFullName = esa.getExhibitFullName();
297                m.put(exhibitFullName.getShortName().persistableKey(), exhibitFullName);
298                }
299            assert(m.size() == orderedEsa.length) : "must be no duplicate persistable keys";
300    
301            pKeysToFull = m; // Atomically assign new map.
302            }
303    
304        /**Gets the full name of an exhibit from its (unique) file component, ie short name or persistable key; null if no exhibit has the specified short name/key.
305         * @param shortName  file component or persistable key; must not be null
306         * @return non-null full name iff the supplied short name is a syntactically-valid short name or persistable key for that extant full name
307         */
308        public Name.ExhibitFull getFullName(final CharSequence shortName)
309            {
310            if(null == shortName) { throw new IllegalArgumentException(); }
311            if(0 == shortName.length()) { return(null); }
312            if(shortName.charAt(0) != Name.ExhibitShort.PERSISTABLE_NAME_LEADING_CHAR)
313                {
314                final int index = Arrays.binarySearch(uniqueShortNames, shortName, SORT_ORDER);
315                if(index >= 0) { return(((Name.ExhibitShort) uniqueShortNames[index]).getFullName()); }
316                }
317            return(getFullNameFromPeristableKey(shortName)); // Attempt fall-back lookup of persistable key.
318            }
319    
320        /**Returns true iff the supplied full exhibit name is valid and is present in this collection.
321         * Equivalent to the 'traditional' (null != getStaticAttr(fullName)) test
322         * but may be more efficient and its purpose is clearer.
323         *
324         * @param fullName  putative full name of an exhibit
325         * @return  true iff fullName is not null
326         *     and is the valid full name of an exhibit in this collection
327         */
328        public boolean isPresent(final CharSequence fullName)
329            {
330            final ExhibitStaticAttr key;
331            try { key = ExhibitStaticAttr.makeNameOnlyKey(fullName); }
332            catch(final IllegalArgumentException e) { return(false); } // If key is not a valid name them search is futile.
333            // Do lookup by binary chop in the ordered array...
334            return(Arrays.binarySearch(orderedEsa, key) >= 0);
335            }
336    
337        /**Look up exhibit attributes by full exhibit name; null if not a valid exhibit in this set.
338         * If result is non-null then the exhibit is valid.
339         * <p>
340         * May be slow if the argument is not a Name.ExhibitFillName,
341         * but especially in this case note that what is returned is the canonical internal ExhibitFull.
342         * <p>
343         * If the argument is null or invalid then this returns null rather than throwing an exception.
344         */
345        public ExhibitStaticAttr getStaticAttr(final CharSequence fullName)
346            {
347            final ExhibitStaticAttr key;
348            try { key = ExhibitStaticAttr.makeNameOnlyKey(fullName); }
349            catch(final IllegalArgumentException e) { return(null); } // If key is not a valid name them search is futile.
350            // Do lookup by binary chop in the ordered array...
351            final int index = Arrays.binarySearch(orderedEsa, key);
352            if(index < 0) { return(null); } // Not present.
353            return(orderedEsa[index]); // Found it!
354            }
355    
356        /**Get exhibit count; 0 if no exhibits. */
357        public int size() { return(length); }
358    
359        /**True iff zero/no exhibits. */
360        public boolean isEmpty() { return(length == 0); }
361    
362        /**Cache for getAllExhibitNamesList() immutable content.
363         * Initially null, but never non-null once set.
364         * <p>
365         * Marked volatile for lock-free thread-safe access.
366         * <p>
367         * Marked transient as this is recomputable redundant state.
368         * <p>
369         * <em>Not cleared once set so that the reference (and its identity hash) will be stable</em>,
370         * suitable for use by AbstractFilterBean._select() for example.
371         */
372        private transient volatile List<Name.ExhibitFull> _cacheSR_getAllExhibitNamesListSorted;
373    
374        /**Gets an immutable sorted RandomAccess list of all full exhibit names; never null.
375         * Result is cached (and does not change)
376         * and so may be more efficient than (say) getAllExhibitNamesSet().
377         */
378        public List<Name.ExhibitFull> getAllExhibitNamesSorted()
379            {
380            List<Name.ExhibitFull> result = _cacheSR_getAllExhibitNamesListSorted;
381            if(null == result)
382                {
383                synchronized(this) // Avoid races while constructing the cache...
384                    {
385                    result = _cacheSR_getAllExhibitNamesListSorted;
386                    if(null == result) // Only recompute if still actually necessary.
387                        {
388                        // Compute and cache (immutable) result.
389                        final ArrayList<Name.ExhibitFull> r = new ArrayList<Name.ExhibitFull>(orderedEsa.length);
390                        for(final ExhibitStaticAttr esa : orderedEsa)
391                            { r.add(esa.getExhibitFullName()); }
392                        r.trimToSize(); // Ensure minimal footprint since this will be around for a while.
393                        // Create an immutable view to avoid any tampering with our internal state.
394                        result = Collections.unmodifiableList(r);
395                        _cacheSR_getAllExhibitNamesListSorted = result; // Cache the result.
396                        }
397                    }
398                }
399            return(result);
400            }
401    
402        /**Gets immutable (sorted) List of all static attrs; never null.
403         * Should be fast to get and access elements of, ie lightweight and efficient.
404         */
405        public List<ExhibitStaticAttr> getAllStaticAttrs()
406            { return(Collections.unmodifiableList(Arrays.asList(orderedEsa))); }
407    
408    //    /**Gets SortedSet of all static attrs; sorted by raw name.
409    //     * Could be very large and slow to get; use with caution.
410    //     */
411    //    public SortedSet<ExhibitStaticAttr> getAllStaticAttrsSorted()
412    //        {
413    //        return(Collections.unmodifiableSortedSet(new TreeSet<ExhibitStaticAttr>(Arrays.asList(orderedEsa))));
414    //        }
415    
416        /**Conservatively estimates filespace bytes required to store all exhibits.
417         * This makes some assumptions about filespace overhead and so on.
418         * <p>
419         * This is for the specific named author, unless author is null in which
420         * case it for all authors.
421         * <p>
422         * This separately rounds up each exhibit size and name size to an
423         * assume filesystem block size as per FileTools.roundUpToFSBlockSize().
424         *
425         * @throws IllegalArgumentException  if author is not null and is not a
426         *     syntactically-valid author name (initials)
427         */
428        public final long computeFileSpaceBytes(final String author)
429            {
430            if((author != null) &&
431               !ExhibitName.validAuthorSyntax(author))
432                { throw new IllegalArgumentException(); }
433    
434            long result = 0;
435            for(final ExhibitStaticAttr esa : orderedEsa)
436                {
437                // Skip this if only adding code for specific author other than this one.
438                final CharSequence name = esa.getCharSequence();
439                if((author != null) &&
440                   !author.equals(ExhibitName.getAuthorComponent(name)))
441                    { continue; } // Not interested in this exhibit.
442    
443                result += FileTools.roundUpToFSBlockSize(name.length());
444                result += FileTools.roundUpToFSBlockSize(esa.length);
445                }
446    
447            return(result);
448            }
449    
450    
451        /**Returns a hash code value for the object derived from the timestamp.
452         * Will be zero (because the timestamp is) if there are no exhibits.
453         *
454         * @return  a hash code value for this object.
455         * @see     Object#equals(Object)
456         * @see     Hashtable
457         */
458        @Override
459        public int hashCode()
460            {
461            return(((int) (timestamp >>> 32)) ^ (int) timestamp);
462            }
463    
464        /**Returns true when the timestamp and underlying set of exhibit names is the same.
465         *
466         * @param   obj   the reference object with which to compare.
467         * @return  <code>true</code> if this object is the same as the obj
468         *          argument; <code>false</code> otherwise.
469         * @see     Boolean#hashCode()
470         * @see     Hashtable
471         */
472        @Override
473        public boolean equals(final Object obj)
474            {
475            if(this == obj) { return(true); }
476            if(!(obj instanceof AllExhibitImmutableData)) { return(false); }
477            final AllExhibitImmutableData other = (AllExhibitImmutableData) obj;
478            if(timestamp != other.timestamp) { return(false); }
479            return(Arrays.equals(orderedEsa, other.orderedEsa));
480            }
481    
482    
483        /**Write out a less-redundant (and more compressible) form of our internal information.
484         * We used to write this out in reverse sorted order;
485         * now we write in in forward sorted order
486         * and the ESA itself should be holding the name in a shared-prefix form.
487         */
488        private void writeObject(final ObjectOutputStream oos)
489            throws IOException
490            {
491            // Write the fields that we are not trying to optimise.
492            // Note that this includes our length field.
493            oos.defaultWriteObject();
494    
495            // Write the ESA data out directly (ie in (forward) sorted order)
496            // assuming that this locality will improve any compression of the serialised stream
497            // even above any work done while creating this instance.
498            final int l = length;
499            for(int i = 0; i < l; ++i)
500                { oos.writeObject(orderedEsa[i]); }
501            }
502    
503        /**Deserialise.
504         */
505        private void readObject(final ObjectInputStream ois)
506            throws IOException, ClassNotFoundException
507            {
508            // Read the fields that we are not trying to optimise.
509            // This includes our length field...
510            ois.defaultReadObject();
511    
512            // Read the map as an array of static attr objects
513            // which we check for re-sort to avoid accident and mischief
514            // and will check for dups later with validateObject().
515            // We replace/set the extant map atomically.
516            final int newSize = length;
517            final ExhibitStaticAttr newOrderedEsa[] = new ExhibitStaticAttr[newSize];
518            for(int i = 0; i < newSize; ++i)
519                { newOrderedEsa[i] = (ExhibitStaticAttr) ois.readObject(); }
520            // Note that the old on-the-wire format had reverse sort order...
521            Arrays.sort(newOrderedEsa);
522            orderedEsa = newOrderedEsa;
523    
524            // Build the short-to-full-name map.
525            _buildShortToFull();
526    
527            optionalEagerWarmup(); // Do eager warm-up if system not too stressed.
528    
529            validateObject();
530            }
531    
532    //    /**Deserialise: use constructor for validation, defensive copying, etc. */
533    //    protected Object readResolve()
534    //        // throws ObjectStreamException
535    //        {
536    //        // Construct new instance of object in normal defensive way.
537    //        // This also ensures that we get a chance to intern() stuff, etc,
538    //        // and reduce old-heap churn in generational GC systems.
539    //        return(new AllExhibitImmutableData(newMap, timestamp));
540    //        }
541    
542        /**Validate fields/state.
543         * Called in the constructor and possibly after de-serialising.
544         * <p>
545         * Barf if something bad is found.
546         * (Maybe allow some extra info in debug version.)
547         */
548        public void validateObject()
549            throws InvalidObjectException
550            {
551            // Check that all components are sane and safe.
552            if(timestamp < 0)
553                { throw new InvalidObjectException("bad object: timestamp < 0"); }
554            if(length < 0)
555                { throw new InvalidObjectException("bad object: length < 0"); }
556            if((length > 0) && (timestamp <= 0))
557                { throw new InvalidObjectException("bad object: timestamp must be positive if there are exhibits"); }
558            if((length == 0) && (timestamp != 0))
559                { throw new InvalidObjectException("bad object: timestamp must be zero if no exhibits"); }
560            // Verify that internal static-attr data is present and correctly ordered.
561            if(null == orderedEsa)
562                { throw new InvalidObjectException("bad object: internal ordered ESA data is absent"); }
563            for(int i = orderedEsa.length; --i >= 0; )
564                {
565                if(!(orderedEsa[i] instanceof ExhibitStaticAttr))
566                    { throw new InvalidObjectException("bad object: invalid ESA data"); }
567                }
568            for(int i = orderedEsa.length; --i > 0; )
569                {
570                if(orderedEsa[i-1].compareTo(orderedEsa[i]) >= 0)
571                    { throw new InvalidObjectException("bad object: ESA data not correctly totally ordered"); }
572                }
573            // The two size measures must match.
574            if(length != orderedEsa.length)
575                { throw new InvalidObjectException("bad object: length != internal data length"); }
576            // The two external size measures must match.
577            if(length != size())
578                { throw new InvalidObjectException("bad object: length != size()"); }
579            }
580    
581        /**Our serial version... */
582        private static final long serialVersionUID = 1227490240470952655L;
583        }