blob: 264ecf97b326facf8ab200e1796b90295f3a4426 [file] [log] [blame]
Christian van Spaandonk63814412008-08-02 09:56:01 +00001/*
Richard S. Hall8df9ab12009-07-24 17:06:37 +00002 * Copyright (c) OSGi Alliance (2004, 2008). All Rights Reserved.
Christian van Spaandonk63814412008-08-02 09:56:01 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package info.dmtree;
17
18import java.util.Arrays;
19import java.util.Hashtable;
20
21// Possible enhancements to this class:
22// * new constructors and get/set methods for b64, to access the encoded value
23// * new constructors and get/set methods for date/time, for more convenient
24// Java access
25/**
26 * An immutable data structure representing the contents of a leaf or interior
27 * node. This structure represents only the value and the format property of the
28 * node, all other properties (like MIME type) can be set and read using the
29 * <code>DmtSession</code> interface.
30 * <p>
31 * Different constructors are available to create nodes with different formats.
32 * Nodes of <code>null</code> format can be created using the static
33 * {@link #NULL_VALUE} constant instance of this class.
34 * <p>
35 * {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING} enable the support
36 * of future data formats. When using these formats, the actual format name is
37 * specified as a <code>String</code>. The application is responsible for the
38 * proper encoding of the data according to the specified format.
Richard S. Hall8df9ab12009-07-24 17:06:37 +000039 *
40 * @version $Revision: 5673 $
Christian van Spaandonk63814412008-08-02 09:56:01 +000041 */
42public final class DmtData {
43
44 /**
45 * The node holds an OMA DM <code>int</code> value.
46 */
47 public static final int FORMAT_INTEGER = 0x0001;
48
49 /**
50 * The node holds an OMA DM <code>float</code> value.
51 */
52 public static final int FORMAT_FLOAT = 0x0002;
53
54 /**
55 * The node holds an OMA DM <code>chr</code> value.
56 */
57 public static final int FORMAT_STRING = 0x0004;
58
59 /**
60 * The node holds an OMA DM <code>bool</code> value.
61 */
62 public static final int FORMAT_BOOLEAN = 0x0008;
63
64 /**
65 * The node holds an OMA DM <code>date</code> value.
66 */
67 public static final int FORMAT_DATE = 0x0010;
68
69 /**
70 * The node holds an OMA DM <code>time</code> value.
71 */
72 public static final int FORMAT_TIME = 0x0020;
73
74 /**
75 * The node holds an OMA DM <code>bin</code> value. The value of the node
76 * corresponds to the Java <code>byte[]</code> type.
77 */
78 public static final int FORMAT_BINARY = 0x0040;
79
80 /**
81 * The node holds an OMA DM <code>b64</code> value. Like
82 * {@link #FORMAT_BINARY}, this format is also represented by the Java
83 * <code>byte[]</code> type, the difference is only in the corresponding
84 * OMA DM format.
85 */
86 public static final int FORMAT_BASE64 = 0x0080;
87
88 /**
89 * The node holds an OMA DM <code>xml</code> value.
90 */
91 public static final int FORMAT_XML = 0x0100;
92
93 /**
94 * The node holds an OMA DM <code>null</code> value. This corresponds to
95 * the Java <code>null</code> type.
96 */
97 public static final int FORMAT_NULL = 0x0200;
98
99 /**
100 * Format specifier of an internal node. An interior node can hold a Java
101 * object as value (see {@link DmtData#DmtData(Object)} and
102 * {@link DmtData#getNode()}). This value can be used by Java programs that
103 * know a specific URI understands the associated Java type. This type is
104 * further used as a return value of the {@link MetaNode#getFormat} method
105 * for interior nodes.
106 */
107 public static final int FORMAT_NODE = 0x0400;
108
109 /**
110 * The node holds raw protocol data encoded as <code>String</code>. The
111 * {@link #getFormatName()} method can be used to get the actual format
112 * name.
113 */
114 public static final int FORMAT_RAW_STRING = 0x0800;
115
116 /**
117 * The node holds raw protocol data encoded in binary format. The
118 * {@link #getFormatName()} method can be used to get the actual format
119 * name.
120 */
121 public static final int FORMAT_RAW_BINARY = 0x1000;
122
123
124 private static final Hashtable FORMAT_NAMES = new Hashtable();
125
126 static {
127 FORMAT_NAMES.put(new Integer(FORMAT_BASE64), "b64");
128 FORMAT_NAMES.put(new Integer(FORMAT_BINARY), "bin");
129 FORMAT_NAMES.put(new Integer(FORMAT_BOOLEAN), "bool");
130 FORMAT_NAMES.put(new Integer(FORMAT_DATE), "date");
131 FORMAT_NAMES.put(new Integer(FORMAT_FLOAT), "float");
132 FORMAT_NAMES.put(new Integer(FORMAT_INTEGER), "int");
133 FORMAT_NAMES.put(new Integer(FORMAT_NODE), "node");
134 FORMAT_NAMES.put(new Integer(FORMAT_NULL), "null");
135 FORMAT_NAMES.put(new Integer(FORMAT_STRING), "chr");
136 FORMAT_NAMES.put(new Integer(FORMAT_TIME), "time");
137 FORMAT_NAMES.put(new Integer(FORMAT_XML), "xml");
138 }
139
140 /**
141 * Constant instance representing a leaf node of <code>null</code> format.
142 */
143 public static final DmtData NULL_VALUE = new DmtData();
144 // FORMAT_NAMES must be initialized by the time the constr. is called
145
146 private final String str;
147
148 private final int integer;
149
150 private final float flt;
151
152 private final boolean bool;
153
154 private final byte[] bytes;
155
156 private final int format;
157
158 private final String formatName;
159
160 private final Object complex;
161
162 /**
163 * Create a <code>DmtData</code> instance of <code>null</code> format.
164 * This constructor is private and used only to create the public
165 * {@link #NULL_VALUE} constant.
166 */
167 private DmtData() {
168 format = FORMAT_NULL;
169 formatName = getFormatName(format);
170
171 this.str = null;
172 this.integer = 0;
173 this.flt = 0;
174 this.bool = false;
175 this.bytes = null;
176 this.complex = null;
177 }
178
179 /**
180 * Create a <code>DmtData</code> instance of <code>chr</code> format
181 * with the given string value. The <code>null</code> string argument is
182 * valid.
183 *
184 * @param str the string value to set
185 */
186 public DmtData(String str) {
187 format = FORMAT_STRING;
188 formatName = getFormatName(format);
189 this.str = str;
190
191 this.integer = 0;
192 this.flt = 0;
193 this.bool = false;
194 this.bytes = null;
195 this.complex = null;
196 }
197
198 /**
199 * Create a <code>DmtData</code> instance of <code>node</code> format
200 * with the given object value. The value represents complex data associated
201 * with an interior node.
202 * <p>
203 * Certain interior nodes can support access to their subtrees through such
204 * complex values, making it simpler to retrieve or update all leaf nodes in
205 * a subtree.
206 * <p>
207 * The given value must be a non-<code>null</code> immutable object.
208 *
209 * @param complex the complex data object to set
210 */
211 public DmtData(Object complex) {
212 if(complex == null)
213 throw new NullPointerException("Complex data argument is null.");
214
215 format = FORMAT_NODE;
216 formatName = getFormatName(format);
217 this.complex = complex;
218
219 this.str = null;
220 this.integer = 0;
221 this.flt = 0;
222 this.bool = false;
223 this.bytes = null;
224 }
225
226 /**
227 * Create a <code>DmtData</code> instance of the specified format and set
228 * its value based on the given string. Only the following string-based
229 * formats can be created using this constructor:
230 * <ul>
231 * <li>{@link #FORMAT_STRING} - value can be any string
232 * <li>{@link #FORMAT_XML} - value must contain an XML fragment (the
233 * validity is not checked by this constructor)
234 * <li>{@link #FORMAT_DATE} - value must be parseable to an ISO 8601
235 * calendar date in complete representation, basic format (pattern
236 * <tt>CCYYMMDD</tt>)
237 * <li>{@link #FORMAT_TIME} - value must be parseable to an ISO 8601 time
238 * of day in either local time, complete representation, basic format
239 * (pattern <tt>hhmmss</tt>) or Coordinated Universal Time, basic format
240 * (pattern <tt>hhmmssZ</tt>)
241 * </ul>
242 * The <code>null</code> string argument is only valid if the format is
243 * string or XML.
244 *
245 * @param value the string, XML, date or time value to set
246 * @param format the format of the <code>DmtData</code> instance to be
247 * created, must be one of the formats specified above
248 * @throws IllegalArgumentException if <code>format</code> is not one of
249 * the allowed formats, or <code>value</code> is not a valid
250 * string for the given format
251 * @throws NullPointerException if a date or time is constructed and
252 * <code>value</code> is <code>null</code>
253 */
254 public DmtData(String value, int format) {
255 switch (format) {
256 case FORMAT_DATE:
257 checkDateFormat(value);
258 break;
259 case FORMAT_TIME:
260 checkTimeFormat(value);
261 break;
262 case FORMAT_STRING:
263 case FORMAT_XML:
264 break; // nothing to do, all string values are accepted
265 default:
266 throw new IllegalArgumentException(
267 "Invalid format in string constructor: " + format);
268 }
269 this.format = format;
270 this.formatName = getFormatName(format);
271 this.str = value;
272
273 this.integer = 0;
274 this.flt = 0;
275 this.bool = false;
276 this.bytes = null;
277 this.complex = null;
278 }
279
280 /**
281 * Create a <code>DmtData</code> instance of <code>int</code> format and
282 * set its value.
283 *
284 * @param integer the integer value to set
285 */
286 public DmtData(int integer) {
287 format = FORMAT_INTEGER;
288 formatName = getFormatName(format);
289 this.integer = integer;
290
291 this.str = null;
292 this.flt = 0;
293 this.bool = false;
294 this.bytes = null;
295 this.complex = null;
296 }
297
298 /**
299 * Create a <code>DmtData</code> instance of <code>float</code> format
300 * and set its value.
301 *
302 * @param flt the float value to set
303 */
304 public DmtData(float flt) {
305 format = FORMAT_FLOAT;
306 formatName = getFormatName(format);
307 this.flt = flt;
308
309 this.str = null;
310 this.integer = 0;
311 this.bool = false;
312 this.bytes = null;
313 this.complex = null;
314 }
315
316 /**
317 * Create a <code>DmtData</code> instance of <code>bool</code> format
318 * and set its value.
319 *
320 * @param bool the boolean value to set
321 */
322 public DmtData(boolean bool) {
323 format = FORMAT_BOOLEAN;
324 formatName = getFormatName(format);
325 this.bool = bool;
326
327 this.str = null;
328 this.integer = 0;
329 this.flt = 0;
330 this.bytes = null;
331 this.complex = null;
332 }
333
334 /**
335 * Create a <code>DmtData</code> instance of <code>bin</code> format and
336 * set its value.
337 *
338 * @param bytes the byte array to set, must not be <code>null</code>
339 * @throws NullPointerException if <code>bytes</code> is <code>null</code>
340 */
341 public DmtData(byte[] bytes) {
342 if (bytes == null)
343 throw new NullPointerException("Binary data argument is null.");
344
345 format = FORMAT_BINARY;
346 formatName = getFormatName(format);
347 this.bytes = bytes;
348
349 this.str = null;
350 this.integer = 0;
351 this.flt = 0;
352 this.bool = false;
353 this.complex = null;
354 }
355
356 /**
357 * Create a <code>DmtData</code> instance of <code>bin</code> or
358 * <code>b64</code> format and set its value. The chosen format is
359 * specified by the <code>base64</code> parameter.
360 *
361 * @param bytes the byte array to set, must not be <code>null</code>
362 * @param base64 if <code>true</code>, the new instance will have
363 * <code>b64</code> format, if <code>false</code>, it will have
364 * <code>bin</code> format
365 * @throws NullPointerException if <code>bytes</code> is <code>null</code>
366 */
367 public DmtData(byte[] bytes, boolean base64) {
368 if (bytes == null)
369 throw new NullPointerException("Binary data argument is null.");
370
371 format = base64 ? FORMAT_BASE64 : FORMAT_BINARY;
372 formatName = getFormatName(format);
373 this.bytes = bytes;
374
375 this.str = null;
376 this.integer = 0;
377 this.flt = 0;
378 this.bool = false;
379 this.complex = null;
380 }
381
382 /**
383 * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_STRING}
384 * format. The data is provided encoded as a <code>String</code>. The
385 * actual data format is specified in <code>formatName</code>. The
386 * encoding used in <code>data</code> must conform to this format.
387 *
388 * @param formatName the name of the format, must not be <code>null</code>
389 * @param data the data encoded according to the specified format, must not
390 * be <code>null</code>
391 * @throws NullPointerException if <code>formatName</code> or
392 * <code>data</code> is <code>null</code>
393 */
394 public DmtData(String formatName, String data) {
395 if(formatName == null)
396 throw new NullPointerException("Format name argument is null.");
397 if(data == null)
398 throw new NullPointerException("Data argument is null.");
399
400 format = FORMAT_RAW_STRING;
401 this.formatName = formatName;
402 this.str = data;
403
404 this.bytes = null;
405 this.integer = 0;
406 this.flt = 0;
407 this.bool = false;
408 this.complex = null;
409 }
410
411 /**
412 * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_BINARY}
413 * format. The data is provided encoded as binary. The actual data format is
414 * specified in <code>formatName</code>. The encoding used in
415 * <code>data</code> must conform to this format.
416 *
417 * @param formatName the name of the format, must not be <code>null</code>
418 * @param data the data encoded according to the specified format, must not
419 * be <code>null</code>
420 * @throws NullPointerException if <code>formatName</code> or
421 * <code>data</code> is <code>null</code>
422 */
423 public DmtData(String formatName, byte[] data) {
424 if(formatName == null)
425 throw new NullPointerException("Format name argument is null.");
426 if(data == null)
427 throw new NullPointerException("Data argument is null.");
428
429 format = FORMAT_RAW_BINARY;
430 this.formatName = formatName;
431 this.bytes = (byte[]) data.clone();
432
433 this.str = null;
434 this.integer = 0;
435 this.flt = 0;
436 this.bool = false;
437 this.complex = null;
438 }
439
440 /**
441 * Gets the value of a node with string (<code>chr</code>) format.
442 *
443 * @return the string value
444 * @throws DmtIllegalStateException if the format of the node is not string
445 */
446 public String getString() {
447 if (format == FORMAT_STRING)
448 return str;
449
450 throw new DmtIllegalStateException("DmtData value is not string.");
451 }
452
453 /**
454 * Gets the value of a node with date format. The returned date string is
455 * formatted according to the ISO 8601 definition of a calendar date in
456 * complete representation, basic format (pattern <tt>CCYYMMDD</tt>).
457 *
458 * @return the date value
459 * @throws DmtIllegalStateException if the format of the node is not date
460 */
461 public String getDate() {
462 if (format == FORMAT_DATE)
463 return str;
464
465 throw new DmtIllegalStateException("DmtData value is not date.");
466 }
467
468 /**
469 * Gets the value of a node with time format. The returned time string is
470 * formatted according to the ISO 8601 definition of the time of day. The
471 * exact format depends on the value the object was initialized with: either
472 * local time, complete representation, basic format (pattern
473 * <tt>hhmmss</tt>) or Coordinated Universal Time, basic format (pattern
474 * <tt>hhmmssZ</tt>).
475 *
476 * @return the time value
477 * @throws DmtIllegalStateException if the format of the node is not time
478 */
479 public String getTime() {
480 if (format == FORMAT_TIME)
481 return str;
482
483 throw new DmtIllegalStateException("DmtData value is not time.");
484 }
485
486 /**
487 * Gets the value of a node with <code>xml</code> format.
488 *
489 * @return the XML value
490 * @throws DmtIllegalStateException if the format of the node is not
491 * <code>xml</code>
492 */
493 public String getXml() {
494 if (format == FORMAT_XML)
495 return str;
496
497 throw new DmtIllegalStateException("DmtData value is not XML.");
498 }
499
500 /**
501 * Gets the value of a node with integer (<code>int</code>) format.
502 *
503 * @return the integer value
504 * @throws DmtIllegalStateException if the format of the node is not integer
505 */
506 public int getInt() {
507 if (format == FORMAT_INTEGER)
508 return integer;
509
510 throw new DmtIllegalStateException("DmtData value is not integer.");
511 }
512
513 /**
514 * Gets the value of a node with <code>float</code> format.
515 *
516 * @return the float value
517 * @throws DmtIllegalStateException if the format of the node is not
518 * <code>float</code>
519 */
520 public float getFloat() {
521 if (format == FORMAT_FLOAT)
522 return flt;
523
524 throw new DmtIllegalStateException("DmtData value is not float.");
525 }
526
527 /**
528 * Gets the value of a node with boolean (<code>bool</code>) format.
529 *
530 * @return the boolean value
531 * @throws DmtIllegalStateException if the format of the node is not boolean
532 */
533 public boolean getBoolean() {
534 if (format == FORMAT_BOOLEAN)
535 return bool;
536
537 throw new DmtIllegalStateException("DmtData value is not boolean.");
538 }
539
540 /**
541 * Gets the value of a node with binary (<code>bin</code>) format.
542 *
543 * @return the binary value
544 * @throws DmtIllegalStateException if the format of the node is not binary
545 */
546 public byte[] getBinary() {
547 if (format == FORMAT_BINARY) {
548 byte[] bytesCopy = new byte[bytes.length];
549 for (int i = 0; i < bytes.length; i++)
550 bytesCopy[i] = bytes[i];
551
552 return bytesCopy;
553 }
554
555 throw new DmtIllegalStateException("DmtData value is not a byte array.");
556 }
557
558 /**
559 * Gets the value of a node in raw binary ({@link #FORMAT_RAW_BINARY})
560 * format.
561 *
562 * @return the data value in raw binary format
563 * @throws DmtIllegalStateException if the format of the node is not raw binary
564 */
565 public byte[] getRawBinary() {
566 if (format == FORMAT_RAW_BINARY)
567 return (byte[]) bytes.clone();
568
569 throw new DmtIllegalStateException(
570 "DmtData value is not in raw binary format.");
571 }
572
573 /**
574 * Gets the value of a node in raw <code>String</code>
575 * ({@link #FORMAT_RAW_STRING}) format.
576 *
577 * @return the data value in raw <code>String</code> format
578 * @throws DmtIllegalStateException if the format of the node is not raw
579 * <code>String</code>
580 */
581 public String getRawString() {
582 if (format == FORMAT_RAW_STRING)
583 return str;
584
585 throw new DmtIllegalStateException(
586 "DmtData value is not in raw string format.");
587 }
588
589 /**
590 * Gets the value of a node with base 64 (<code>b64</code>) format.
591 *
592 * @return the binary value
593 * @throws DmtIllegalStateException if the format of the node is not base 64.
594 */
595 public byte[] getBase64() {
596 if (format == FORMAT_BASE64) {
597 byte[] bytesCopy = new byte[bytes.length];
598 for (int i = 0; i < bytes.length; i++)
599 bytesCopy[i] = bytes[i];
600
601 return bytesCopy;
602 }
603
604 throw new DmtIllegalStateException(
605 "DmtData value is not in base 64 format.");
606 }
607
608 /**
609 * Gets the complex data associated with an interior node (<code>node</code>
610 * format).
611 * <p>
612 * Certain interior nodes can support access to their subtrees through
613 * complex values, making it simpler to retrieve or update all leaf nodes in
614 * the subtree.
615 *
616 * @return the data object associated with an interior node
617 * @throws DmtIllegalStateException if the format of the data is not
618 * <code>node</code>
619 */
620 public Object getNode() {
621 if(format == FORMAT_NODE)
622 return complex;
623
624 throw new DmtIllegalStateException(
625 "DmtData does not contain interior node data.");
626 }
627
628 /**
629 * Get the node's format, expressed in terms of type constants defined in
630 * this class. Note that the 'format' term is a legacy from OMA DM, it is
631 * more customary to think of this as 'type'.
632 *
633 * @return the format of the node
634 */
635 public int getFormat() {
636 return format;
637 }
638
639 /**
640 * Returns the format of this <code>DmtData</code> as <code>String</code>.
641 * For the predefined data formats this is the OMA DM defined name of the
642 * format. For {@link #FORMAT_RAW_STRING} and {@link #FORMAT_RAW_BINARY}
643 * this is the format specified when the object was created.
644 *
645 * @return the format name as <code>String</code>
646 */
647 public String getFormatName() {
648 return formatName;
649 }
650
651 /**
652 * Get the size of the data. The returned value depends on the format of
653 * data in the node:
654 * <ul>
655 * <li>{@link #FORMAT_STRING}, {@link #FORMAT_XML}, {@link #FORMAT_BINARY},
656 * {@link #FORMAT_BASE64}, {@link #FORMAT_RAW_STRING}, and
657 * {@link #FORMAT_RAW_BINARY}: the length of the stored data, or 0 if
658 * the data is <code>null</code>
659 * <li>{@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4
660 * <li>{@link #FORMAT_DATE} and {@link #FORMAT_TIME}: the length of the
661 * date or time in its string representation
662 * <li>{@link #FORMAT_BOOLEAN}: 1
663 * <li>{@link #FORMAT_NODE}: -1 (unknown)
664 * <li>{@link #FORMAT_NULL}: 0
665 * </ul>
666 *
667 * @return the size of the data stored by this object
668 */
669 public int getSize() {
670 switch (format) {
671 case FORMAT_STRING:
672 case FORMAT_XML:
673 case FORMAT_DATE:
674 case FORMAT_TIME:
675 case FORMAT_RAW_STRING:
676 return str == null ? 0 : str.length();
677 case FORMAT_BINARY:
678 case FORMAT_BASE64:
679 case FORMAT_RAW_BINARY:
680 return bytes.length;
681 case FORMAT_INTEGER:
682 case FORMAT_FLOAT:
683 return 4;
684 case FORMAT_BOOLEAN:
685 return 1;
686 case FORMAT_NODE:
687 return -1;
688 case FORMAT_NULL:
689 return 0;
690 }
691
692 return 0; // never reached
693 }
694
695 /**
696 * Gets the string representation of the <code>DmtData</code>. This
697 * method works for all formats.
698 * <p>
699 * For string format data - including {@link #FORMAT_RAW_STRING} - the
700 * string value itself is returned, while for XML, date, time, integer,
701 * float, boolean and node formats the string form of the value is returned.
702 * Binary - including {@link #FORMAT_RAW_BINARY} - and base64 data is
703 * represented by two-digit hexadecimal numbers for each byte separated by
704 * spaces. The {@link #NULL_VALUE} data has the string form of
705 * "<code>null</code>". Data of string or XML format containing the Java
706 * <code>null</code> value is represented by an empty string.
707 *
708 * @return the string representation of this <code>DmtData</code> instance
709 */
710 public String toString() {
711 switch (format) {
712 case FORMAT_STRING:
713 case FORMAT_XML:
714 case FORMAT_DATE:
715 case FORMAT_TIME:
716 case FORMAT_RAW_STRING:
717 return str == null ? "" : str;
718 case FORMAT_INTEGER:
719 return String.valueOf(integer);
720 case FORMAT_FLOAT:
721 return String.valueOf(flt);
722 case FORMAT_BOOLEAN:
723 return String.valueOf(bool);
724 case FORMAT_BINARY:
725 case FORMAT_BASE64:
726 case FORMAT_RAW_BINARY:
727 return getHexDump(bytes);
728 case FORMAT_NODE:
729 return complex.toString();
730 case FORMAT_NULL:
731 return "null";
732 }
733
734 return null; // never reached
735 }
736
737 /**
738 * Compares the specified object with this <code>DmtData</code> instance.
739 * Two <code>DmtData</code> objects are considered equal if their format
740 * is the same, and their data (selected by the format) is equal.
741 * <p>
742 * In case of {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING}
743 * the textual name of the data format - as returned by
744 * {@link #getFormatName()} - must be equal as well.
745 *
746 * @param obj the object to compare with this <code>DmtData</code>
747 * @return true if the argument represents the same <code>DmtData</code>
748 * as this object
749 */
750 public boolean equals(Object obj) {
751 if (!(obj instanceof DmtData))
752 return false;
753
754 DmtData other = (DmtData) obj;
755
756 if (format != other.format)
757 return false;
758
759 switch (format) {
760 case FORMAT_STRING:
761 case FORMAT_XML:
762 case FORMAT_DATE:
763 case FORMAT_TIME:
764 return str == null ? other.str == null : str.equals(other.str);
765 case FORMAT_INTEGER:
766 return integer == other.integer;
767 case FORMAT_FLOAT:
768 return flt == other.flt;
769 case FORMAT_BOOLEAN:
770 return bool == other.bool;
771 case FORMAT_BINARY:
772 case FORMAT_BASE64:
773 return Arrays.equals(bytes, other.bytes);
774 case FORMAT_NODE:
775 return complex.equals(other.complex);
776 case FORMAT_NULL:
777 return true;
778 case FORMAT_RAW_BINARY:
779 return formatName.equals(other.formatName)
780 && Arrays.equals(bytes, other.bytes);
781 case FORMAT_RAW_STRING:
782 // in this case str cannot be null
783 return formatName.equals(other.formatName) && str.equals(other.str);
784 }
785
786 return false; // never reached
787 }
788
789 /**
790 * Returns the hash code value for this <code>DmtData</code> instance. The
791 * hash code is calculated based on the data (selected by the format) of
792 * this object.
793 *
794 * @return the hash code value for this object
795 */
796 public int hashCode() {
797 switch (format) {
798 case FORMAT_STRING:
799 case FORMAT_XML:
800 case FORMAT_DATE:
801 case FORMAT_TIME:
802 case FORMAT_RAW_STRING:
803 return str == null ? 0 : str.hashCode();
804 case FORMAT_INTEGER:
805 return new Integer(integer).hashCode();
806 case FORMAT_FLOAT:
807 return new Float(flt).hashCode();
808 case FORMAT_BOOLEAN:
809 return new Boolean(bool).hashCode();
810 case FORMAT_BINARY:
811 case FORMAT_BASE64:
812 case FORMAT_RAW_BINARY:
813 return new String(bytes).hashCode();
814 case FORMAT_NODE:
815 return complex.hashCode();
816 case FORMAT_NULL:
817 return 0;
818 }
819
820 return 0; // never reached
821 }
822
823 private static void checkDateFormat(String value) {
824 if(value.length() != 8)
825 throw new IllegalArgumentException("Date string '" + value +
826 "' does not follow the format 'CCYYMMDD'.");
827
828 int year = checkNumber(value, "Date", 0, 4, 0, 9999);
829 int month = checkNumber(value, "Date", 4, 2, 1, 12);
830 int day = checkNumber(value, "Date", 6, 2, 1, 31);
831
832 // Date checking is not prepared for all special rules (for example
833 // historical leap years), production code could contain a full check.
834
835 // Day 31 is invalid for April, June, September and November
836 if((month == 4 || month == 6 || month == 9 || month == 11) && day == 31)
837 throw new IllegalArgumentException("Date string '" + value +
838 "' contains an invalid date.");
839
840 // February 29 is invalid except for leap years, Feb. 30-31 are invalid
841 if(month == 2 && day > 28 &&
842 !(day == 29 && year%4 == 0 && (year%100 != 0 || year%400 == 0)))
843 throw new IllegalArgumentException("Date string '" + value +
844 "' contains an invalid date.");
845 }
846
847 private static void checkTimeFormat(String value) {
848 if(value.length() > 0 && value.charAt(value.length()-1) == 'Z')
849 value = value.substring(0, value.length()-1);
850
851 if(value.length() != 6)
852 throw new IllegalArgumentException("Time string '" + value +
853 "' does not follow the format 'hhmmss' or 'hhmmssZ'.");
854
855 // Time checking is not prepared for all special rules (for example
856 // leap seconds), production code could contain a full check.
857
858 // if hour is 24, only 240000 should be allowed
859 checkNumber(value, "Time", 0, 2, 0, 24);
860 checkNumber(value, "Time", 2, 2, 0, 59);
861 checkNumber(value, "Time", 4, 2, 0, 59);
862
863 if(value.startsWith("24") && !value.startsWith("240000"))
864 throw new IllegalArgumentException("Time string is out of range.");
865 }
866
867 private static int checkNumber(String value, String name, int from,
868 int length, int min, int max) {
869 String part = value.substring(from, from+length);
870 int number;
871 try {
872 number = Integer.parseInt(part);
873 } catch(NumberFormatException e) {
874 throw new IllegalArgumentException(name + " string '" + value +
875 "' contains a non-numeric part.");
876 }
877 if(number < min || number > max)
878 throw new IllegalArgumentException("A segment of the " + name +
879 " string '" + value + "' is out of range.");
880
881 return number;
882 }
883
884 // character array of hexadecimal digits, used for printing binary data
885 private static char[] hex = "0123456789ABCDEF".toCharArray();
886
887 // generates a hexadecimal dump of the given binary data
888 private static String getHexDump(byte[] bytes) {
889 if (bytes.length == 0)
890 return "";
891
892 StringBuffer buf = new StringBuffer();
893 appendHexByte(buf, bytes[0]);
894 for (int i = 1; i < bytes.length; i++)
895 appendHexByte(buf.append(' '), bytes[i]);
896
897 return buf.toString();
898 }
899
900 private static void appendHexByte(StringBuffer buf, byte b) {
901 buf.append(hex[(b & 0xF0) >> 4]).append(hex[b & 0x0F]);
902 }
903
904 private static String getFormatName(int format) {
905 return (String) FORMAT_NAMES.get(new Integer(format));
906 }
907}