FELIX-514 Updated compendium bundle to R4.1 OSGi API

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@681945 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java b/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
new file mode 100644
index 0000000..51c5cae
--- /dev/null
+++ b/org.osgi.compendium/src/main/java/info/dmtree/DmtData.java
@@ -0,0 +1,907 @@
+/*
+ * $Header: /cvshome/build/info.dmtree/src/info/dmtree/DmtData.java,v 1.8 2006/07/10 21:37:07 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package info.dmtree;
+
+import java.util.Arrays;
+import java.util.Hashtable;
+
+// Possible enhancements to this class:
+// * new constructors and get/set methods for b64, to access the encoded value
+// * new constructors and get/set methods for date/time, for more convenient 
+//   Java access
+/**
+ * An immutable data structure representing the contents of a leaf or interior
+ * node. This structure represents only the value and the format property of the
+ * node, all other properties (like MIME type) can be set and read using the
+ * <code>DmtSession</code> interface.
+ * <p>
+ * Different constructors are available to create nodes with different formats.
+ * Nodes of <code>null</code> format can be created using the static
+ * {@link #NULL_VALUE} constant instance of this class.
+ * <p>
+ * {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING} enable the support
+ * of future data formats. When using these formats, the actual format name is
+ * specified as a <code>String</code>. The application is responsible for the
+ * proper encoding of the data according to the specified format.
+ */
+public final class DmtData {
+
+    /**
+     * The node holds an OMA DM <code>int</code> value.
+     */
+    public static final int FORMAT_INTEGER = 0x0001;
+
+    /**
+     * The node holds an OMA DM <code>float</code> value.
+     */
+    public static final int FORMAT_FLOAT = 0x0002;
+
+    /**
+     * The node holds an OMA DM <code>chr</code> value.
+     */
+    public static final int FORMAT_STRING = 0x0004;
+
+    /**
+     * The node holds an OMA DM <code>bool</code> value.
+     */
+    public static final int FORMAT_BOOLEAN = 0x0008;
+
+    /**
+     * The node holds an OMA DM <code>date</code> value.
+     */
+    public static final int FORMAT_DATE = 0x0010;
+
+    /**
+     * The node holds an OMA DM <code>time</code> value.
+     */
+    public static final int FORMAT_TIME = 0x0020;
+
+    /**
+     * The node holds an OMA DM <code>bin</code> value. The value of the node
+     * corresponds to the Java <code>byte[]</code> type.
+     */
+    public static final int FORMAT_BINARY = 0x0040;
+
+    /**
+     * The node holds an OMA DM <code>b64</code> value. Like
+     * {@link #FORMAT_BINARY}, this format is also represented by the Java
+     * <code>byte[]</code> type, the difference is only in the corresponding
+     * OMA DM format.
+     */
+    public static final int FORMAT_BASE64 = 0x0080;
+
+    /**
+     * The node holds an OMA DM <code>xml</code> value.
+     */
+    public static final int FORMAT_XML = 0x0100;
+
+    /**
+     * The node holds an OMA DM <code>null</code> value. This corresponds to
+     * the Java <code>null</code> type.
+     */
+    public static final int FORMAT_NULL = 0x0200;
+
+    /**
+     * Format specifier of an internal node. An interior node can hold a Java
+     * object as value (see {@link DmtData#DmtData(Object)} and
+     * {@link DmtData#getNode()}). This value can be used by Java programs that
+     * know a specific URI understands the associated Java type. This type is
+     * further used as a return value of the {@link MetaNode#getFormat} method
+     * for interior nodes.
+     */
+    public static final int FORMAT_NODE = 0x0400;
+    
+    /**
+     * The node holds raw protocol data encoded as <code>String</code>. The
+     * {@link #getFormatName()} method can be used to get the actual format
+     * name.
+     */
+    public static final int FORMAT_RAW_STRING = 0x0800;
+
+    /**
+     * The node holds raw protocol data encoded in binary format. The
+     * {@link #getFormatName()} method can be used to get the actual format
+     * name.
+     */
+    public static final int FORMAT_RAW_BINARY = 0x1000;
+
+    
+    private static final Hashtable FORMAT_NAMES = new Hashtable();
+    
+    static {
+        FORMAT_NAMES.put(new Integer(FORMAT_BASE64),    "b64");
+        FORMAT_NAMES.put(new Integer(FORMAT_BINARY),    "bin");
+        FORMAT_NAMES.put(new Integer(FORMAT_BOOLEAN),   "bool");
+        FORMAT_NAMES.put(new Integer(FORMAT_DATE),      "date");
+        FORMAT_NAMES.put(new Integer(FORMAT_FLOAT),     "float");
+        FORMAT_NAMES.put(new Integer(FORMAT_INTEGER),   "int");
+        FORMAT_NAMES.put(new Integer(FORMAT_NODE),      "node");
+        FORMAT_NAMES.put(new Integer(FORMAT_NULL),      "null");
+        FORMAT_NAMES.put(new Integer(FORMAT_STRING),    "chr");
+        FORMAT_NAMES.put(new Integer(FORMAT_TIME),      "time");
+        FORMAT_NAMES.put(new Integer(FORMAT_XML),       "xml");
+    }
+
+    /**
+     * Constant instance representing a leaf node of <code>null</code> format.
+     */
+    public static final DmtData NULL_VALUE = new DmtData();
+        // FORMAT_NAMES must be initialized by the time the constr. is called 
+    
+    private final String str;
+
+    private final int integer;
+
+    private final float flt;
+
+    private final boolean bool;
+
+    private final byte[] bytes;
+
+    private final int format;
+    
+    private final String formatName;
+
+    private final Object complex;
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>null</code> format.
+     * This constructor is private and used only to create the public
+     * {@link #NULL_VALUE} constant.
+     */
+    private DmtData() {
+        format = FORMAT_NULL;
+        formatName = getFormatName(format);
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>chr</code> format
+     * with the given string value. The <code>null</code> string argument is
+     * valid.
+     * 
+     * @param str the string value to set
+     */
+    public DmtData(String str) {
+        format = FORMAT_STRING;
+        formatName = getFormatName(format);
+        this.str = str;
+
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>node</code> format
+     * with the given object value. The value represents complex data associated
+     * with an interior node.
+     * <p>
+     * Certain interior nodes can support access to their subtrees through such
+     * complex values, making it simpler to retrieve or update all leaf nodes in
+     * a subtree.
+     * <p>
+     * The given value must be a non-<code>null</code> immutable object.
+     * 
+     * @param complex the complex data object to set
+     */
+    public DmtData(Object complex) {
+        if(complex == null)
+            throw new NullPointerException("Complex data argument is null.");
+
+        format = FORMAT_NODE;
+        formatName = getFormatName(format);
+        this.complex = complex;
+
+        this.str     = null;
+        this.integer = 0;
+        this.flt     = 0;
+        this.bool    = false;
+        this.bytes   = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of the specified format and set
+     * its value based on the given string. Only the following string-based
+     * formats can be created using this constructor:
+     * <ul>
+     * <li>{@link #FORMAT_STRING} - value can be any string
+     * <li>{@link #FORMAT_XML} - value must contain an XML fragment (the
+     *     validity is not checked by this constructor)
+     * <li>{@link #FORMAT_DATE} - value must be parseable to an ISO 8601
+     *     calendar date in complete representation, basic format (pattern
+     * <tt>CCYYMMDD</tt>)
+     * <li>{@link #FORMAT_TIME} - value must be parseable to an ISO 8601 time
+     *     of day in either local time, complete representation, basic format
+     *     (pattern <tt>hhmmss</tt>) or Coordinated Universal Time, basic format
+     *     (pattern <tt>hhmmssZ</tt>)
+     * </ul>
+     * The <code>null</code> string argument is only valid if the format is
+     * string or XML.
+     * 
+     * @param value the string, XML, date or time value to set
+     * @param format the format of the <code>DmtData</code> instance to be
+     *        created, must be one of the formats specified above
+     * @throws IllegalArgumentException if <code>format</code> is not one of
+     *         the allowed formats, or <code>value</code> is not a valid
+     *         string for the given format
+     * @throws NullPointerException if a date or time is constructed and
+     *         <code>value</code> is <code>null</code>
+     */
+    public DmtData(String value, int format) {
+        switch (format) {
+        case FORMAT_DATE:
+            checkDateFormat(value);
+            break;
+        case FORMAT_TIME:
+            checkTimeFormat(value);
+            break;
+        case FORMAT_STRING:
+        case FORMAT_XML:
+            break; // nothing to do, all string values are accepted
+        default:
+            throw new IllegalArgumentException(
+                    "Invalid format in string constructor: " + format);
+        }
+        this.format = format;
+        this.formatName = getFormatName(format);
+        this.str = value;
+
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>int</code> format and
+     * set its value.
+     * 
+     * @param integer the integer value to set
+     */
+    public DmtData(int integer) {
+        format = FORMAT_INTEGER;
+        formatName = getFormatName(format);
+        this.integer = integer;
+
+        this.str = null;
+        this.flt = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>float</code> format
+     * and set its value.
+     * 
+     * @param flt the float value to set
+     */
+    public DmtData(float flt) {
+        format = FORMAT_FLOAT;
+        formatName = getFormatName(format);
+        this.flt = flt;
+
+        this.str = null;
+        this.integer = 0;
+        this.bool = false;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bool</code> format
+     * and set its value.
+     * 
+     * @param bool the boolean value to set
+     */
+    public DmtData(boolean bool) {
+        format = FORMAT_BOOLEAN;
+        formatName = getFormatName(format);
+        this.bool = bool;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bytes = null;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bin</code> format and
+     * set its value.
+     * 
+     * @param bytes the byte array to set, must not be <code>null</code>
+     * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+     */
+    public DmtData(byte[] bytes) {
+        if (bytes == null)
+            throw new NullPointerException("Binary data argument is null.");
+
+        format = FORMAT_BINARY;
+        formatName = getFormatName(format);
+        this.bytes = bytes;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance of <code>bin</code> or
+     * <code>b64</code> format and set its value. The chosen format is
+     * specified by the <code>base64</code> parameter.
+     * 
+     * @param bytes the byte array to set, must not be <code>null</code>
+     * @param base64 if <code>true</code>, the new instance will have
+     *        <code>b64</code> format, if <code>false</code>, it will have
+     *        <code>bin</code> format
+     * @throws NullPointerException if <code>bytes</code> is <code>null</code>
+     */
+    public DmtData(byte[] bytes, boolean base64) {
+        if (bytes == null)
+            throw new NullPointerException("Binary data argument is null.");
+
+        format = base64 ? FORMAT_BASE64 : FORMAT_BINARY;
+        formatName = getFormatName(format);
+        this.bytes = bytes;
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+
+    /**
+     * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_STRING}
+     * format. The data is provided encoded as a <code>String</code>. The
+     * actual data format is specified in <code>formatName</code>. The
+     * encoding used in <code>data</code> must conform to this format.
+     * 
+     * @param formatName the name of the format, must not be <code>null</code>
+     * @param data the data encoded according to the specified format, must not
+     *        be <code>null</code>
+     * @throws NullPointerException if <code>formatName</code> or
+     *         <code>data</code> is <code>null</code>
+     */
+    public DmtData(String formatName, String data) {
+        if(formatName == null)
+            throw new NullPointerException("Format name argument is null.");
+        if(data == null)
+            throw new NullPointerException("Data argument is null.");
+        
+        format = FORMAT_RAW_STRING;
+        this.formatName = formatName;
+        this.str = data;
+
+        this.bytes = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+    
+    /**
+     * Create a <code>DmtData</code> instance in {@link #FORMAT_RAW_BINARY}
+     * format. The data is provided encoded as binary. The actual data format is
+     * specified in <code>formatName</code>. The encoding used in
+     * <code>data</code> must conform to this format.
+     * 
+     * @param formatName the name of the format, must not be <code>null</code>
+     * @param data the data encoded according to the specified format, must not
+     *        be <code>null</code>
+     * @throws NullPointerException if <code>formatName</code> or
+     *         <code>data</code> is <code>null</code>
+     */
+    public DmtData(String formatName, byte[] data) {
+        if(formatName == null)
+            throw new NullPointerException("Format name argument is null.");
+        if(data == null)
+            throw new NullPointerException("Data argument is null.");
+
+        format = FORMAT_RAW_BINARY;
+        this.formatName = formatName;
+        this.bytes = (byte[]) data.clone();
+
+        this.str = null;
+        this.integer = 0;
+        this.flt = 0;
+        this.bool = false;
+        this.complex = null;
+    }
+    
+    /**
+     * Gets the value of a node with string (<code>chr</code>) format.
+     * 
+     * @return the string value
+     * @throws DmtIllegalStateException if the format of the node is not string
+     */
+    public String getString() {
+        if (format == FORMAT_STRING)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not string.");
+    }
+
+    /**
+     * Gets the value of a node with date format. The returned date string is
+     * formatted according to the ISO 8601 definition of a calendar date in
+     * complete representation, basic format (pattern <tt>CCYYMMDD</tt>).
+     * 
+     * @return the date value
+     * @throws DmtIllegalStateException if the format of the node is not date
+     */
+    public String getDate() {
+        if (format == FORMAT_DATE)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not date.");
+    }
+
+    /**
+     * Gets the value of a node with time format. The returned time string is
+     * formatted according to the ISO 8601 definition of the time of day. The
+     * exact format depends on the value the object was initialized with: either
+     * local time, complete representation, basic format (pattern
+     * <tt>hhmmss</tt>) or Coordinated Universal Time, basic format (pattern
+     * <tt>hhmmssZ</tt>).
+     * 
+     * @return the time value
+     * @throws DmtIllegalStateException if the format of the node is not time
+     */
+    public String getTime() {
+        if (format == FORMAT_TIME)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not time.");
+    }
+
+    /**
+     * Gets the value of a node with <code>xml</code> format.
+     * 
+     * @return the XML value
+     * @throws DmtIllegalStateException if the format of the node is not
+     *         <code>xml</code>
+     */
+    public String getXml() {
+        if (format == FORMAT_XML)
+            return str;
+
+        throw new DmtIllegalStateException("DmtData value is not XML.");
+    }
+
+    /**
+     * Gets the value of a node with integer (<code>int</code>) format.
+     * 
+     * @return the integer value
+     * @throws DmtIllegalStateException if the format of the node is not integer
+     */
+    public int getInt() {
+        if (format == FORMAT_INTEGER)
+            return integer;
+
+        throw new DmtIllegalStateException("DmtData value is not integer.");
+    }
+
+    /**
+     * Gets the value of a node with <code>float</code> format.
+     * 
+     * @return the float value
+     * @throws DmtIllegalStateException if the format of the node is not
+     *         <code>float</code>
+     */
+    public float getFloat() {
+        if (format == FORMAT_FLOAT)
+            return flt;
+
+        throw new DmtIllegalStateException("DmtData value is not float.");
+    }
+
+    /**
+     * Gets the value of a node with boolean (<code>bool</code>) format.
+     * 
+     * @return the boolean value
+     * @throws DmtIllegalStateException if the format of the node is not boolean
+     */
+    public boolean getBoolean() {
+        if (format == FORMAT_BOOLEAN)
+            return bool;
+
+        throw new DmtIllegalStateException("DmtData value is not boolean.");
+    }
+
+    /**
+     * Gets the value of a node with binary (<code>bin</code>) format.
+     * 
+     * @return the binary value
+     * @throws DmtIllegalStateException if the format of the node is not binary
+     */
+    public byte[] getBinary() {
+        if (format == FORMAT_BINARY) {
+            byte[] bytesCopy = new byte[bytes.length];
+            for (int i = 0; i < bytes.length; i++)
+                bytesCopy[i] = bytes[i];
+
+            return bytesCopy;
+        }
+
+        throw new DmtIllegalStateException("DmtData value is not a byte array.");
+    }
+    
+    /**
+     * Gets the value of a node in raw binary ({@link #FORMAT_RAW_BINARY})
+     * format.
+     * 
+     * @return the data value in raw binary format
+     * @throws DmtIllegalStateException if the format of the node is not raw binary
+     */
+    public byte[] getRawBinary() {
+        if (format == FORMAT_RAW_BINARY)
+            return (byte[]) bytes.clone();
+        
+        throw new DmtIllegalStateException(
+                "DmtData value is not in raw binary format.");
+    }
+
+    /**
+     * Gets the value of a node in raw <code>String</code>
+     * ({@link #FORMAT_RAW_STRING}) format.
+     * 
+     * @return the data value in raw <code>String</code> format
+     * @throws DmtIllegalStateException if the format of the node is not raw
+     *     <code>String</code>
+    */
+    public String getRawString() {
+        if (format == FORMAT_RAW_STRING)
+            return str;
+        
+        throw new DmtIllegalStateException(
+                "DmtData value is not in raw string format.");
+    }
+
+    /**
+     * Gets the value of a node with base 64 (<code>b64</code>) format.
+     * 
+     * @return the binary value
+     * @throws DmtIllegalStateException if the format of the node is not base 64.
+     */
+    public byte[] getBase64() {
+        if (format == FORMAT_BASE64) {
+            byte[] bytesCopy = new byte[bytes.length];
+            for (int i = 0; i < bytes.length; i++)
+                bytesCopy[i] = bytes[i];
+
+            return bytesCopy;
+        }
+
+        throw new DmtIllegalStateException(
+                "DmtData value is not in base 64 format.");
+    }
+
+    /**
+     * Gets the complex data associated with an interior node (<code>node</code>
+     * format).
+     * <p>
+     * Certain interior nodes can support access to their subtrees through
+     * complex values, making it simpler to retrieve or update all leaf nodes in
+     * the subtree.
+     * 
+     * @return the data object associated with an interior node
+     * @throws DmtIllegalStateException if the format of the data is not 
+     *         <code>node</code>
+     */
+    public Object getNode() {
+        if(format == FORMAT_NODE)
+            return complex;
+
+        throw new DmtIllegalStateException(
+                "DmtData does not contain interior node data.");
+    }
+
+    /**
+     * Get the node's format, expressed in terms of type constants defined in
+     * this class. Note that the 'format' term is a legacy from OMA DM, it is
+     * more customary to think of this as 'type'.
+     * 
+     * @return the format of the node
+     */
+    public int getFormat() {
+        return format;
+    }
+
+    /**
+     * Returns the format of this <code>DmtData</code> as <code>String</code>.
+     * For the predefined data formats this is the OMA DM defined name of the
+     * format. For {@link #FORMAT_RAW_STRING} and {@link #FORMAT_RAW_BINARY}
+     * this is the format specified when the object was created.
+     * 
+     * @return the format name as <code>String</code>
+     */
+    public String getFormatName() {
+        return formatName;
+    }
+
+    /**
+     * Get the size of the data. The returned value depends on the format of
+     * data in the node:
+     * <ul>
+     * <li>{@link #FORMAT_STRING}, {@link #FORMAT_XML}, {@link #FORMAT_BINARY},
+     *     {@link #FORMAT_BASE64}, {@link #FORMAT_RAW_STRING}, and
+     *     {@link #FORMAT_RAW_BINARY}: the length of the stored data, or 0 if
+     *     the data is <code>null</code>
+     * <li>{@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4
+     * <li>{@link #FORMAT_DATE} and {@link #FORMAT_TIME}: the length of the
+     *     date or time in its string representation
+     * <li>{@link #FORMAT_BOOLEAN}: 1
+     * <li>{@link #FORMAT_NODE}: -1 (unknown)
+     * <li>{@link #FORMAT_NULL}: 0
+     * </ul>
+     * 
+     * @return the size of the data stored by this object
+     */
+    public int getSize() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? 0 : str.length();
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return bytes.length;
+        case FORMAT_INTEGER:
+        case FORMAT_FLOAT:
+            return 4;
+        case FORMAT_BOOLEAN:
+            return 1;
+        case FORMAT_NODE:
+            return -1;
+        case FORMAT_NULL:
+            return 0;
+        }
+
+        return 0; // never reached
+    }
+
+    /**
+     * Gets the string representation of the <code>DmtData</code>. This
+     * method works for all formats.
+     * <p>
+     * For string format data - including {@link #FORMAT_RAW_STRING} - the
+     * string value itself is returned, while for XML, date, time, integer,
+     * float, boolean and node formats the string form of the value is returned.
+     * Binary - including {@link #FORMAT_RAW_BINARY} - and base64 data is
+     * represented by two-digit hexadecimal numbers for each byte separated by
+     * spaces. The {@link #NULL_VALUE} data has the string form of
+     * "<code>null</code>". Data of string or XML format containing the Java
+     * <code>null</code> value is represented by an empty string.
+     * 
+     * @return the string representation of this <code>DmtData</code> instance
+     */
+    public String toString() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? "" : str;
+        case FORMAT_INTEGER:
+            return String.valueOf(integer);
+        case FORMAT_FLOAT:
+            return String.valueOf(flt);
+        case FORMAT_BOOLEAN:
+            return String.valueOf(bool);
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return getHexDump(bytes);
+        case FORMAT_NODE:
+            return complex.toString();
+        case FORMAT_NULL:
+            return "null";
+        }
+
+        return null; // never reached
+    }
+
+    /**
+     * Compares the specified object with this <code>DmtData</code> instance.
+     * Two <code>DmtData</code> objects are considered equal if their format
+     * is the same, and their data (selected by the format) is equal.
+     * <p>
+     * In case of {@link #FORMAT_RAW_BINARY} and {@link #FORMAT_RAW_STRING}
+     * the textual name of the data format - as returned by
+     * {@link #getFormatName()} - must be equal as well.
+     * 
+     * @param obj the object to compare with this <code>DmtData</code>
+     * @return true if the argument represents the same <code>DmtData</code>
+     *         as this object
+     */
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DmtData))
+            return false;
+
+        DmtData other = (DmtData) obj;
+
+        if (format != other.format)
+            return false;
+
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+            return str == null ? other.str == null : str.equals(other.str);
+        case FORMAT_INTEGER:
+            return integer == other.integer;
+        case FORMAT_FLOAT:
+            return flt == other.flt;
+        case FORMAT_BOOLEAN:
+            return bool == other.bool;
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+            return Arrays.equals(bytes, other.bytes);
+        case FORMAT_NODE:
+            return complex.equals(other.complex);
+        case FORMAT_NULL:
+            return true;
+        case FORMAT_RAW_BINARY:
+            return formatName.equals(other.formatName)
+                    && Arrays.equals(bytes, other.bytes);
+        case FORMAT_RAW_STRING:
+            // in this case str cannot be null
+            return formatName.equals(other.formatName) && str.equals(other.str);
+        }
+
+        return false; // never reached
+    }
+
+    /**
+     * Returns the hash code value for this <code>DmtData</code> instance. The
+     * hash code is calculated based on the data (selected by the format) of
+     * this object.
+     * 
+     * @return the hash code value for this object
+     */
+    public int hashCode() {
+        switch (format) {
+        case FORMAT_STRING:
+        case FORMAT_XML:
+        case FORMAT_DATE:
+        case FORMAT_TIME:
+        case FORMAT_RAW_STRING:
+            return str == null ? 0 : str.hashCode();
+        case FORMAT_INTEGER:
+            return new Integer(integer).hashCode();
+        case FORMAT_FLOAT:
+            return new Float(flt).hashCode();
+        case FORMAT_BOOLEAN:
+            return new Boolean(bool).hashCode();
+        case FORMAT_BINARY:
+        case FORMAT_BASE64:
+        case FORMAT_RAW_BINARY:
+            return new String(bytes).hashCode();
+        case FORMAT_NODE:
+            return complex.hashCode();
+        case FORMAT_NULL:
+            return 0;
+        }
+
+        return 0; // never reached
+    }
+    
+    private static void checkDateFormat(String value) {
+        if(value.length() != 8)
+            throw new IllegalArgumentException("Date string '" + value +
+                    "' does not follow the format 'CCYYMMDD'.");
+        
+        int year = checkNumber(value, "Date", 0, 4, 0, 9999);
+        int month = checkNumber(value, "Date", 4, 2, 1, 12);
+        int day = checkNumber(value, "Date", 6, 2, 1, 31);
+        
+        // Date checking is not prepared for all special rules (for example
+        // historical leap years), production code could contain a full check.
+        
+        // Day 31 is invalid for April, June, September and November
+        if((month == 4 || month == 6 || month == 9 || month == 11) && day == 31)
+        	throw new IllegalArgumentException("Date string '" + value +
+        			"' contains an invalid date.");
+        
+        // February 29 is invalid except for leap years, Feb. 30-31 are invalid
+        if(month == 2 && day > 28 &&
+        	!(day == 29 && year%4 == 0 && (year%100 != 0 || year%400 == 0)))
+        	throw new IllegalArgumentException("Date string '" + value +
+        			"' contains an invalid date.");
+    }
+    
+    private static void checkTimeFormat(String value) {
+        if(value.length() > 0 && value.charAt(value.length()-1) == 'Z')
+            value = value.substring(0, value.length()-1);
+        
+        if(value.length() != 6)
+            throw new IllegalArgumentException("Time string '" + value +
+                    "' does not follow the format 'hhmmss' or 'hhmmssZ'.");
+            
+        // Time checking is not prepared for all special rules (for example
+        // leap seconds), production code could contain a full check.
+        
+        // if hour is 24, only 240000 should be allowed
+        checkNumber(value, "Time", 0, 2, 0, 24);
+        checkNumber(value, "Time", 2, 2, 0, 59);
+        checkNumber(value, "Time", 4, 2, 0, 59);
+        
+        if(value.startsWith("24") && !value.startsWith("240000"))
+        	throw new IllegalArgumentException("Time string is out of range.");
+    }
+    
+    private static int checkNumber(String value, String name, int from,
+            int length, int min, int max) {
+        String part = value.substring(from, from+length);
+        int number;
+        try {
+            number = Integer.parseInt(part);
+        } catch(NumberFormatException e) {
+            throw new IllegalArgumentException(name + " string '" + value +
+                    "' contains a non-numeric part.");
+        }
+        if(number < min || number > max)
+            throw new IllegalArgumentException("A segment of the " + name +
+                    " string '" + value + "' is out of range.");
+        
+        return number;
+    }
+
+    // character array of hexadecimal digits, used for printing binary data
+    private static char[] hex = "0123456789ABCDEF".toCharArray();
+
+    // generates a hexadecimal dump of the given binary data
+    private static String getHexDump(byte[] bytes) {
+        if (bytes.length == 0)
+            return "";
+
+        StringBuffer buf = new StringBuffer();
+        appendHexByte(buf, bytes[0]);
+        for (int i = 1; i < bytes.length; i++)
+            appendHexByte(buf.append(' '), bytes[i]);
+
+        return buf.toString();
+    }
+
+    private static void appendHexByte(StringBuffer buf, byte b) {
+        buf.append(hex[(b & 0xF0) >> 4]).append(hex[b & 0x0F]);
+    }
+    
+    private static String getFormatName(int format) {
+        return (String) FORMAT_NAMES.get(new Integer(format));
+    }
+}