org.osgi.service.dmt.DmtData Maven / Gradle / Ivy
/*
* Copyright (c) OSGi Alliance (2004, 2013). 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 org.osgi.service.dmt;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
/**
* 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} interface.
*
* Different constructors are available to create nodes with different formats.
* Nodes of {@code 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 {@code String}. The application is responsible for the proper
* encoding of the data according to the specified format.
*
* @Immutable
* @author $Id: 8e6d3187b802cdc3e5ee7cc3dce841797479dd93 $
*/
public final class DmtData {
/**
* The node holds an OMA DM {@code int} value.
*/
public static final int FORMAT_INTEGER = 0x0001;
/**
* The node holds an OMA DM {@code float} value.
*/
public static final int FORMAT_FLOAT = 0x0002;
/**
* The node holds an OMA DM {@code chr} value.
*/
public static final int FORMAT_STRING = 0x0004;
/**
* The node holds an OMA DM {@code bool} value.
*/
public static final int FORMAT_BOOLEAN = 0x0008;
/**
* The node holds an OMA DM {@code date} value.
*/
public static final int FORMAT_DATE = 0x0010;
/**
* The node holds an OMA DM {@code time} value.
*/
public static final int FORMAT_TIME = 0x0020;
/**
* The node holds an OMA DM {@code bin} value. The value of the node
* corresponds to the Java {@code byte[]} type.
*/
public static final int FORMAT_BINARY = 0x0040;
/**
* The node holds an OMA DM {@code b64} value. Like {@link #FORMAT_BINARY},
* this format is also represented by the Java {@code byte[]} type, the
* difference is only in the corresponding OMA DM format.
*
* This format does not affect the internal storage format of the data as
* {@code byte[]}. It is intended as a hint for the external representation
* of this data. Protocol Adapters can use this hint for their further
* processing.
*/
public static final int FORMAT_BASE64 = 0x0080;
/**
* The node holds an OMA DM {@code xml} value.
*/
public static final int FORMAT_XML = 0x0100;
/**
* The node holds an OMA DM {@code null} value. This corresponds to the Java
* {@code 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 {@code 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;
/**
* The node holds a long value. The {@link #getFormatName()} method can be
* used to get the actual format name.
*
* @since 2.0
*/
public static final int FORMAT_LONG = 0x2000;
/**
* The node holds a Date object. If the getTime() equals zero then the date
* time is not known. If the getTime() is negative it must be interpreted as
* a relative number of milliseconds.
*
* @since 2.0
*/
public static final int FORMAT_DATE_TIME = 0x4000;
// FORMAT_NAMES must be initialized before any constructor is called.
private static final Map FORMAT_NAMES = new HashMap();
static {
FORMAT_NAMES.put(new Integer(FORMAT_BASE64), "base64");
FORMAT_NAMES.put(new Integer(FORMAT_BINARY), "binary");
FORMAT_NAMES.put(new Integer(FORMAT_BOOLEAN), "boolean");
FORMAT_NAMES.put(new Integer(FORMAT_DATE), "date");
FORMAT_NAMES.put(new Integer(FORMAT_FLOAT), "float");
FORMAT_NAMES.put(new Integer(FORMAT_INTEGER), "integer");
FORMAT_NAMES.put(new Integer(FORMAT_NODE), "NODE");
FORMAT_NAMES.put(new Integer(FORMAT_NULL), "null");
FORMAT_NAMES.put(new Integer(FORMAT_STRING), "string");
FORMAT_NAMES.put(new Integer(FORMAT_TIME), "time");
FORMAT_NAMES.put(new Integer(FORMAT_XML), "xml");
FORMAT_NAMES.put(new Integer(FORMAT_LONG), "long");
FORMAT_NAMES.put(new Integer(FORMAT_DATE_TIME), "dateTime");
}
/**
* Constant instance representing a leaf node of {@code null} format.
*/
public static final DmtData NULL_VALUE = new DmtData();
/**
* Constant instance representing a boolean {@code true} value.
*
* @since 2.0
*/
public static final DmtData TRUE_VALUE = new DmtData(true);
/**
* Constant instance representing a boolean {@code false} value.
*
* @since 2.0
*/
public static final DmtData FALSE_VALUE = new DmtData(false);
private final int format;
private final Object value;
/*
* For format names, can be overridden by raw formats
*/
private String formatName;
/**
* Create a {@code DmtData} instance of {@code null} format. This
* constructor is private and used only to create the public
* {@link #NULL_VALUE} constant.
*/
private DmtData() {
this(null, FORMAT_NULL, null);
}
/**
* Create a {@code DmtData} instance of {@code chr} format with the given
* string value. The {@code null} string argument is valid.
*
* @param string the string value to set
*/
public DmtData(String string) {
this(string, FORMAT_STRING, null);
}
/**
* Create a {@code DmtData} instance of {@code dateTime} format with the
* given Date value. The given Date value must be a non-null {@code Date}
* object.
*
* @param date the Date object to set
*/
public DmtData(Date date) {
if (date == null)
throw new NullPointerException("The date argument is null.");
this.format = FORMAT_DATE_TIME;
this.value = date;
}
/**
* Create a {@code DmtData} instance of {@code 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-{@code 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;
value = complex;
}
/**
* Create a {@code 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 parsable to an ISO 8601 calendar
* date in complete representation, basic format (pattern {@code CCYYMMDD})
* - {@link #FORMAT_TIME} - value must be parsable to an ISO 8601 time of
* day in either local time, complete representation, basic format (pattern
* {@code hhmmss}) or Coordinated Universal Time, basic format (pattern
* {@code hhmmssZ})
*
* * The {@code 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 {@code DmtData} instance to be created,
* must be one of the formats specified above
* @throws IllegalArgumentException if {@code format} is not one of the
* allowed formats, or {@code value} is not a valid string for the
* given format
* @throws NullPointerException if a string, XML, date, or time is
* constructed and {@code value} is {@code null}
*/
public DmtData(String value, int format) {
this(value, format, null);
if (format == FORMAT_STRING || format == FORMAT_XML || format == FORMAT_DATE || format == FORMAT_TIME)
return;
throw new IllegalArgumentException("Wrong format for DmtData(String,int), format must be one of FORMAT_STRING, FORMAT_XML, FORMAT_DATE, or FORMAT_TIME");
}
/**
* Create a {@code DmtData} instance of {@code int} format and set its
* value.
*
* @param integer the integer value to set
*/
public DmtData(int integer) {
this(new Integer(integer), FORMAT_INTEGER, null);
}
/**
* Create a {@code DmtData} instance of {@code float} format and set its
* value.
*
* @param flt the float value to set
*/
public DmtData(float flt) {
this(new Float(flt), FORMAT_FLOAT, null);
}
/**
* Create a {@code DmtData} instance of {@code long} format and set its
* value.
*
* @param lng the long value to set
* @since 2.0
*/
public DmtData(long lng) {
this(new Long(lng), FORMAT_LONG, null);
}
/**
* Create a {@code DmtData} instance of {@code bool} format and set its
* value.
*
* @param bool the boolean value to set
*/
public DmtData(boolean bool) {
this(Boolean.valueOf(bool), FORMAT_BOOLEAN, null);
}
/**
* Create a {@code DmtData} instance of {@code bin} format and set its
* value.
*
* @param bytes the byte array to set, must not be {@code null}
* @throws NullPointerException if {@code bytes} is {@code null}
*/
public DmtData(byte[] bytes) {
this(bytes, FORMAT_BINARY, null);
}
/**
* Create a {@code DmtData} instance of {@code bin} or {@code b64} format
* and set its value. The chosen format is specified by the {@code base64}
* parameter.
*
* @param bytes the byte array to set, must not be {@code null}
* @param base64 if {@code true}, the new instance will have {@code b64}
* format, if {@code false}, it will have {@code bin} format
* @throws NullPointerException if {@code bytes} is {@code null}
*/
public DmtData(byte[] bytes, boolean base64) {
this(bytes, base64 ? FORMAT_BASE64 : FORMAT_BINARY, null);
}
/**
* Create a {@code DmtData} instance of the specified format and set its
* value based on the given {@code byte[]}. Only the following
* {@code byte[]} based formats can be created using this constructor:
*
*
* - {@link #FORMAT_BINARY}
* - {@link #FORMAT_BASE64}
*
*
*
* @param bytes the byte array to set, must not be {@code null}
* @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
* @throws NullPointerException if {@code bytes} is {@code null}
*/
public DmtData(byte[] bytes, int format) {
this(bytes, format, null);
if (format == FORMAT_BINARY || format == FORMAT_BASE64)
return;
throw new IllegalArgumentException("Invalid format for DmtDate(byte[],format), only FORMAT_BINARY and FORMAT_BASE64 are allowed");
}
/**
* Create a {@code DmtData} instance in {@link #FORMAT_RAW_STRING} format.
* The data is provided encoded as a {@code String}. The actual data format
* is specified in {@code formatName}. The encoding used in {@code data}
* must conform to this format.
*
* @param formatName the name of the format, must not be {@code null}
* @param data the data encoded according to the specified format, must not
* be {@code null}
* @throws NullPointerException if {@code formatName} or {@code data} is
* {@code null}
*/
public DmtData(String formatName, String data) {
this(data.toString(), FORMAT_RAW_STRING, formatName.toString());
}
/*
* Constructor used by all others.
*
* @param value The value
*
* @param format The format
*
* @param formatName The name of the format, can be null will then be set to
* the format name
*/
private DmtData(Object value, int format, String formatName) {
this.value = value;
this.format = format;
this.formatName = formatName;
validate();
}
/**
* Create a {@code DmtData} instance in {@link #FORMAT_RAW_BINARY} format.
* The data is provided encoded as binary. The actual data format is
* specified in {@code formatName}. The encoding used in {@code data} must
* conform to this format.
*
* @param formatName the name of the format, must not be {@code null}
* @param data the data encoded according to the specified format, must not
* be {@code null}
* @throws NullPointerException if {@code formatName} or {@code data} is
* {@code null}
*/
public DmtData(String formatName, byte[] data) {
this(data, FORMAT_RAW_BINARY, formatName);
if (formatName == null)
throw new NullPointerException("Format name argument is null.");
}
/*
* Validate the setup
*/
private void validate() {
Class c;
switch (format) {
case FORMAT_INTEGER :
c = Integer.class;
break;
case FORMAT_FLOAT :
c = Float.class;
break;
case FORMAT_TIME :
checkTimeFormat((String) value);
c = String.class;
break;
case FORMAT_DATE :
checkDateFormat((String) value);
c = String.class;
break;
case FORMAT_STRING :
case FORMAT_XML :
c = String.class;
if (value != null) {
break;
} else {
return;
}
case FORMAT_RAW_STRING :
c = String.class;
break;
case FORMAT_BOOLEAN :
c = Boolean.class;
break;
case FORMAT_BINARY :
case FORMAT_RAW_BINARY :
case FORMAT_BASE64 :
if (value != null) {
c = byte[].class;
break;
}
throw new NullPointerException("The bytes argument is null.");
case FORMAT_NULL :
if (value != null)
throw new IllegalArgumentException("Format is null but value is not ");
else
return;
case FORMAT_NODE :
c = Object.class;
break;
case FORMAT_LONG :
c = Long.class;
break;
default :
throw new IllegalArgumentException("Invalid format number for DmtData " + format);
}
if (!c.isInstance(value))
throw new IllegalArgumentException("Invalid type type for DmtData, expected " + c.getClass() + " but have " + value.getClass());
}
/**
* Gets the value of a node with string ({@code 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 (String) value;
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 {@code CCYYMMDD}).
*
* @return the date value
* @throws DmtIllegalStateException if the format of the node is not date
*/
public String getDate() {
if (format == FORMAT_DATE)
return (String) value;
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 {@code hhmmss}
* ) or Coordinated Universal Time, basic format (pattern {@code hhmmssZ}).
*
* @return the time value
* @throws DmtIllegalStateException if the format of the node is not time
*/
public String getTime() {
if (format == FORMAT_TIME)
return (String) value;
throw new DmtIllegalStateException("DmtData value is not time.");
}
/**
* Gets the value of a node with {@code dateTime} format.
*
* @return the Date value
* @throws DmtIllegalStateException if the format of the node is not time
* @since 2.0
*/
public Date getDateTime() {
if (format == FORMAT_DATE_TIME)
return (Date) value;
throw new DmtIllegalStateException("DmtData value is not dateTime.");
}
/**
* Gets the value of a node with {@code xml} format.
*
* @return the XML value
* @throws DmtIllegalStateException if the format of the node is not
* {@code xml}
*/
public String getXml() {
if (format == FORMAT_XML)
return (String) value;
throw new DmtIllegalStateException("DmtData value is not XML.");
}
/**
* Gets the value of a node with integer ({@code 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) value).intValue();
throw new DmtIllegalStateException("DmtData value is not integer.");
}
/**
* Gets the value of a node with long format.
*
* @return the long value
* @throws DmtIllegalStateException if the format of the node is not long
* @since 2.0
*/
public long getLong() {
if (format == FORMAT_LONG)
return ((Long) value).longValue();
throw new DmtIllegalStateException("DmtData value is not long.");
}
/**
* Gets the value of a node with {@code float} format.
*
* @return the float value
* @throws DmtIllegalStateException if the format of the node is not
* {@code float}
*/
public float getFloat() {
if (format == FORMAT_FLOAT)
return ((Float) value).floatValue();
throw new DmtIllegalStateException("DmtData value is not float.");
}
/**
* Gets the value of a node with boolean ({@code 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 ((Boolean) value).booleanValue();
throw new DmtIllegalStateException("DmtData value is not boolean.");
}
/**
* Gets the value of a node with binary ({@code bin}) format.
*
* @return the binary value
* @throws DmtIllegalStateException if the format of the node is not binary
*/
public byte[] getBinary() {
if (format == FORMAT_BINARY) {
return copyBytes();
}
throw new DmtIllegalStateException("DmtData value is not a byte array.");
}
/*
* Copy bytes to a new buffer
*
* @return the copied bytes
*/
private byte[] copyBytes() {
byte[] bytes = (byte[]) value;
byte[] bytesCopy = new byte[bytes.length];
System.arraycopy(bytes, 0, bytesCopy, 0, bytes.length);
return bytesCopy;
}
/**
* 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 copyBytes();
throw new DmtIllegalStateException("DmtData value is not in raw binary format.");
}
/**
* Gets the value of a node in raw {@code String} (
* {@link #FORMAT_RAW_STRING}) format.
*
* @return the data value in raw {@code String} format
* @throws DmtIllegalStateException if the format of the node is not raw
* {@code String}
*/
public String getRawString() {
if (format == FORMAT_RAW_STRING)
return (String) value;
throw new DmtIllegalStateException("DmtData value is not in raw string format.");
}
/**
* Gets the value of a node with base 64 ({@code 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) {
return copyBytes();
}
throw new DmtIllegalStateException("DmtData value is not in base 64 format.");
}
/**
* Gets the complex data associated with an interior node ({@code 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
* {@code node}
*/
public Object getNode() {
if (format == FORMAT_NODE)
return value;
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} as {@code 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 {@code String}
*/
public String getFormatName() {
if (formatName == null)
return getFormatName(format);
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 {@code null}
* - {@link #FORMAT_INTEGER} and {@link #FORMAT_FLOAT}: 4
* - {@link #FORMAT_LONG} and {@link #FORMAT_DATE_TIME}: 8
* - {@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 :
if (value != null) {
return ((String) value).length();
} else {
return 0;
}
case FORMAT_BINARY :
case FORMAT_BASE64 :
case FORMAT_RAW_BINARY :
return ((byte[]) value).length;
case FORMAT_INTEGER :
case FORMAT_FLOAT :
return 4;
case FORMAT_BOOLEAN :
return 1;
case FORMAT_NODE :
return -1;
case FORMAT_DATE_TIME :
case FORMAT_LONG :
return 8;
case FORMAT_NULL :
return 0;
}
return 0; // never reached
}
/**
* Gets the string representation of the {@code 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, long and node formats the string form of the value is
* returned. Binary - including {@link #FORMAT_RAW_BINARY} - 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}". Data of string or XML format containing the Java
* {@code null} value is represented by an empty string. DateTime data is
* formatted as {@code yyyy-MM-dd'T'HH:mm:SS'Z'}).
*
* @return the string representation of this {@code DmtData} instance
*/
public String toString() {
switch (format) {
case FORMAT_STRING :
case FORMAT_XML :
case FORMAT_DATE :
case FORMAT_TIME :
case FORMAT_RAW_STRING :
if (value != null) {
return (String) value;
} else {
return "";
}
case FORMAT_INTEGER :
case FORMAT_LONG :
case FORMAT_FLOAT :
case FORMAT_BOOLEAN :
case FORMAT_NODE :
return value.toString();
case FORMAT_BINARY :
case FORMAT_BASE64 :
case FORMAT_RAW_BINARY :
return getHexDump((byte[]) value);
case FORMAT_NULL :
return "null";
case FORMAT_DATE_TIME :
// unfortunately SimpleDateFormat is not thread safe :-(
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:SS'Z'");
sf.setTimeZone(TimeZone.getTimeZone("UTC"));
return sf.format(value);
default :
throw new IllegalStateException("Invalid format " + format);
}
}
/**
* Compares the specified object with this {@code DmtData} instance. Two
* {@code 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 {@code DmtData}
* @return true if the argument represents the same {@code 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;
if (formatName != null) {
if (!formatName.equals(other.formatName)) {
return false;
}
}
if (value == null)
if (other.value == null)
return true;
else
return false;
if (value.equals(other.value)) {
return true;
}
switch (format) {
case FORMAT_BINARY :
case FORMAT_BASE64 :
case FORMAT_RAW_BINARY :
return Arrays.equals((byte[]) value, (byte[]) other.value);
}
return false;
}
/**
* Returns the hash code value for this {@code 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() {
if (value == null)
return 42;
else
return value.hashCode();
}
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();
String del = "";
for (int i = 0; i < bytes.length; i++) {
appendHexByte(buf.append(del), bytes[i]);
del = " ";
}
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));
}
}