All Downloads are FREE. Search and download functionalities are using the official Maven repository.

info.dmtree.DmtData Maven / Gradle / Ivy

/*
 * Copyright (c) OSGi Alliance (2004, 2008). 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
 * DmtSession interface.
 * 

* Different constructors are available to create nodes with different formats. * Nodes of null format can be created using the static * {@link #NULL_VALUE} constant instance of this class. *

* {@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 String. The application is responsible for the * proper encoding of the data according to the specified format. * * @version $Revision: 5673 $ */ public final class DmtData { /** * The node holds an OMA DM int value. */ public static final int FORMAT_INTEGER = 0x0001; /** * The node holds an OMA DM float value. */ public static final int FORMAT_FLOAT = 0x0002; /** * The node holds an OMA DM chr value. */ public static final int FORMAT_STRING = 0x0004; /** * The node holds an OMA DM bool value. */ public static final int FORMAT_BOOLEAN = 0x0008; /** * The node holds an OMA DM date value. */ public static final int FORMAT_DATE = 0x0010; /** * The node holds an OMA DM time value. */ public static final int FORMAT_TIME = 0x0020; /** * The node holds an OMA DM bin value. The value of the node * corresponds to the Java byte[] type. */ public static final int FORMAT_BINARY = 0x0040; /** * The node holds an OMA DM b64 value. Like * {@link #FORMAT_BINARY}, this format is also represented by the Java * byte[] type, the difference is only in the corresponding * OMA DM format. */ public static final int FORMAT_BASE64 = 0x0080; /** * The node holds an OMA DM xml value. */ public static final int FORMAT_XML = 0x0100; /** * The node holds an OMA DM null value. This corresponds to * the Java null 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 String. 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 null 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 DmtData instance of null 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 DmtData instance of chr format * with the given string value. The null 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 DmtData instance of node format * with the given object value. The value represents complex data associated * with an interior node. *

* 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. *

* The given value must be a non-null 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 DmtData 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: *

    *
  • {@link #FORMAT_STRING} - value can be any string *
  • {@link #FORMAT_XML} - value must contain an XML fragment (the * validity is not checked by this constructor) *
  • {@link #FORMAT_DATE} - value must be parseable to an ISO 8601 * calendar date in complete representation, basic format (pattern * CCYYMMDD) *
  • {@link #FORMAT_TIME} - value must be parseable to an ISO 8601 time * of day in either local time, complete representation, basic format * (pattern hhmmss) or Coordinated Universal Time, basic format * (pattern hhmmssZ) *
* The null 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 DmtData instance to be * created, must be one of the formats specified above * @throws IllegalArgumentException if format is not one of * the allowed formats, or value is not a valid * string for the given format * @throws NullPointerException if a date or time is constructed and * value is null */ 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 DmtData instance of int 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 DmtData instance of float 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 DmtData instance of bool 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 DmtData instance of bin format and * set its value. * * @param bytes the byte array to set, must not be null * @throws NullPointerException if bytes is null */ 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 DmtData instance of bin or * b64 format and set its value. The chosen format is * specified by the base64 parameter. * * @param bytes the byte array to set, must not be null * @param base64 if true, the new instance will have * b64 format, if false, it will have * bin format * @throws NullPointerException if bytes is null */ 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 DmtData instance in {@link #FORMAT_RAW_STRING} * format. The data is provided encoded as a String. The * actual data format is specified in formatName. The * encoding used in data must conform to this format. * * @param formatName the name of the format, must not be null * @param data the data encoded according to the specified format, must not * be null * @throws NullPointerException if formatName or * data is null */ 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 DmtData instance in {@link #FORMAT_RAW_BINARY} * format. The data is provided encoded as binary. The actual data format is * specified in formatName. The encoding used in * data must conform to this format. * * @param formatName the name of the format, must not be null * @param data the data encoded according to the specified format, must not * be null * @throws NullPointerException if formatName or * data is null */ 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 (chr) 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 CCYYMMDD). * * @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 * hhmmss) or Coordinated Universal Time, basic format (pattern * hhmmssZ). * * @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 xml format. * * @return the XML value * @throws DmtIllegalStateException if the format of the node is not * xml */ 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 (int) 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 float format. * * @return the float value * @throws DmtIllegalStateException if the format of the node is not * float */ 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 (bool) 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 (bin) 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 String * ({@link #FORMAT_RAW_STRING}) format. * * @return the data value in raw String format * @throws DmtIllegalStateException if the format of the node is not raw * String */ 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 (b64) 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 (node * format). *

* 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 * node */ 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 DmtData as String. * 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 String */ public String getFormatName() { return formatName; } /** * Get the size of the data. The returned value depends on the format of * data in the node: *

    *
  • {@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 null *
  • {@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4 *
  • {@link #FORMAT_DATE} and {@link #FORMAT_TIME}: the length of the * date or time in its string representation *
  • {@link #FORMAT_BOOLEAN}: 1 *
  • {@link #FORMAT_NODE}: -1 (unknown) *
  • {@link #FORMAT_NULL}: 0 *
* * @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 DmtData. This * method works for all formats. *

* 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 * "null". Data of string or XML format containing the Java * null value is represented by an empty string. * * @return the string representation of this DmtData 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 DmtData instance. * Two DmtData objects are considered equal if their format * is the same, and their data (selected by the format) is equal. *

* 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 DmtData * @return true if the argument represents the same DmtData * 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 DmtData 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)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy