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));
}
}