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 }