001: /* JFox, the OpenSource J2EE Application Server
002: *
003: * Copyright (C) 2002 huihoo.org
004: * Distributable under GNU LGPL license
005: * See the GNU Lesser General Public License for more details.
006: */
007:
008: package javax.management.openmbean;
009:
010: import java.util.Map;
011: import java.util.List;
012: import java.util.HashMap;
013: import java.util.Arrays;
014: import java.util.ArrayList;
015: import java.util.Set;
016: import java.util.Collection;
017: import java.util.Iterator;
018: import java.util.Collections;
019: import java.io.Serializable;
020: import java.io.IOException;
021: import java.io.ObjectInputStream;
022:
023: /**
024: * The <tt>TabularDataSupport</tt> class is the <i>open data</i> class which implements the <tt>TabularData</tt>
025: * and the <tt>Map</tt> interfaces, and which is internally based on a hash map data structure.
026: *
027: * @author <a href="mailto:young_yy@hotmail.org">Young Yang</a>
028: */
029:
030: public class TabularDataSupport implements TabularData, Map, Cloneable,
031: Serializable {
032:
033: /**
034: * @serial This tabular data instance's contents: a {@link HashMap}
035: */
036: private Map dataMap;
037:
038: /**
039: * @serial This tabular data instance's tabular type
040: */
041: private TabularType tabularType;
042:
043: /**
044: * @serial The array of item names that define the index used for rows (convenience field)
045: */
046: private transient String[] indexNamesArray;
047:
048: /* *** Constructors *** */
049:
050: /**
051: * Creates an empty <tt>TabularDataSupport</tt> instance whose open-type is <var>tabularType</var>,
052: * and whose underlying <tt>HashMap</tt> has a default initial capacity (101) and default load factor (0.75).
053: * <p>
054: * This constructor simply calls <tt>this(tabularType, 101, 0.75f);</tt>
055: *
056: * @param tabularType the <i>tabular type</i> describing this <tt>TabularData</tt> instance;
057: * cannot be null.
058: *
059: * @throws IllegalArgumentException if the tabular type is null.
060: */
061: public TabularDataSupport(TabularType tabularType) {
062:
063: this (tabularType, 101, 0.75f);
064: }
065:
066: /**
067: * Creates an empty <tt>TabularDataSupport</tt> instance whose open-type is <var>tabularType</var>,
068: * and whose underlying <tt>HashMap</tt> has the specified initial capacity and load factor.
069: *
070: * @param tabularType the <i>tabular type</i> describing this <tt>TabularData</tt> instance;
071: * cannot be null.
072: *
073: * @param initialCapacity the initial capacity of the HashMap.
074: *
075: * @param loadFactor the load factor of the HashMap
076: *
077: * @throws IllegalArgumentException if the initial capacity is less than zero,
078: * or the load factor is nonpositive,
079: * or the tabular type is null.
080: */
081: public TabularDataSupport(TabularType tabularType,
082: int initialCapacity, float loadFactor) {
083:
084: // Check tabularType is not null
085: //
086: if (tabularType == null) {
087: throw new IllegalArgumentException(
088: "Argument tabularType cannot be null.");
089: }
090:
091: // Initialize this.tabularType (and indexNamesArray for convenience)
092: //
093: this .tabularType = tabularType;
094: List tmpNames = tabularType.getIndexNames();
095: this .indexNamesArray = (String[]) tmpNames
096: .toArray(new String[tmpNames.size()]);
097:
098: // Construct the empty contents HashMap
099: //
100: this .dataMap = new HashMap(initialCapacity, loadFactor);
101: }
102:
103: /* *** TabularData specific information methods *** */
104:
105: /**
106: * Returns the <i>tabular type</i> describing this <tt>TabularData</tt> instance.
107: */
108: public TabularType getTabularType() {
109:
110: return tabularType;
111: }
112:
113: /**
114: * Calculates the index that would be used in this <tt>TabularData</tt> instance to refer to the specified
115: * composite data <var>value</var> parameter if it were added to this instance.
116: * This method checks for the type validity of the specified <var>value</var>,
117: * but does not check if the calculated index is already used to refer to a value in this <tt>TabularData</tt> instance.
118: *
119: * @param value the composite data value whose index in this
120: * <tt>TabularData</tt> instance is to be calculated;
121: * must be of the same composite type as this instance's row type;
122: * must not be null.
123: *
124: * @return the index that the specified <var>value</var> would have in this <tt>TabularData</tt> instance.
125: *
126: * @throws NullPointerException if <var>value</var> is <tt>null</tt>.
127: *
128: * @throws InvalidOpenTypeException if <var>value</var> does not conform to this <tt>TabularData</tt> instance's
129: * row type definition.
130: */
131: public Object[] calculateIndex(CompositeData value) {
132:
133: // Check value is valid
134: //
135: checkValueType(value);
136:
137: // Return its calculated index
138: //
139: return internalCalculateIndex(value).toArray();
140: }
141:
142: /* *** Content information query methods *** */
143:
144: /**
145: * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains a <tt>CompositeData</tt> value
146: * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> cannot be cast to a one dimension array
147: * of Object instances, this method simply returns <tt>false</tt>; otherwise it returns the the result of the call to
148: * <tt>this.containsKey((Object[]) key)</tt>.
149: *
150: * @param key the index value whose presence in this <tt>TabularData</tt> instance is to be tested.
151: *
152: * @return <tt>true</tt> if this <tt>TabularData</tt> indexes a row value with the specified key.
153: */
154: public boolean containsKey(Object key) {
155:
156: // if key is not an array of Object instances, return false
157: //
158: Object[] k;
159: try {
160: k = (Object[]) key;
161: } catch (ClassCastException e) {
162: return false;
163: }
164:
165: return this .containsKey(k);
166: }
167:
168: /**
169: * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains a <tt>CompositeData</tt> value
170: * (ie a row) whose index is the specified <var>key</var>. If <var>key</var> is <tt>null</tt> or does not conform to
171: * this <tt>TabularData</tt> instance's <tt>TabularType</tt> definition, this method simply returns <tt>false</tt>.
172: *
173: * @param key the index value whose presence in this <tt>TabularData</tt> instance is to be tested.
174: *
175: * @return <tt>true</tt> if this <tt>TabularData</tt> indexes a row value with the specified key.
176: */
177: public boolean containsKey(Object[] key) {
178:
179: return (key == null ? false : dataMap.containsKey(Arrays
180: .asList(key)));
181: }
182:
183: /**
184: * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains the specified
185: * <tt>CompositeData</tt> value. If <var>value</var> is <tt>null</tt> or does not conform to
186: * this <tt>TabularData</tt> instance's row type definition, this method simply returns <tt>false</tt>.
187: *
188: * @param value the row value whose presence in this <tt>TabularData</tt> instance is to be tested.
189: *
190: * @return <tt>true</tt> if this <tt>TabularData</tt> instance contains the specified row value.
191: */
192: public boolean containsValue(CompositeData value) {
193:
194: return dataMap.containsValue(value);
195: }
196:
197: /**
198: * Returns <tt>true</tt> if and only if this <tt>TabularData</tt> instance contains the specified
199: * value.
200: *
201: * @param value the row value whose presence in this <tt>TabularData</tt> instance is to be tested.
202: *
203: * @return <tt>true</tt> if this <tt>TabularData</tt> instance contains the specified row value.
204: */
205: public boolean containsValue(Object value) {
206:
207: return dataMap.containsValue(value);
208: }
209:
210: /**
211: * This method simply calls <tt>get((Object[]) key)</tt>.
212: *
213: * @throws NullPointerException if the <var>key</var> is <tt>null</tt>
214: * @throws ClassCastException if the <var>key</var> is not of the type <tt>Object[]</tt>
215: * @throws InvalidKeyException if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
216: * <tt>TabularType</tt> definition
217: */
218: public Object get(Object key) {
219:
220: return get((Object[]) key);
221: }
222:
223: /**
224: * Returns the <tt>CompositeData</tt> value whose index is
225: * <var>key</var>, or <tt>null</tt> if there is no value mapping
226: * to <var>key</var>, in this <tt>TabularData</tt> instance.
227: *
228: * @param key the index of the value to get in this
229: * <tt>TabularData</tt> instance; * must be valid with this
230: * <tt>TabularData</tt> instance's row type definition; * must not
231: * be null.
232: *
233: * @return the value corresponding to <var>key</var>.
234: *
235: * @throws NullPointerException if the <var>key</var> is <tt>null</tt>
236: * @throws InvalidKeyException if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
237: * <tt>TabularType</tt> type definition.
238: */
239: public CompositeData get(Object[] key) {
240:
241: // Check key is not null and valid with tabularType
242: // (throws NullPointerException, InvalidKeyException)
243: //
244: checkKeyType(key);
245:
246: // Return the mapping stored in the parent HashMap
247: //
248: return (CompositeData) dataMap.get(Arrays.asList(key));
249: }
250:
251: /* *** Content modification operations (one element at a time) *** */
252:
253: /**
254: * This method simply calls <tt>put((CompositeData) value)</tt> and
255: * therefore ignores its <var>key</var> parameter which can be <tt>null</tt>.
256: *
257: * @param key an ignored parameter.
258: * @param value the {@link CompositeData} to put.
259: *
260: * @return the value which is put
261: *
262: * @throws NullPointerException if the <var>value</var> is <tt>null</tt>
263: * @throws ClassCastException if the <var>value</var> is not of the type <tt>CompositeData</tt>
264: * @throws InvalidOpenTypeException if the <var>value</var> does not conform to this <tt>TabularData</tt> instance's
265: * <tt>TabularType</tt> definition
266: * @throws KeyAlreadyExistsException if the key for the <var>value</var> parameter, calculated according to
267: * this <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
268: * already maps to an existing value
269: */
270: public Object put(Object key, Object value) {
271:
272: put((CompositeData) value);
273: return value;
274: }
275:
276: public void put(CompositeData value) {
277:
278: // Check value is not null, value's type is the same as this instance's row type,
279: // and calculate the value's index according to this instance's tabularType and
280: // check it is not already used for a mapping in the parent HashMap
281: //
282: List index = checkValueAndIndex(value);
283:
284: // store the (key, value) mapping in the dataMap HashMap
285: //
286: dataMap.put(index, value);
287: }
288:
289: /**
290: * This method simply calls <tt>remove((Object[]) key)</tt>.
291: *
292: * @param key an <tt>Object[]</tt> representing the key to remove.
293: *
294: * @return previous value associated with specified key, or <tt>null</tt>
295: * if there was no mapping for key.
296: *
297: * @throws NullPointerException if the <var>key</var> is <tt>null</tt>
298: * @throws ClassCastException if the <var>key</var> is not of the type <tt>Object[]</tt>
299: * @throws InvalidKeyException if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
300: * <tt>TabularType</tt> definition
301: */
302: public Object remove(Object key) {
303:
304: return remove((Object[]) key);
305: }
306:
307: /**
308: * Removes the <tt>CompositeData</tt> value whose index is <var>key</var> from this <tt>TabularData</tt> instance,
309: * and returns the removed value, or returns <tt>null</tt> if there is no value whose index is <var>key</var>.
310: *
311: * @param key the index of the value to get in this <tt>TabularData</tt> instance;
312: * must be valid with this <tt>TabularData</tt> instance's row type definition;
313: * must not be null.
314: *
315: * @return previous value associated with specified key, or <tt>null</tt>
316: * if there was no mapping for key.
317: *
318: * @throws NullPointerException if the <var>key</var> is <tt>null</tt>
319: * @throws InvalidKeyException if the <var>key</var> does not conform to this <tt>TabularData</tt> instance's
320: * <tt>TabularType</tt> definition
321: */
322: public CompositeData remove(Object[] key) {
323:
324: // Check key is not null and valid with tabularType
325: // (throws NullPointerException, InvalidKeyException)
326: //
327: checkKeyType(key);
328:
329: // Removes the (key, value) mapping in the parent HashMap
330: //
331: return (CompositeData) dataMap.remove(Arrays.asList(key));
332: }
333:
334: /* *** Content modification bulk operations *** */
335:
336: /**
337: * Add all the values contained in the specified map <var>t</var> to this <tt>TabularData</tt> instance.
338: * This method converts the collection of values contained in this map into an array of <tt>CompositeData</tt> values,
339: * if possible, and then call the method <tt>putAll(CompositeData[])</tt>. Note that the keys used in the specified
340: * map <var>t</var> are ignored. This method allows, for example to add the content of another <tt>TabularData</tt>
341: * instance with the same row type (but possibly different index names) into this instance.
342: *
343: * @param t the map whose values are to be added as new rows to this <tt>TabularData</tt> instance;
344: * if <var>t</var> is <tt>null</tt> or empty, this method returns without doing anything.
345: *
346: * @throws NullPointerException if a value in <var>t</var> is <tt>null</tt>.
347: * @throws ClassCastException if a value in <var>t</var> is not an instance of <tt>CompositeData</tt>.
348: * @throws InvalidOpenTypeException if a value in <var>t</var> does not conform to
349: * this <tt>TabularData</tt> instance's row type definition.
350: * @throws KeyAlreadyExistsException if the index for a value in <var>t</var>, calculated according to
351: * this <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
352: * already maps to an existing value in this instance,
353: * or two values in <var>t</var> have the same index.
354: */
355: public void putAll(Map t) {
356:
357: // if t is null or empty, just return
358: //
359: if ((t == null) || (t.size() == 0)) {
360: return;
361: }
362:
363: // Convert the values in t into an array of <tt>CompositeData</tt>
364: //
365: CompositeData[] values;
366: try {
367: values = (CompositeData[]) t.values().toArray(
368: new CompositeData[t.size()]);
369: } catch (java.lang.ArrayStoreException e) {
370: throw new ClassCastException(
371: "Map argument t contains values which are not instances of <tt>CompositeData</tt>");
372: }
373:
374: // Add the array of values
375: //
376: putAll(values);
377: }
378:
379: /**
380: * Add all the elements in <var>values</var> to this <tt>TabularData</tt> instance.
381: * If any element in <var>values</var> does not satisfy the constraints defined in {@link #put(CompositeData) <tt>put</tt>},
382: * or if any two elements in <var>values</var> have the same index calculated according to this <tt>TabularData</tt>
383: * instance's <tt>TabularType</tt> definition, then an exception describing the failure is thrown
384: * and no element of <var>values</var> is added, thus leaving this <tt>TabularData</tt> instance unchanged.
385: *
386: * @param values the array of composite data values to be added as new rows to this <tt>TabularData</tt> instance;
387: * if <var>values</var> is <tt>null</tt> or empty, this method returns without doing anything.
388: *
389: * @throws NullPointerException if an element of <var>values</var> is <tt>null</tt>
390: * @throws InvalidOpenTypeException if an element of <var>values</var> does not conform to
391: * this <tt>TabularData</tt> instance's row type definition
392: * (ie its <tt>TabularType</tt> definition)
393: * @throws KeyAlreadyExistsException if the index for an element of <var>values</var>, calculated according to
394: * this <tt>TabularData</tt> instance's <tt>TabularType</tt> definition
395: * already maps to an existing value in this instance,
396: * or two elements of <var>values</var> have the same index
397: */
398: public void putAll(CompositeData[] values) {
399:
400: // if values is null or empty, just return
401: //
402: if ((values == null) || (values.length == 0)) {
403: return;
404: }
405:
406: // doCreate the list of indexes corresponding to each value
407: ArrayList indexes = new ArrayList(values.length + 1);
408:
409: // Check all elements in values and build index list
410: //
411: List index;
412: for (int i = 0; i < values.length; i++) {
413: // check value and calculate index
414: index = checkValueAndIndex(values[i]);
415: // check index is different of those previously calculated
416: if (indexes.contains(index)) {
417: throw new KeyAlreadyExistsException(
418: "Argument elements values["
419: + i
420: + "] and values["
421: + indexes.indexOf(index)
422: + "] have the same indexes, "
423: + "calculated according to this TabularData instance's tabularType.");
424: }
425: // add to index list
426: indexes.add(index);
427: }
428:
429: // store all (index, value) mappings in the dataMap HashMap
430: //
431: for (int i = 0; i < values.length; i++) {
432: dataMap.put(indexes.get(i), values[i]);
433: }
434: }
435:
436: /**
437: * Removes all rows from this <code>TabularDataSupport</code> instance.
438: */
439: public void clear() {
440:
441: dataMap.clear();
442: }
443:
444: /* *** Informational methods from java.util.Map *** */
445:
446: /**
447: * Returns the number of rows in this <code>TabularDataSupport</code> instance.
448: *
449: * @return the number of rows in this <code>TabularDataSupport</code> instance.
450: */
451: public int size() {
452:
453: return dataMap.size();
454: }
455:
456: /**
457: * Returns <tt>true</tt> if this <code>TabularDataSupport</code> instance contains no rows.
458: *
459: * @return <tt>true</tt> if this <code>TabularDataSupport</code> instance contains no rows.
460: */
461: public boolean isEmpty() {
462:
463: return (this .size() == 0);
464: }
465:
466: /* *** Collection views from java.util.Map *** */
467:
468: /**
469: * Returns a set view of the keys contained in the underlying map of this <code>TabularDataSupport</code> instance,
470: * and used to index the rows. Each key contained in this set is an unmodifiable List.
471: * The set is backed by the underlying map of this <code>TabularDataSupport</code> instance,
472: * so changes to the <code>TabularDataSupport</code> instance are reflected in the set, and vice-versa.
473: *
474: * The set supports element removal, which removes the
475: * corresponding row from this <code>TabularDataSupport</code> instance, via the <tt>Iterator.remove</tt>,
476: * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and
477: * <tt>clear</tt> operations.
478: * It does not support the <tt>add</tt> or <tt>addAll</tt> operations
479: *
480: * @return a set view of the keys used to index the rows of this <code>TabularDataSupport</code> instance.
481: */
482: public Set keySet() {
483:
484: return dataMap.keySet();
485: }
486:
487: /**
488: * Returns a collection view of the rows contained in this <code>TabularDataSupport</code> instance.
489: * The collection is backed by the underlying map, so changes to the <code>TabularDataSupport</code> instance
490: * are reflected in the collection, and vice-versa.
491: *
492: * The collection supports element removal,
493: * which removes the corresponding index to row mapping from this <code>TabularDataSupport</code> instance,
494: * via the <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
495: * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
496: * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
497: *
498: * @return a collection view of the values contained in this <code>TabularDataSupport</code> instance.
499: */
500: public Collection values() {
501:
502: return dataMap.values();
503: }
504:
505: /**
506: * Returns a collection view of the index to row mappings contained in this <code>TabularDataSupport</code> instance.
507: * Each element in the returned collection is a <tt>Map.Entry</tt>.
508: * The collection is backed by the underlying map of this <code>TabularDataSupport</code> instance, in
509: * so changes to the <code>TabularDataSupport</code> instance are reflected the collection, and vice-versa.
510: * The collection supports element removal, which removes the corresponding mapping from the map, via the
511: * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
512: * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations.
513: * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.
514: * <p>
515: * <b>IMPORTANT NOTICE</b>: Do not use the <tt>SetValue</tt> method of <tt>Map.Entry</tt> elements contained in the returned
516: * collection view. Doing so would corrupt the index to row mappings contained in this <code>TabularDataSupport</code> instance.
517: * <p>
518: *
519: * @return a collection view of the mappings contained in this map.
520: * @see java.util.Map.Entry
521: */
522: public Set entrySet() {
523:
524: return dataMap.entrySet();
525: }
526:
527: /* *** Commodity methods from java.lang.Object *** */
528:
529: /**
530: * Returns a clone of this <code>TabularDataSupport</code> instance:
531: * the clone is obtained by calling <tt>super.clone()</tt>, and then cloning the underlying map.
532: * Only a shallow clone of the underlying map is made, i.e. no cloning of the indexes and row values is made as they are immutable.
533: */
534: public Object clone() {
535: try {
536: TabularDataSupport c = (TabularDataSupport) super .clone();
537: c.dataMap = (HashMap) ((HashMap) c.dataMap).clone();
538: return c;
539: } catch (CloneNotSupportedException e) {
540: throw new InternalError(e.toString());
541: }
542: }
543:
544: /**
545: * Compares the specified <var>obj</var> parameter with this <code>TabularDataSupport</code> instance for equality.
546: * <p>
547: * Returns <tt>true</tt> if and only if all of the following statements are true:
548: * <ul>
549: * <li><var>obj</var> is non null,</li>
550: * <li><var>obj</var> also implements the <code>TabularData</code> interface,</li>
551: * <li>their tabular types are equal</li>
552: * <li>their contents (ie all CompositeData values) are equal.</li>
553: * </ul>
554: * This ensures that this <tt>equals</tt> method works properly for <var>obj</var> parameters which are
555: * different implementations of the <code>TabularData</code> interface.
556: * <br>
557: * @param obj the object to be compared for equality with this <code>TabularDataSupport</code> instance;
558: *
559: * @return <code>true</code> if the specified object is equal to this <code>TabularDataSupport</code> instance.
560: */
561: public boolean equals(Object obj) {
562:
563: // if obj is null, return false
564: //
565: if (obj == null) {
566: return false;
567: }
568:
569: // if obj is not a TabularData, return false
570: //
571: TabularData other;
572: try {
573: other = (TabularData) obj;
574: } catch (ClassCastException e) {
575: return false;
576: }
577:
578: // Now, really test for equality between this TabularData implementation and the other:
579: //
580:
581: // their tabularType should be equal
582: if (!this .getTabularType().equals(other.getTabularType())) {
583: return false;
584: }
585:
586: // their contents should be equal:
587: // . same size
588: // . values in this instance are in the other (we know there are no duplicate elements possible)
589: // (row values comparison is enough, because keys are calculated according to tabularType)
590:
591: if (this .size() != other.size()) {
592: return false;
593: }
594: for (Iterator iter = this .values().iterator(); iter.hasNext();) {
595: CompositeData value = (CompositeData) iter.next();
596: if (!other.containsValue(value)) {
597: return false;
598: }
599: }
600:
601: // All tests for equality were successfull
602: //
603: return true;
604: }
605:
606: /**
607: * Returns the hash code value for this <code>TabularDataSupport</code> instance.
608: * <p>
609: * The hash code of a <code>TabularDataSupport</code> instance is the sum of the hash codes
610: * of all elements of information used in <code>equals</code> comparisons
611: * (ie: its <i>tabular type</i> and its content, where the content is defined as all the CompositeData values).
612: * <p>
613: * This ensures that <code> t1.equals(t2) </code> implies that <code> t1.hashCode()==t2.hashCode() </code>
614: * for any two <code>TabularDataSupport</code> instances <code>t1</code> and <code>t2</code>,
615: * as required by the general contract of the method
616: * {@link <a href="http://java.sun.com/j2se/1.3/docs/api/java/lang/Object.html#hashCode()">
617: * <code>Object.hashCode</code> </a>}.
618: * <p>
619: * However, note that another instance of a class implementing the <code>TabularData</code> interface
620: * may be equal to this <code>TabularDataSupport</code> instance as defined by {@link #equals},
621: * but may have a different hash code if it is calculated differently.
622: *
623: * @return the hash code value for this <code>TabularDataSupport</code> instance
624: */
625: public int hashCode() {
626:
627: int result = 0;
628:
629: result += this .tabularType.hashCode();
630: for (Iterator iter = this .values().iterator(); iter.hasNext();) {
631: result += iter.next().hashCode();
632: }
633:
634: return result;
635:
636: }
637:
638: /**
639: * Returns a string representation of this <code>TabularDataSupport</code> instance.
640: * <p>
641: * The string representation consists of the name of this class (ie <code>javax.management.openmbean.TabularDataSupport</code>),
642: * the string representation of the tabular type of this instance, and the string representation of the contents
643: * (ie list the key=value mappings as returned by a call to
644: * <tt>dataMap.{@link <a href="http://java.sun.com/j2se/1.3/docs/api/java/util/HashMap.html#toString()">toString()</a>}</tt>).
645: *
646: * @return a string representation of this <code>TabularDataSupport</code> instance
647: */
648: public String toString() {
649:
650: return new StringBuffer().append(this .getClass().getName())
651: .append("(tabularType=").append(tabularType.toString())
652: .append(",contents=").append(dataMap.toString())
653: .append(")").toString();
654: }
655:
656: /* *** TabularDataSupport internal utility methods *** */
657:
658: /**
659: * Returns the index for value, assuming value is valid for this <tt>TabularData</tt> instance
660: * (ie value is not null, and its composite type is equal to row type).
661: *
662: * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents,
663: * not just the objects references as is done for an array object.
664: *
665: * The returned List is unmodifiable so that once a row has been put into the dataMap, its index cannot be modified,
666: * for example by a user that would attempt to modify an index contained in the Set returned by keySet().
667: */
668: private List internalCalculateIndex(CompositeData value) {
669:
670: return Collections.unmodifiableList(Arrays.asList(value
671: .getAll(this .indexNamesArray)));
672: }
673:
674: /**
675: * Checks if the specified key is valid for this <tt>TabularData</tt> instance.
676: *
677: * @throws NullPointerException
678: * @throws InvalidOpenTypeException
679: */
680: private void checkKeyType(Object[] key) {
681:
682: // Check key is neither null nor empty
683: //
684: if ((key == null) || (key.length == 0)) {
685: throw new NullPointerException(
686: "Argument key cannot be null or empty.");
687: }
688:
689: /* Now check key is valid with tabularType index and row type definitions: */
690:
691: // key[] should have the size expected for an index
692: //
693: if (key.length != this .indexNamesArray.length) {
694: throw new InvalidKeyException(
695: "Argument key's length="
696: + key.length
697: + " is different from the number of item values, which is "
698: + indexNamesArray.length
699: + ", specified for the indexing rows in this TabularData instance.");
700: }
701:
702: // each element in key[] should be a value for its corresponding open type specified in rowType
703: //
704: OpenType keyElementType;
705: for (int i = 0; i < key.length; i++) {
706: keyElementType = tabularType.getRowType().getType(
707: this .indexNamesArray[i]);
708: if ((key[i] != null) && (!keyElementType.isValue(key[i]))) {
709: throw new InvalidKeyException(
710: "Argument element key["
711: + i
712: + "] is not a value for the open type expected for "
713: + "this element of the index, whose name is \""
714: + indexNamesArray[i]
715: + "\" and whose open type is "
716: + keyElementType);
717: }
718: }
719: }
720:
721: /**
722: * Checks the specified value's type is valid for this <tt>TabularData</tt> instance
723: * (ie value is not null, and its composite type is equal to row type).
724: *
725: * @throws NullPointerException
726: * @throws InvalidOpenTypeException
727: */
728: private void checkValueType(CompositeData value) {
729:
730: // Check value is not null
731: //
732: if (value == null) {
733: throw new NullPointerException(
734: "Argument value cannot be null.");
735: }
736:
737: // if value's type is not the same as this instance's row type, throw InvalidOpenTypeException
738: //
739: if (!value.getCompositeType().equals(tabularType.getRowType())) {
740: throw new InvalidOpenTypeException(
741: "Argument value's composite type ["
742: + value.getCompositeType()
743: + "] is not equal to "
744: + "this TabularData instance's row type ["
745: + tabularType.getRowType() + "].");
746: }
747: }
748:
749: /**
750: * Checks if the specified value can be put (ie added) in this <tt>TabularData</tt> instance
751: * (ie value is not null, its composite type is equal to row type, and its index is not already used),
752: * and returns the index calculated for this value.
753: *
754: * The index is a List, and not an array, so that an index.equals(otherIndex) call will actually compare contents,
755: * not just the objects references as is done for an array object.
756: *
757: * @throws NullPointerException
758: * @throws InvalidOpenTypeException
759: * @throws KeyAlreadyExistsException
760: */
761: private List checkValueAndIndex(CompositeData value) {
762:
763: // Check value is valid
764: //
765: checkValueType(value);
766:
767: // Calculate value's index according to this instance's tabularType
768: // and check it is not already used for a mapping in the parent HashMap
769: //
770: List index = internalCalculateIndex(value);
771:
772: if (dataMap.containsKey(index)) {
773: throw new KeyAlreadyExistsException(
774: "Argument value's index, calculated according to this TabularData "
775: + "instance's tabularType, already refers to a value in this table.");
776: }
777:
778: // The check is OK, so return the index
779: //
780: return index;
781: }
782:
783: /**
784: * Deserializes a {@link TabularDataSupport} from an {@link ObjectInputStream}.
785: */
786: private void readObject(ObjectInputStream in) throws IOException,
787: ClassNotFoundException {
788: in.defaultReadObject();
789: List tmpNames = tabularType.getIndexNames();
790: indexNamesArray = (String[]) tmpNames
791: .toArray(new String[tmpNames.size()]);
792: }
793: }
|