/** 
 * 
 * Copyright 2004 Protique Ltd
 * 
 * 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.activemq.message;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;

import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageFormatException;
import javax.jms.MessageNotWriteableException;

import org.activemq.io.util.ByteArray;
import org.activemq.io.util.ByteArrayCompression;
import org.activemq.io.util.MemoryManageable;
import org.activemq.service.MessageIdentity;
import org.activemq.util.IdGenerator;

/**
 * The <CODE>Message</CODE> interface is the root interface of all JMS
 * messages. It defines the message header and the <CODE>acknowledge</CODE>
 * method used for all messages.
 * <p/>
 * <P>Most message-oriented middleware (MOM) products treat messages as
 * lightweight entities that consist
 * of a header and a payload. The header contains fields used for message
 * routing and identification; the payload contains the application data
 * being sent.
 * <p/>
 * <P>Within this general form, the definition of a message varies
 * significantly across products. It would be quite difficult for the JMS API
 * to support all of these message models.
 * <p/>
 * <P>With this in mind, the JMS message model has the following goals:
 * <UL>
 * <LI>Provide a single, unified message API
 * <LI>Provide an API suitable for creating messages that match the
 * format used by provider-native messaging applications
 * <LI>Support the development of heterogeneous applications that span
 * operating systems, machine architectures, and computer languages
 * <LI>Support messages containing objects in the Java programming language
 * ("Java objects")
 * <LI>Support messages containing Extensible Markup Language (XML) pages
 * </UL>
 * <p/>
 * <P>JMS messages are composed of the following parts:
 * <UL>
 * <LI>Header - All messages support the same set of header fields.
 * Header fields contain values used by both clients and providers to
 * identify and route messages.
 * <LI>Properties - Each message contains a built-in facility for supporting
 * application-defined property values. Properties provide an efficient
 * mechanism for supporting application-defined message filtering.
 * <LI>Body - The JMS API defines several types of message body, which cover
 * the majority of messaging styles currently in use.
 * </UL>
 * <p/>
 * <H4>Message Bodies</H4>
 * <p/>
 * <P>The JMS API defines five types of message body:
 * <UL>
 * <LI>Stream - A <CODE>StreamMessage</CODE> object's message body contains
 * a stream of primitive values in the Java programming
 * language ("Java primitives"). It is filled and read sequentially.
 * <LI>Map - A <CODE>MapMessage</CODE> object's message body contains a set
 * of name-value pairs, where names are <CODE>String</CODE>
 * objects, and values are Java primitives. The entries can be accessed
 * sequentially or randomly by name. The order of the entries is
 * undefined.
 * <LI>Text - A <CODE>TextMessage</CODE> object's message body contains a
 * <CODE>java.lang.String</CODE> object. This message type can be used
 * to transport plain-text messages, and XML messages.
 * <LI>Object - An <CODE>ObjectMessage</CODE> object's message body contains
 * a <CODE>Serializable</CODE> Java object.
 * <LI>Bytes - A <CODE>BytesMessage</CODE> object's message body contains a
 * stream of uninterpreted bytes. This message type is for
 * literally encoding a body to match an existing message format. In
 * many cases, it is possible to use one of the other body types,
 * which are easier to use. Although the JMS API allows the use of
 * message properties with byte messages, they are typically not used,
 * since the inclusion of properties may affect the format.
 * </UL>
 * <p/>
 * <H4>Message Headers</H4>
 * <p/>
 * <P>The <CODE>JMSCorrelationID</CODE> header field is used for linking one
 * message with
 * another. It typically links a reply message with its requesting message.
 * <p/>
 * <P><CODE>JMSCorrelationID</CODE> can hold a provider-specific message ID,
 * an application-specific <CODE>String</CODE> object, or a provider-native
 * <CODE>byte[]</CODE> value.
 * <p/>
 * <H4>Message Properties</H4>
 * <p/>
 * <P>A <CODE>Message</CODE> object contains a built-in facility for supporting
 * application-defined property values. In effect, this provides a mechanism
 * for adding application-specific header fields to a message.
 * <p/>
 * <P>Properties allow an application, via message selectors, to have a JMS
 * provider select, or filter, messages on its behalf using
 * application-specific criteria.
 * <p/>
 * <P>Property names must obey the rules for a message selector identifier.
 * Property names must not be null, and must not be empty strings. If a property
 * name is set and it is either null or an empty string, an
 * <CODE>IllegalArgumentException</CODE> must be thrown.
 * <p/>
 * <P>Property values can be <CODE>boolean</CODE>, <CODE>byte</CODE>,
 * <CODE>short</CODE>, <CODE>int</CODE>, <CODE>long</CODE>, <CODE>float</CODE>,
 * <CODE>double</CODE>, and <CODE>String</CODE>.
 * <p/>
 * <P>Property values are set prior to sending a message. When a client
 * receives a message, its properties are in read-only mode. If a
 * client attempts to set properties at this point, a
 * <CODE>MessageNotWriteableException</CODE> is thrown. If
 * <CODE>clearProperties</CODE> is called, the properties can now be both
 * read from and written to. Note that header fields are distinct from
 * properties. Header fields are never in read-only mode.
 * <p/>
 * <P>A property value may duplicate a value in a message's body, or it may
 * not. Although JMS does not define a policy for what should or should not
 * be made a property, application developers should note that JMS providers
 * will likely handle data in a message's body more efficiently than data in
 * a message's properties. For best performance, applications should use
 * message properties only when they need to customize a message's header.
 * The primary reason for doing this is to support customized message
 * selection.
 * <p/>
 * <P>Message properties support the following conversion table. The marked
 * cases must be supported. The unmarked cases must throw a
 * <CODE>JMSException</CODE>. The <CODE>String</CODE>-to-primitive conversions
 * may throw a runtime exception if the
 * primitive's <CODE>valueOf</CODE> method does not accept the
 * <CODE>String</CODE> as a valid representation of the primitive.
 * <p/>
 * <P>A value written as the row type can be read as the column type.
 * <p/>
 * <PRE>
 * |        | boolean byte short int long float double String
 * |----------------------------------------------------------
 * |boolean |    X                                       X
 * |byte    |          X     X    X   X                  X
 * |short   |                X    X   X                  X
 * |int     |                     X   X                  X
 * |long    |                         X                  X
 * |float   |                               X     X      X
 * |double  |                                     X      X
 * |String  |    X     X     X    X   X     X     X      X
 * |----------------------------------------------------------
 * </PRE>
 * <p/>
 * <P>In addition to the type-specific set/get methods for properties, JMS
 * provides the <CODE>setObjectProperty</CODE> and
 * <CODE>getObjectProperty</CODE> methods. These support the same set of
 * property types using the objectified primitive values. Their purpose is
 * to allow the decision of property type to made at execution time rather
 * than at compile time. They support the same property value conversions.
 * <p/>
 * <P>The <CODE>setObjectProperty</CODE> method accepts values of class
 * <CODE>Boolean</CODE>, <CODE>Byte</CODE>, <CODE>Short</CODE>,
 * <CODE>Integer</CODE>, <CODE>Long</CODE>, <CODE>Float</CODE>,
 * <CODE>Double</CODE>, and <CODE>String</CODE>. An attempt
 * to use any other class must throw a <CODE>JMSException</CODE>.
 * <p/>
 * <P>The <CODE>getObjectProperty</CODE> method only returns values of class
 * <CODE>Boolean</CODE>, <CODE>Byte</CODE>, <CODE>Short</CODE>,
 * <CODE>Integer</CODE>, <CODE>Long</CODE>, <CODE>Float</CODE>,
 * <CODE>Double</CODE>, and <CODE>String</CODE>.
 * <p/>
 * <P>The order of property values is not defined. To iterate through a
 * message's property values, use <CODE>getPropertyNames</CODE> to retrieve
 * a property name enumeration and then use the various property get methods
 * to retrieve their values.
 * <p/>
 * <P>A message's properties are deleted by the <CODE>clearProperties</CODE>
 * method. This leaves the message with an empty set of properties.
 * <p/>
 * <P>Getting a property value for a name which has not been set returns a
 * null value. Only the <CODE>getStringProperty</CODE> and
 * <CODE>getObjectProperty</CODE> methods can return a null value.
 * Attempting to read a null value as a primitive type must be treated as
 * calling the primitive's corresponding <CODE>valueOf(String)</CODE>
 * conversion method with a null value.
 * <p/>
 * <P>The JMS API reserves the <CODE>JMSX</CODE> property name prefix for JMS
 * defined properties.
 * The full set of these properties is defined in the Java Message Service
 * specification. New JMS defined properties may be added in later versions
 * of the JMS API.  Support for these properties is optional. The
 * <CODE>String[] ConnectionMetaData.getJMSXPropertyNames</CODE> method
 * returns the names of the JMSX properties supported by a connection.
 * <p/>
 * <P>JMSX properties may be referenced in message selectors whether or not
 * they are supported by a connection. If they are not present in a
 * message, they are treated like any other absent property.
 * <p/>
 * <P>JMSX properties defined in the specification as "set by provider on
 * send" are available to both the producer and the consumers of the message.
 * JMSX properties defined in the specification as "set by provider on
 * receive" are available only to the consumers.
 * <p/>
 * <P><CODE>JMSXGroupID</CODE> and <CODE>JMSXGroupSeq</CODE> are standard
 * properties that clients
 * should use if they want to group messages. All providers must support them.
 * Unless specifically noted, the values and semantics of the JMSX properties
 * are undefined.
 * <p/>
 * <P>The JMS API reserves the <CODE>JMS_<I>vendor_name</I></CODE> property
 * name prefix for provider-specific properties. Each provider defines its own
 * value for <CODE><I>vendor_name</I></CODE>. This is the mechanism a JMS
 * provider uses to make its special per-message services available to a JMS
 * client.
 * <p/>
 * <P>The purpose of provider-specific properties is to provide special
 * features needed to integrate JMS clients with provider-native clients in a
 * single JMS application. They should not be used for messaging between JMS
 * clients.
 * <p/>
 * <H4>Provider Implementations of JMS Message Interfaces</H4>
 * <p/>
 * <P>The JMS API provides a set of message interfaces that define the JMS
 * message
 * model. It does not provide implementations of these interfaces.
 * <p/>
 * <P>Each JMS provider supplies a set of message factories with its
 * <CODE>Session</CODE> object for creating instances of messages. This allows
 * a provider to use message implementations tailored to its specific needs.
 * <p/>
 * <P>A provider must be prepared to accept message implementations that are
 * not its own. They may not be handled as efficiently as its own
 * implementation; however, they must be handled.
 * <p/>
 * <P>Note the following exception case when a provider is handling a foreign
 * message implementation. If the foreign message implementation contains a
 * <CODE>JMSReplyTo</CODE> header field that is set to a foreign destination
 * implementation, the provider is not required to handle or preserve the
 * value of this header field.
 * <p/>
 * <H4>Message Selectors</H4>
 * <p/>
 * <P>A JMS message selector allows a client to specify, by
 * header field references and property references, the
 * messages it is interested in. Only messages whose header
 * and property values
 * match the
 * selector are delivered. What it means for a message not to be delivered
 * depends on the <CODE>MessageConsumer</CODE> being used (see
 * {@link javax.jms.QueueReceiver QueueReceiver} and
 * {@link javax.jms.TopicSubscriber TopicSubscriber}).
 * <p/>
 * <P>Message selectors cannot reference message body values.
 * <p/>
 * <P>A message selector matches a message if the selector evaluates to
 * true when the message's header field values and property values are
 * substituted for their corresponding identifiers in the selector.
 * <p/>
 * <P>A message selector is a <CODE>String</CODE> whose syntax is based on a
 * subset of
 * the SQL92 conditional expression syntax. If the value of a message selector
 * is an empty string, the value is treated as a null and indicates that there
 * is no message selector for the message consumer.
 * <p/>
 * <P>The order of evaluation of a message selector is from left to right
 * within precedence level. Parentheses can be used to change this order.
 * <p/>
 * <P>Predefined selector literals and operator names are shown here in
 * uppercase; however, they are case insensitive.
 * <p/>
 * <P>A selector can contain:
 * <p/>
 * <UL>
 * <LI>Literals:
 * <UL>
 * <LI>A string literal is enclosed in single quotes, with a single quote
 * represented by doubled single quote; for example,
 * <CODE>'literal'</CODE> and <CODE>'literal''s'</CODE>. Like
 * string literals in the Java programming language, these use the
 * Unicode character encoding.
 * <LI>An exact numeric literal is a numeric value without a decimal
 * point, such as <CODE>57</CODE>, <CODE>-957</CODE>, and
 * <CODE>+62</CODE>; numbers in the range of <CODE>long</CODE> are
 * supported. Exact numeric literals use the integer literal
 * syntax of the Java programming language.
 * <LI>An approximate numeric literal is a numeric value in scientific
 * notation, such as <CODE>7E3</CODE> and <CODE>-57.9E2</CODE>, or a
 * numeric value with a decimal, such as <CODE>7.</CODE>,
 * <CODE>-95.7</CODE>, and <CODE>+6.2</CODE>; numbers in the range of
 * <CODE>double</CODE> are supported. Approximate literals use the
 * floating-point literal syntax of the Java programming language.
 * <LI>The boolean literals <CODE>TRUE</CODE> and <CODE>FALSE</CODE>.
 * </UL>
 * <LI>Identifiers:
 * <UL>
 * <LI>An identifier is an unlimited-length sequence of letters
 * and digits, the first of which must be a letter. A letter is any
 * character for which the method <CODE>Character.isJavaLetter</CODE>
 * returns true. This includes <CODE>'_'</CODE> and <CODE>'$'</CODE>.
 * A letter or digit is any character for which the method
 * <CODE>Character.isJavaLetterOrDigit</CODE> returns true.
 * <LI>Identifiers cannot be the names <CODE>NULL</CODE>,
 * <CODE>TRUE</CODE>, and <CODE>FALSE</CODE>.
 * <LI>Identifiers cannot be <CODE>NOT</CODE>, <CODE>AND</CODE>,
 * <CODE>OR</CODE>, <CODE>BETWEEN</CODE>, <CODE>LIKE</CODE>,
 * <CODE>IN</CODE>, <CODE>IS</CODE>, or <CODE>ESCAPE</CODE>.
 * <LI>Identifiers are either header field references or property
 * references.  The type of a property value in a message selector
 * corresponds to the type used to set the property. If a property
 * that does not exist in a message is referenced, its value is
 * <CODE>NULL</CODE>.
 * <LI>The conversions that apply to the get methods for properties do not
 * apply when a property is used in a message selector expression.
 * For example, suppose you set a property as a string value, as in the
 * following:
 * <PRE>myMessage.setStringProperty("NumberOfOrders", "2");</PRE>
 * The following expression in a message selector would evaluate to
 * false, because a string cannot be used in an arithmetic expression:
 * <PRE>"NumberOfOrders > 1"</PRE>
 * <LI>Identifiers are case-sensitive.
 * <LI>Message header field references are restricted to
 * <CODE>JMSDeliveryMode</CODE>, <CODE>JMSPriority</CODE>,
 * <CODE>JMSMessageID</CODE>, <CODE>JMSTimestamp</CODE>,
 * <CODE>JMSCorrelationID</CODE>, and <CODE>JMSType</CODE>.
 * <CODE>JMSMessageID</CODE>, <CODE>JMSCorrelationID</CODE>, and
 * <CODE>JMSType</CODE> values may be null and if so are treated as a
 * <CODE>NULL</CODE> value.
 * <LI>Any name beginning with <CODE>'JMSX'</CODE> is a JMS defined
 * property name.
 * <LI>Any name beginning with <CODE>'JMS_'</CODE> is a provider-specific
 * property name.
 * <LI>Any name that does not begin with <CODE>'JMS'</CODE> is an
 * application-specific property name.
 * </UL>
 * <LI>White space is the same as that defined for the Java programming
 * language: space, horizontal tab, form feed, and line terminator.
 * <LI>Expressions:
 * <UL>
 * <LI>A selector is a conditional expression; a selector that evaluates
 * to <CODE>true</CODE> matches; a selector that evaluates to
 * <CODE>false</CODE> or unknown does not match.
 * <LI>Arithmetic expressions are composed of themselves, arithmetic
 * operations, identifiers (whose value is treated as a numeric
 * literal), and numeric literals.
 * <LI>Conditional expressions are composed of themselves, comparison
 * operations, and logical operations.
 * </UL>
 * <LI>Standard bracketing <CODE>()</CODE> for ordering expression evaluation
 * is supported.
 * <LI>Logical operators in precedence order: <CODE>NOT</CODE>,
 * <CODE>AND</CODE>, <CODE>OR</CODE>
 * <LI>Comparison operators: <CODE>=</CODE>, <CODE>></CODE>, <CODE>>=</CODE>,
 * <CODE><</CODE>, <CODE><=</CODE>, <CODE><></CODE> (not equal)
 * <UL>
 * <LI>Only like type values can be compared. One exception is that it
 * is valid to compare exact numeric values and approximate numeric
 * values; the type conversion required is defined by the rules of
 * numeric promotion in the Java programming language. If the
 * comparison of non-like type values is attempted, the value of the
 * operation is false. If either of the type values evaluates to
 * <CODE>NULL</CODE>, the value of the expression is unknown.
 * <LI>String and boolean comparison is restricted to <CODE>=</CODE> and
 * <CODE><></CODE>. Two strings are equal
 * if and only if they contain the same sequence of characters.
 * </UL>
 * <LI>Arithmetic operators in precedence order:
 * <UL>
 * <LI><CODE>+</CODE>, <CODE>-</CODE> (unary)
 * <LI><CODE>*</CODE>, <CODE>/</CODE> (multiplication and division)
 * <LI><CODE>+</CODE>, <CODE>-</CODE> (addition and subtraction)
 * <LI>Arithmetic operations must use numeric promotion in the Java
 * programming language.
 * </UL>
 * <LI><CODE><I>arithmetic-expr1</I> [NOT] BETWEEN <I>arithmetic-expr2</I>
 * AND <I>arithmetic-expr3</I></CODE> (comparison operator)
 * <UL>
 * <LI><CODE>"age&nbsp;BETWEEN&nbsp;15&nbsp;AND&nbsp;19"</CODE> is
 * equivalent to
 * <CODE>"age&nbsp;>=&nbsp;15&nbsp;AND&nbsp;age&nbsp;<=&nbsp;19"</CODE>
 * <LI><CODE>"age&nbsp;NOT&nbsp;BETWEEN&nbsp;15&nbsp;AND&nbsp;19"</CODE>
 * is equivalent to
 * <CODE>"age&nbsp;<&nbsp;15&nbsp;OR&nbsp;age&nbsp;>&nbsp;19"</CODE>
 * </UL>
 * <LI><CODE><I>identifier</I> [NOT] IN (<I>string-literal1</I>,
 * <I>string-literal2</I>,...)</CODE> (comparison operator where
 * <CODE><I>identifier</I></CODE> has a <CODE>String</CODE> or
 * <CODE>NULL</CODE> value)
 * <UL>
 * <LI><CODE>"Country&nbsp;IN&nbsp;('&nbsp;UK',&nbsp;'US',&nbsp;'France')"</CODE>
 * is true for
 * <CODE>'UK'</CODE> and false for <CODE>'Peru'</CODE>; it is
 * equivalent to the expression
 * <CODE>"(Country&nbsp;=&nbsp;'&nbsp;UK')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;US')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;France')"</CODE>
 * <LI><CODE>"Country&nbsp;NOT&nbsp;IN&nbsp;('&nbsp;UK',&nbsp;'US',&nbsp;'France')"</CODE>
 * is false for <CODE>'UK'</CODE> and true for <CODE>'Peru'</CODE>; it
 * is equivalent to the expression
 * <CODE>"NOT&nbsp;((Country&nbsp;=&nbsp;'&nbsp;UK')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;US')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;France'))"</CODE>
 * <LI>If identifier of an <CODE>IN</CODE> or <CODE>NOT IN</CODE>
 * operation is <CODE>NULL</CODE>, the value of the operation is
 * unknown.
 * </UL>
 * <LI><CODE><I>identifier</I> [NOT] LIKE <I>pattern-value</I> [ESCAPE
 * <I>escape-character</I>]</CODE> (comparison operator, where
 * <CODE><I>identifier</I></CODE> has a <CODE>String</CODE> value;
 * <CODE><I>pattern-value</I></CODE> is a string literal where
 * <CODE>'_'</CODE> stands for any single character; <CODE>'%'</CODE>
 * stands for any sequence of characters, including the empty sequence;
 * and all other characters stand for themselves. The optional
 * <CODE><I>escape-character</I></CODE> is a single-character string
 * literal whose character is used to escape the special meaning of the
 * <CODE>'_'</CODE> and <CODE>'%'</CODE> in
 * <CODE><I>pattern-value</I></CODE>.)
 * <UL>
 * <LI><CODE>"phone&nbsp;LIKE&nbsp;'12%3'"</CODE> is true for
 * <CODE>'123'</CODE> or <CODE>'12993'</CODE> and false for
 * <CODE>'1234'</CODE>
 * <LI><CODE>"word&nbsp;LIKE&nbsp;'l_se'"</CODE> is true for
 * <CODE>'lose'</CODE> and false for <CODE>'loose'</CODE>
 * <LI><CODE>"underscored&nbsp;LIKE&nbsp;'\_%'&nbsp;ESCAPE&nbsp;'\'"</CODE>
 * is true for <CODE>'_foo'</CODE> and false for <CODE>'bar'</CODE>
 * <LI><CODE>"phone&nbsp;NOT&nbsp;LIKE&nbsp;'12%3'"</CODE> is false for
 * <CODE>'123'</CODE> or <CODE>'12993'</CODE> and true for
 * <CODE>'1234'</CODE>
 * <LI>If <CODE><I>identifier</I></CODE> of a <CODE>LIKE</CODE> or
 * <CODE>NOT LIKE</CODE> operation is <CODE>NULL</CODE>, the value
 * of the operation is unknown.
 * </UL>
 * <LI><CODE><I>identifier</I> IS NULL</CODE> (comparison operator that tests
 * for a null header field value or a missing property value)
 * <UL>
 * <LI><CODE>"prop_name&nbsp;IS&nbsp;NULL"</CODE>
 * </UL>
 * <LI><CODE><I>identifier</I> IS NOT NULL</CODE> (comparison operator that
 * tests for the existence of a non-null header field value or a property
 * value)
 * <UL>
 * <LI><CODE>"prop_name&nbsp;IS&nbsp;NOT&nbsp;NULL"</CODE>
 * </UL>
 * <p/>
 * <P>JMS providers are required to verify the syntactic correctness of a
 * message selector at the time it is presented. A method that provides a
 * syntactically incorrect selector must result in a <CODE>JMSException</CODE>.
 * JMS providers may also optionally provide some semantic checking at the time
 * the selector is presented. Not all semantic checking can be performed at
 * the time a message selector is presented, because property types are not known.
 * <p/>
 * <P>The following message selector selects messages with a message type
 * of car and color of blue and weight greater than 2500 pounds:
 * <p/>
 * <PRE>"JMSType&nbsp;=&nbsp;'car'&nbsp;AND&nbsp;color&nbsp;=&nbsp;'blue'&nbsp;AND&nbsp;weight&nbsp;>&nbsp;2500"</PRE>
 * <p/>
 * <H4>Null Values</H4>
 * <p/>
 * <P>As noted above, property values may be <CODE>NULL</CODE>. The evaluation
 * of selector expressions containing <CODE>NULL</CODE> values is defined by
 * SQL92 <CODE>NULL</CODE> semantics. A brief description of these semantics
 * is provided here.
 * <p/>
 * <P>SQL treats a <CODE>NULL</CODE> value as unknown. Comparison or arithmetic
 * with an unknown value always yields an unknown value.
 * <p/>
 * <P>The <CODE>IS NULL</CODE> and <CODE>IS NOT NULL</CODE> operators convert
 * an unknown value into the respective <CODE>TRUE</CODE> and
 * <CODE>FALSE</CODE> values.
 * <p/>
 * <P>The boolean operators use three-valued logic as defined by the
 * following tables:
 * <p/>
 * <P><B>The definition of the <CODE>AND</CODE> operator</B>
 * <p/>
 * <PRE>
 * | AND  |   T   |   F   |   U
 * +------+-------+-------+-------
 * |  T   |   T   |   F   |   U
 * |  F   |   F   |   F   |   F
 * |  U   |   U   |   F   |   U
 * +------+-------+-------+-------
 * </PRE>
 * <p/>
 * <P><B>The definition of the <CODE>OR</CODE> operator</B>
 * <p/>
 * <PRE>
 * | OR   |   T   |   F   |   U
 * +------+-------+-------+--------
 * |  T   |   T   |   T   |   T
 * |  F   |   T   |   F   |   U
 * |  U   |   T   |   U   |   U
 * +------+-------+-------+-------
 * </PRE>
 * <p/>
 * <P><B>The definition of the <CODE>NOT</CODE> operator</B>
 * <p/>
 * <PRE>
 * | NOT
 * +------+------
 * |  T   |   F
 * |  F   |   T
 * |  U   |   U
 * +------+-------
 * </PRE>
 * <p/>
 * <H4>Special Notes</H4>
 * <p/>
 * <P>When used in a message selector, the <CODE>JMSDeliveryMode</CODE> header
 * field is treated as having the values <CODE>'PERSISTENT'</CODE> and
 * <CODE>'NON_PERSISTENT'</CODE>.
 * <p/>
 * <P>Date and time values should use the standard <CODE>long</CODE>
 * millisecond value. When a date or time literal is included in a message
 * selector, it should be an integer literal for a millisecond value. The
 * standard way to produce millisecond values is to use
 * <CODE>java.util.Calendar</CODE>.
 * <p/>
 * <P>Although SQL supports fixed decimal comparison and arithmetic, JMS
 * message selectors do not. This is the reason for restricting exact
 * numeric literals to those without a decimal (and the addition of
 * numerics with a decimal as an alternate representation for
 * approximate numeric values).
 * <p/>
 * <P>SQL comments are not supported.
 *
 * @version $Revision: 1.1.1.1 $
 * @see javax.jms.MessageConsumer#receive()
 * @see javax.jms.MessageConsumer#receive(long)
 * @see javax.jms.MessageConsumer#receiveNoWait()
 * @see javax.jms.MessageListener#onMessage(Message)
 * @see javax.jms.BytesMessage
 * @see javax.jms.MapMessage
 * @see javax.jms.ObjectMessage
 * @see javax.jms.StreamMessage
 * @see javax.jms.TextMessage
 */

public class ActiveMQMessage extends AbstractPacket implements Message, Comparable, MemoryManageable {

    /**
     * The message producer's default delivery mode is <CODE>PERSISTENT</CODE>.
     *
     * @see DeliveryMode#PERSISTENT
     */
    static final int DEFAULT_DELIVERY_MODE = DeliveryMode.PERSISTENT;

    /**
     * The message producer's default priority is 4.
     */
    static final int DEFAULT_PRIORITY = 4;

    /**
     * The message producer's default time to live is unlimited; the message
     * never expires.
     */
    static final long DEFAULT_TIME_TO_LIVE = 0;

    /**
     * message property types
     */
    final static byte EOF = 2;
    final static byte BYTES = 3;
    final static byte STRING = 4;
    final static byte BOOLEAN = 5;
    final static byte CHAR = 6;
    final static byte BYTE = 7;
    final static byte SHORT = 8;
    final static byte INT = 9;
    final static byte LONG = 10;
    final static byte FLOAT = 11;
    final static byte DOUBLE = 12;
    final static byte NULL = 13;

    /**
     * Message flag indexes (used for writing/reading to/from a Stream
     */
    
    public static final int CORRELATION_INDEX = 2;
    public static final int TYPE_INDEX = 3;
    public static final int BROKER_NAME_INDEX = 4;
    public static final int CLUSTER_NAME_INDEX = 5;
    public static final int TRANSACTION_ID_INDEX = 6;
    public static final int REPLY_TO_INDEX = 7;
    public static final int TIMESTAMP_INDEX = 8;
    public static final int EXPIRATION_INDEX = 9;
    public static final int REDELIVERED_INDEX = 10;
    public static final int XA_TRANS_INDEX = 11;
    public static final int CID_INDEX = 12;
    public static final int PROPERTIES_INDEX = 13;
    public static final int DISPATCHED_FROM_DLQ_INDEX = 14;
    public static final int PAYLOAD_INDEX = 15;
    public static final int EXTERNAL_MESSAGE_ID_INDEX = 16;
    public static final int MESSAGE_PART_INDEX = 17;
    public static final int CACHED_VALUES_INDEX = 18;
    public static final int CACHED_DESTINATION_INDEX = 19;
    public static final int LONG_SEQUENCE_INDEX = 20;
    


    private static final String DELIVERY_COUNT_NAME = "JMSXDeliveryCount";
    /**
     * <code>readOnlyMessage</code> denotes if the message is read only
     */
    protected boolean readOnlyMessage;

    private String jmsMessageID;
    private String jmsClientID;
    private String jmsCorrelationID;
    private String producerKey;
    private ActiveMQDestination jmsDestination;
    private ActiveMQDestination jmsReplyTo;
    private int jmsDeliveryMode = DEFAULT_DELIVERY_MODE;
    private boolean jmsRedelivered;
    private String jmsType;
    private long jmsExpiration;
    private int jmsPriority = DEFAULT_PRIORITY;
    private long jmsTimestamp;
    private HashMap properties;
    private boolean readOnlyProperties;
    private String entryBrokerName;
    private String entryClusterName;
    private int[] consumerNos; //these are set by the broker, and only relevant to consuming connections
    private Object transactionId;
    private boolean xaTransacted;
    private String consumerIdentifier; //this is only used on the Client for acknowledging receipt of a message
    private boolean messageConsumed;//only used on the client - to denote if its been delivered and read
    private boolean transientConsumed;//only used on the client - to denote if its been delivered and read
    private long sequenceNumber;//the sequence for this message from the producerId
    private int deliveryCount = 1;//number of times the message has been delivered
    private boolean dispatchedFromDLQ;
    private MessageAcknowledge messageAcknowledge;
    private ByteArray bodyAsBytes;
    private MessageIdentity jmsMessageIdentity;
    private short messsageHandle;//refers to the id of the MessageProducer that sent the message
    private boolean externalMessageId;//is the messageId set from another JMS implementation ?
    private boolean messagePart;//is the message split into multiple packets
    private short numberOfParts;
    private short partNumber;
    private String parentMessageID;//if split into multiple parts - the 'real' or first messageId


    /**
     * Retrieve if a JMS Message type or not
     *
     * @return true if it is a JMS Message
     */
    public boolean isJMSMessage() {
        return true;
    }


    /**
     * @return pretty print of this Message
     */
    public String toString() {
        return super.toString() + " ActiveMQMessage{ " +
                ", jmsMessageID = " + jmsMessageID +
                ", bodyAsBytes = " + bodyAsBytes +
                ", readOnlyMessage = " + readOnlyMessage +
                ", jmsClientID = '" + jmsClientID + "' " +
                ", jmsCorrelationID = '" + jmsCorrelationID + "' " +
                ", jmsDestination = " + jmsDestination +
                ", jmsReplyTo = " + jmsReplyTo +
                ", jmsDeliveryMode = " + jmsDeliveryMode +
                ", jmsRedelivered = " + jmsRedelivered +
                ", jmsType = '" + jmsType + "' " +
                ", jmsExpiration = " + jmsExpiration +
                ", jmsPriority = " + jmsPriority +
                ", jmsTimestamp = " + jmsTimestamp +
                ", properties = " + properties +
                ", readOnlyProperties = " + readOnlyProperties +
                ", entryBrokerName = '" + entryBrokerName + "' " +
                ", entryClusterName = '" + entryClusterName + "' " +
                ", consumerNos = " + consumerNos +
                ", transactionId = '" + transactionId + "' " +
                ", xaTransacted = " + xaTransacted +
                ", consumerIdentifer = '" + consumerIdentifier + "' " +
                ", messageConsumed = " + messageConsumed +
                ", transientConsumed = " + transientConsumed +
                ", sequenceNumber = " + sequenceNumber +
                ", deliveryCount = " + deliveryCount +
                ", dispatchedFromDLQ = " + dispatchedFromDLQ +
                ", messageAcknowledge = " + messageAcknowledge +
                ", jmsMessageIdentity = " + jmsMessageIdentity +
                ", producerKey = " + producerKey + 
                " }";
    }


    /**
     * @return Returns the messageAcknowledge.
     */
    public MessageAcknowledge getMessageAcknowledge() {
        return messageAcknowledge;
    }

    /**
     * @param messageAcknowledge The messageAcknowledge to set.
     */
    public void setMessageAcknowledge(MessageAcknowledge messageAcknowledge) {
        this.messageAcknowledge = messageAcknowledge;
    }

    /**
     * Return the type of Packet
     *
     * @return integer representation of the type of Packet
     */

    public int getPacketType() {
        return ACTIVEMQ_MESSAGE;
    }


    /**
     * set the message readOnly
     *
     * @param value
     */
    public void setReadOnly(boolean value) {
        this.readOnlyProperties = value;
        this.readOnlyMessage = value;
    }

    /**
     * test to see if a particular Consumer at a Connection
     * is meant to receive this Message
     *
     * @param consumerNumber
     * @return true if a target
     */

    public boolean isConsumerTarget(int consumerNumber) {
        if (consumerNos != null) {
            for (int i = 0; i < consumerNos.length; i++) {
                if (consumerNos[i] == consumerNumber) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @return consumer Nos as a String
     */
    public String getConsumerNosAsString() {
        String result = "";
        if (consumerNos != null) {
            for (int i = 0; i < consumerNos.length; i++) {
                result += consumerNos[i] + ",";
            }
        }
        return result;
    }

    /**
     * @return true if the message is non-persistent or intended for a temporary destination
     */
    public boolean isTemporary() {
        return jmsDeliveryMode == DeliveryMode.NON_PERSISTENT ||
                (jmsDestination != null && jmsDestination.isTemporary());
    }

    /**
     * @return Returns hash code for this instance
     */

    public int hashCode() {
        return this.getJMSMessageID() != null ? this.getJMSMessageID().hashCode() : super.hashCode();
    }

    /**
     * Returns true if this instance is equivalent to obj
     *
     * @param obj the other instance to test
     * @return true/false
     */

    public boolean equals(Object obj) {
        boolean result = obj == this;
        if (!result && obj != null && obj instanceof ActiveMQMessage) {
            ActiveMQMessage other = (ActiveMQMessage) obj;
            //the call getJMSMessageID() will initialize the messageID
            //if it hasn't already been set
            result = this.getJMSMessageID() == other.getJMSMessageID();
            if (!result){
                if (this.jmsMessageID != null && this.jmsMessageID.length() > 0 ||
                        other.jmsMessageID != null && other.jmsMessageID.length() > 0){
                    if (this.jmsMessageID != null && other.jmsMessageID != null){
                        result = this.jmsMessageID.equals(other.jmsMessageID);
                    }
                }else{
                    result = this.getId() == other.getId();
                }
            }
        }
        return result;
    }

    /**
     * @param o object to compare
     * @return 1 if this > o else 0 if they are equal or -1 if this < o
     */
    public int compareTo(Object o) {
        if (o instanceof ActiveMQMessage) {
            return compareTo((ActiveMQMessage) o);
        }
        return -1;
    }

    /**
     * Sorted by destination and then messageId
     *
     * @param that another message to compare against
     * @return 1 if this > that else 0 if they are equal or -1 if this < that
     */
    public int compareTo(ActiveMQMessage that) {
        int answer = 1;

        if (that != null && this.jmsDestination != null && that.jmsDestination != null) {
            answer = this.jmsDestination.compareTo(that.jmsDestination);
            if (answer == 0) {
                if (this.jmsMessageID != null && that.jmsMessageID != null) {
                    answer = IdGenerator.compare(this.jmsMessageID, that.jmsMessageID);
                }
                else {
                    answer = 1;
                }
            }
        }
        return answer;
    }


    /**
     * @return Returns a shallow copy of the message instance
     * @throws JMSException
     */

    public ActiveMQMessage shallowCopy() throws JMSException {
        ActiveMQMessage other = new ActiveMQMessage();
        this.initializeOther(other);
        return other;
    }

    /**
     * @return Returns a deep copy of the message - note the header fields are only shallow copied
     * @throws JMSException
     */

    public ActiveMQMessage deepCopy() throws JMSException {
        return shallowCopy();
    }


    /**
     * Indicates if the Message has expired
     *
     * @param currentTime -
     *                    the current time in milliseconds
     * @return true if the message can be expired
     */
    public boolean isExpired(long currentTime) {
        boolean result = false;
        long expiration = this.jmsExpiration;
        if (jmsExpiration > 0 && jmsExpiration < currentTime) {
            result = true;
        }
        return result;
    }

    /**
     * @return true if the message is expired
     */
    public boolean isExpired() {
        return !dispatchedFromDLQ && jmsExpiration > 0 && isExpired(System.currentTimeMillis());
    }
    
    /**
     * @return true if an advisory message
     */
    public boolean isAdvisory(){
        return jmsDestination != null && jmsDestination.isAdvisory();
    }

    /**
     * Initializes another message with current values from this instance
     *
     * @param other the other ActiveMQMessage to initialize
     */
    protected void initializeOther(ActiveMQMessage other) {
        super.initializeOther(other);
        other.jmsMessageID = this.jmsMessageID;
        other.jmsClientID = this.jmsClientID;
        other.jmsCorrelationID = this.jmsCorrelationID;
        other.jmsDestination = this.jmsDestination;
        other.jmsReplyTo = this.jmsReplyTo;
        other.jmsDeliveryMode = this.jmsDeliveryMode;
        other.jmsRedelivered = this.jmsRedelivered;
        other.jmsType = this.jmsType;
        other.jmsExpiration = this.jmsExpiration;
        other.jmsPriority = this.jmsPriority;
        other.jmsTimestamp = this.jmsTimestamp;
        other.properties = this.properties != null ? new HashMap(this.properties) : null;
        other.readOnlyProperties = this.readOnlyProperties;
        other.readOnlyMessage = this.readOnlyMessage;
        other.entryBrokerName = this.entryBrokerName;
        other.entryClusterName = this.entryClusterName;
        other.consumerNos = this.consumerNos;
        other.transactionId = this.transactionId;
        other.xaTransacted = this.xaTransacted;
        other.bodyAsBytes = this.bodyAsBytes;
        other.messageAcknowledge = this.messageAcknowledge;
        other.jmsMessageIdentity = this.jmsMessageIdentity;
        other.sequenceNumber = this.sequenceNumber;
        other.deliveryCount = this.deliveryCount;
        other.dispatchedFromDLQ = this.dispatchedFromDLQ;
        other.messsageHandle = this.messsageHandle;
        other.consumerIdentifier = this.consumerIdentifier;
        other.externalMessageId = this.externalMessageId;
        other.producerKey = this.producerKey;
        other.messagePart = this.messagePart;
        other.numberOfParts = this.numberOfParts;
        other.partNumber = this.partNumber;
        other.parentMessageID = this.parentMessageID;
    }
    

    /**
     * Gets the message ID.
     * <p/>
     * <P>The <CODE>JMSMessageID</CODE> header field contains a value that
     * uniquely identifies each message sent by a provider.
     * <p/>
     * <P>When a message is sent, <CODE>JMSMessageID</CODE> can be ignored.
     * When the <CODE>send</CODE> or <CODE>publish</CODE> method returns, it
     * contains a provider-assigned value.
     * <p/>
     * <P>A <CODE>JMSMessageID</CODE> is a <CODE>String</CODE> value that
     * should function as a
     * unique key for identifying messages in a historical repository.
     * The exact scope of uniqueness is provider-defined. It should at
     * least cover all messages for a specific installation of a
     * provider, where an installation is some connected set of message
     * routers.
     * <p/>
     * <P>All <CODE>JMSMessageID</CODE> values must start with the prefix
     * <CODE>'ID:'</CODE>.
     * Uniqueness of message ID values across different providers is
     * not required.
     * <p/>
     * <P>Since message IDs take some effort to create and increase a
     * message's size, some JMS providers may be able to optimize message
     * overhead if they are given a hint that the message ID is not used by
     * an application. By calling the
     * <CODE>MessageProducer.setDisableMessageID</CODE> method, a JMS client
     * enables this potential optimization for all messages sent by that
     * message producer. If the JMS provider accepts this
     * hint, these messages must have the message ID set to null; if the
     * provider ignores the hint, the message ID must be set to its normal
     * unique value.
     *
     * @return the message ID
     * @see javax.jms.Message#setJMSMessageID(String)
     * @see javax.jms.MessageProducer#setDisableMessageID(boolean)
     */

    public String getJMSMessageID() {
        if (jmsMessageID == null && producerKey != null){
            jmsMessageID = producerKey + sequenceNumber;
        }
        return jmsMessageID;
    }


    /**
     * Sets the message ID.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param id the ID of the message
     * @see javax.jms.Message#getJMSMessageID()
     */

    public void setJMSMessageID(String id) {
        this.jmsMessageID = id;
        this.jmsMessageIdentity = null;
    }

    /**
     * Another way to get the Message id. 
     */
    public Object getMemoryId() {
        return getJMSMessageID();
    }

    /**
     * Gets the message timestamp.
     * <p/>
     * <P>The <CODE>JMSTimestamp</CODE> header field contains the time a
     * message was
     * handed off to a provider to be sent. It is not the time the
     * message was actually transmitted, because the actual send may occur
     * later due to transactions or other client-side queueing of messages.
     * <p/>
     * <P>When a message is sent, <CODE>JMSTimestamp</CODE> is ignored. When
     * the <CODE>send</CODE> or <CODE>publish</CODE>
     * method returns, it contains a time value somewhere in the interval
     * between the call and the return. The value is in the format of a normal
     * millis time value in the Java programming language.
     * <p/>
     * <P>Since timestamps take some effort to create and increase a
     * message's size, some JMS providers may be able to optimize message
     * overhead if they are given a hint that the timestamp is not used by an
     * application. By calling the
     * <CODE>MessageProducer.setDisableMessageTimestamp</CODE> method, a JMS
     * client enables this potential optimization for all messages sent by
     * that message producer. If the JMS provider accepts this
     * hint, these messages must have the timestamp set to zero; if the
     * provider ignores the hint, the timestamp must be set to its normal
     * value.
     *
     * @return the message timestamp
     * @see javax.jms.Message#setJMSTimestamp(long)
     * @see javax.jms.MessageProducer#setDisableMessageTimestamp(boolean)
     */

    public long getJMSTimestamp() {
        return jmsTimestamp;
    }


    /**
     * Sets the message timestamp.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param timestamp the timestamp for this message
     * @see javax.jms.Message#getJMSTimestamp()
     */

    public void setJMSTimestamp(long timestamp) {
        this.jmsTimestamp = timestamp;
    }


    /**
     * Gets the correlation ID as an array of bytes for the message.
     * <p/>
     * <P>The use of a <CODE>byte[]</CODE> value for
     * <CODE>JMSCorrelationID</CODE> is non-portable.
     *
     * @return the correlation ID of a message as an array of bytes
     * @see javax.jms.Message#setJMSCorrelationID(String)
     * @see javax.jms.Message#getJMSCorrelationID()
     * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
     */

    public byte[] getJMSCorrelationIDAsBytes() {
        return this.jmsCorrelationID != null ? this.jmsCorrelationID.getBytes() : null;
    }


    /**
     * Sets the correlation ID as an array of bytes for the message.
     * <p/>
     * <P>The array is copied before the method returns, so
     * future modifications to the array will not alter this message header.
     * <p/>
     * <P>If a provider supports the native concept of correlation ID, a
     * JMS client may need to assign specific <CODE>JMSCorrelationID</CODE>
     * values to match those expected by native messaging clients.
     * JMS providers without native correlation ID values are not required to
     * support this method and its corresponding get method; their
     * implementation may throw a
     * <CODE>java.lang.UnsupportedOperationException</CODE>.
     * <p/>
     * <P>The use of a <CODE>byte[]</CODE> value for
     * <CODE>JMSCorrelationID</CODE> is non-portable.
     *
     * @param correlationID the correlation ID value as an array of bytes
     * @see javax.jms.Message#setJMSCorrelationID(String)
     * @see javax.jms.Message#getJMSCorrelationID()
     * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
     */

    public void setJMSCorrelationIDAsBytes(byte[] correlationID) {
        if (correlationID == null) {
            this.jmsCorrelationID = null;
        }
        else {
            this.jmsCorrelationID = new String(correlationID);
        }
    }


    /**
     * Sets the correlation ID for the message.
     * <p/>
     * <P>A client can use the <CODE>JMSCorrelationID</CODE> header field to
     * link one message with another. A typical use is to link a response
     * message with its request message.
     * <p/>
     * <P><CODE>JMSCorrelationID</CODE> can hold one of the following:
     * <UL>
     * <LI>A provider-specific message ID
     * <LI>An application-specific <CODE>String</CODE>
     * <LI>A provider-native <CODE>byte[]</CODE> value
     * </UL>
     * <p/>
     * <P>Since each message sent by a JMS provider is assigned a message ID
     * value, it is convenient to link messages via message ID. All message ID
     * values must start with the <CODE>'ID:'</CODE> prefix.
     * <p/>
     * <P>In some cases, an application (made up of several clients) needs to
     * use an application-specific value for linking messages. For instance,
     * an application may use <CODE>JMSCorrelationID</CODE> to hold a value
     * referencing some external information. Application-specified values
     * must not start with the <CODE>'ID:'</CODE> prefix; this is reserved for
     * provider-generated message ID values.
     * <p/>
     * <P>If a provider supports the native concept of correlation ID, a JMS
     * client may need to assign specific <CODE>JMSCorrelationID</CODE> values
     * to match those expected by clients that do not use the JMS API. A
     * <CODE>byte[]</CODE> value is used for this
     * purpose. JMS providers without native correlation ID values are not
     * required to support <CODE>byte[]</CODE> values. The use of a
     * <CODE>byte[]</CODE> value for <CODE>JMSCorrelationID</CODE> is
     * non-portable.
     *
     * @param correlationID the message ID of a message being referred to
     * @see javax.jms.Message#getJMSCorrelationID()
     * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
     * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
     */

    public void setJMSCorrelationID(String correlationID) {
        this.jmsCorrelationID = correlationID;
    }


    /**
     * Gets the correlation ID for the message.
     * <p/>
     * <P>This method is used to return correlation ID values that are
     * either provider-specific message IDs or application-specific
     * <CODE>String</CODE> values.
     *
     * @return the correlation ID of a message as a <CODE>String</CODE>
     * @see javax.jms.Message#setJMSCorrelationID(String)
     * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
     * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
     */

    public String getJMSCorrelationID() {
        return this.jmsCorrelationID;
    }


    /**
     * Gets the <CODE>Destination</CODE> object to which a reply to this
     * message should be sent.
     *
     * @return <CODE>Destination</CODE> to which to send a response to this
     *         message
     * @see javax.jms.Message#setJMSReplyTo(Destination)
     */

    public Destination getJMSReplyTo() {
        return this.jmsReplyTo;

    }


    /**
     * Sets the <CODE>Destination</CODE> object to which a reply to this
     * message should be sent.
     * <p/>
     * <P>The <CODE>JMSReplyTo</CODE> header field contains the destination
     * where a reply
     * to the current message should be sent. If it is null, no reply is
     * expected. The destination may be either a <CODE>Queue</CODE> object or
     * a <CODE>Topic</CODE> object.
     * <p/>
     * <P>Messages sent with a null <CODE>JMSReplyTo</CODE> value may be a
     * notification of some event, or they may just be some data the sender
     * thinks is of interest.
     * <p/>
     * <P>Messages with a <CODE>JMSReplyTo</CODE> value typically expect a
     * response. A response is optional; it is up to the client to decide.
     * These messages are called requests. A message sent in response to a
     * request is called a reply.
     * <p/>
     * <P>In some cases a client may wish to match a request it sent earlier
     * with a reply it has just received. The client can use the
     * <CODE>JMSCorrelationID</CODE> header field for this purpose.
     *
     * @param replyTo <CODE>Destination</CODE> to which to send a response to
     *                this message
     * @see javax.jms.Message#getJMSReplyTo()
     */

    public void setJMSReplyTo(Destination replyTo) {
        this.jmsReplyTo = (ActiveMQDestination) replyTo;
    }


    /**
     * Gets the <CODE>Destination</CODE> object for this message.
     * <p/>
     * <P>The <CODE>JMSDestination</CODE> header field contains the
     * destination to which the message is being sent.
     * <p/>
     * <P>When a message is sent, this field is ignored. After completion
     * of the <CODE>send</CODE> or <CODE>publish</CODE> method, the field
     * holds the destination specified by the method.
     * <p/>
     * <P>When a message is received, its <CODE>JMSDestination</CODE> value
     * must be equivalent to the value assigned when it was sent.
     *
     * @return the destination of this message
     * @see javax.jms.Message#setJMSDestination(Destination)
     */

    public Destination getJMSDestination() {
        return this.jmsDestination;
    }


    /**
     * Sets the <CODE>Destination</CODE> object for this message.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param destination the destination for this message
     * @see javax.jms.Message#getJMSDestination()
     */

    public void setJMSDestination(Destination destination) {
        this.jmsDestination = (ActiveMQDestination) destination;
    }


    /**
     * Gets the <CODE>DeliveryMode</CODE> value specified for this message.
     *
     * @return the delivery mode for this message
     * @see javax.jms.Message#setJMSDeliveryMode(int)
     * @see javax.jms.DeliveryMode
     */

    public int getJMSDeliveryMode() {
        return this.jmsDeliveryMode;
    }


    /**
     * Sets the <CODE>DeliveryMode</CODE> value for this message.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param deliveryMode the delivery mode for this message
     * @see javax.jms.Message#getJMSDeliveryMode()
     * @see javax.jms.DeliveryMode
     */

    public void setJMSDeliveryMode(int deliveryMode) {
        this.jmsDeliveryMode = deliveryMode;
    }


    /**
     * Gets an indication of whether this message is being redelivered.
     * <p/>
     * <P>If a client receives a message with the <CODE>JMSRedelivered</CODE>
     * field set,
     * it is likely, but not guaranteed, that this message was delivered
     * earlier but that its receipt was not acknowledged
     * at that time.
     *
     * @return true if this message is being redelivered
     * @see javax.jms.Message#setJMSRedelivered(boolean)
     */

    public boolean getJMSRedelivered() {
        return this.jmsRedelivered;
    }


    /**
     * Specifies whether this message is being redelivered.
     * <p/>
     * <P>This field is set at the time the message is delivered. This
     * method can be used to change the value for a message that has
     * been received.
     *
     * @param redelivered an indication of whether this message is being
     *                    redelivered
     * @see javax.jms.Message#getJMSRedelivered()
     */

    public void setJMSRedelivered(boolean redelivered) {
        this.jmsRedelivered = redelivered;
    }


    /**
     * Gets the message type identifier supplied by the client when the
     * message was sent.
     *
     * @return the message type
     * @see javax.jms.Message#setJMSType(String)
     */

    public String getJMSType() {
        return this.jmsType;
    }

    /**
     * Sets the message type.
     * <p/>
     * <P>Some JMS providers use a message repository that contains the
     * definitions of messages sent by applications. The <CODE>JMSType</CODE>
     * header field may reference a message's definition in the provider's
     * repository.
     * <p/>
     * <P>The JMS API does not define a standard message definition repository,
     * nor does it define a naming policy for the definitions it contains.
     * <p/>
     * <P>Some messaging systems require that a message type definition for
     * each application message be created and that each message specify its
     * type. In order to work with such JMS providers, JMS clients should
     * assign a value to <CODE>JMSType</CODE>, whether the application makes
     * use of it or not. This ensures that the field is properly set for those
     * providers that require it.
     * <p/>
     * <P>To ensure portability, JMS clients should use symbolic values for
     * <CODE>JMSType</CODE> that can be configured at installation time to the
     * values defined in the current provider's message repository. If string
     * literals are used, they may not be valid type names for some JMS
     * providers.
     *
     * @param type the message type
     * @see javax.jms.Message#getJMSType()
     */

    public void setJMSType(String type) {
        this.jmsType = type;
    }


    /**
     * Gets the message's expiration value.
     * <p/>
     * <P>When a message is sent, the <CODE>JMSExpiration</CODE> header field
     * is left unassigned. After completion of the <CODE>send</CODE> or
     * <CODE>publish</CODE> method, it holds the expiration time of the
     * message. This is the sum of the time-to-live value specified by the
     * client and the GMT at the time of the <CODE>send</CODE> or
     * <CODE>publish</CODE>.
     * <p/>
     * <P>If the time-to-live is specified as zero, <CODE>JMSExpiration</CODE>
     * is set to zero to indicate that the message does not expire.
     * <p/>
     * <P>When a message's expiration time is reached, a provider should
     * discard it. The JMS API does not define any form of notification of
     * message expiration.
     * <p/>
     * <P>Clients should not receive messages that have expired; however,
     * the JMS API does not guarantee that this will not happen.
     *
     * @return the time the message expires, which is the sum of the
     *         time-to-live value specified by the client and the GMT at the
     *         time of the send
     * @see javax.jms.Message#setJMSExpiration(long)
     */

    public long getJMSExpiration() {
        return this.jmsExpiration;
    }


    /**
     * Sets the message's expiration value.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param expiration the message's expiration time
     * @see javax.jms.Message#getJMSExpiration()
     */

    public void setJMSExpiration(long expiration) {
        this.jmsExpiration = expiration;
    }

    /**
     * Gets the message priority level.
     * <p/>
     * <P>The JMS API defines ten levels of priority value, with 0 as the
     * lowest
     * priority and 9 as the highest. In addition, clients should consider
     * priorities 0-4 as gradations of normal priority and priorities 5-9
     * as gradations of expedited priority.
     * <p/>
     * <P>The JMS API does not require that a provider strictly implement
     * priority
     * ordering of messages; however, it should do its best to deliver
     * expedited messages ahead of normal messages.
     *
     * @return the default message priority
     * @see javax.jms.Message#setJMSPriority(int)
     */

    public int getJMSPriority() {
        return this.jmsPriority;
    }


    /**
     * Sets the priority level for this message.
     * <p/>
     * <P>JMS providers set this field when a message is sent. This method
     * can be used to change the value for a message that has been received.
     *
     * @param priority the priority of this message
     * @see javax.jms.Message#getJMSPriority()
     */

    public void setJMSPriority(int priority) {
        this.jmsPriority = priority;
    }

    /**
     * Clears a message's properties.
     * <p/>
     * <P>The message's header fields and body are not cleared.
     */

    public synchronized void clearProperties() {
        if (this.properties != null) {
            this.properties.clear();
        }
        this.readOnlyProperties = false;
    }


    /**
     * Indicates whether a property value exists.
     *
     * @param name the name of the property to test
     * @return true if the property exists
     */

    public boolean propertyExists(String name) {
        return this.properties != null ? this.properties.containsKey(name) : false;
    }


    /**
     * Returns the value of the <CODE>boolean</CODE> property with the
     * specified name.
     *
     * @param name the name of the <CODE>boolean</CODE> property
     * @return the <CODE>boolean</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public boolean getBooleanProperty(String name) throws JMSException {
        return vanillaToBoolean(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>byte</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>byte</CODE> property
     * @return the <CODE>byte</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public byte getByteProperty(String name) throws JMSException {
        return vanillaToByte(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>short</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>short</CODE> property
     * @return the <CODE>short</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public short getShortProperty(String name) throws JMSException {
        return vanillaToShort(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>int</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>int</CODE> property
     * @return the <CODE>int</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public int getIntProperty(String name) throws JMSException {
        return vanillaToInt(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>long</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>long</CODE> property
     * @return the <CODE>long</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public long getLongProperty(String name) throws JMSException {
        return vanillaToLong(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>float</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>float</CODE> property
     * @return the <CODE>float</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public float getFloatProperty(String name) throws JMSException {
        return vanillaToFloat(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>double</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>double</CODE> property
     * @return the <CODE>double</CODE> property value for the specified name
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public double getDoubleProperty(String name) throws JMSException {
        return vanillaToDouble(this.properties, name);
    }


    /**
     * Returns the value of the <CODE>String</CODE> property with the specified
     * name.
     *
     * @param name the name of the <CODE>String</CODE> property
     * @return the <CODE>String</CODE> property value for the specified name;
     *         if there is no property by this name, a null value is returned
     * @throws JMSException           if the JMS provider fails to get the property
     *                                value due to some internal error.
     * @throws MessageFormatException if this type conversion is invalid.
     */

    public String getStringProperty(String name) throws JMSException {
        return vanillaToString(this.properties, name);
    }


    /**
     * Returns the value of the Java object property with the specified name.
     * <p/>
     * <P>This method can be used to return, in objectified format,
     * an object that has been stored as a property in the message with the
     * equivalent <CODE>setObjectProperty</CODE> method call, or its equivalent
     * primitive <CODE>set<I>type</I>Property</CODE> method.
     *
     * @param name the name of the Java object property
     * @return the Java object property value with the specified name, in
     *         objectified format (for example, if the property was set as an
     *         <CODE>int</CODE>, an <CODE>Integer</CODE> is
     *         returned); if there is no property by this name, a null value
     *         is returned
     */

    public Object getObjectProperty(String name) {
        return this.properties != null ? this.properties.get(name) : null;
    }


    /**
     * Returns an <CODE>Enumeration</CODE> of all the property names.
     * <p/>
     * <P>Note that JMS standard header fields are not considered
     * properties and are not returned in this enumeration.
     *
     * @return an enumeration of all the names of property values
     */

    public Enumeration getPropertyNames() {
        if (this.properties == null) {
            this.properties = new HashMap();
        }
        return Collections.enumeration(this.properties.keySet());
    }

    /**
     * Retrieve the message properties as a HashMap
     *
     * @return the HashMap representing the properties or null if not set or used
     */

    public HashMap getProperties() {
        return this.properties;
    }

    /**
     * Set the Message's properties from an external source
     * No checking on correct types is done by this method
     *
     * @param newProperties
     */

    public void setProperties(HashMap newProperties) {
        this.properties = newProperties;
    }


    /**
     * Sets a <CODE>boolean</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>boolean</CODE> property
     * @param value the <CODE>boolean</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setBooleanProperty(String name, boolean value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
    }


    /**
     * Sets a <CODE>byte</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>byte</CODE> property
     * @param value the <CODE>byte</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setByteProperty(String name, byte value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Byte(value));
    }


    /**
     * Sets a <CODE>short</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>short</CODE> property
     * @param value the <CODE>short</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setShortProperty(String name, short value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Short(value));
    }


    /**
     * Sets an <CODE>int</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>int</CODE> property
     * @param value the <CODE>int</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setIntProperty(String name, int value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Integer(value));
    }


    /**
     * Sets a <CODE>long</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>long</CODE> property
     * @param value the <CODE>long</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setLongProperty(String name, long value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Long(value));
    }


    /**
     * Sets a <CODE>float</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>float</CODE> property
     * @param value the <CODE>float</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setFloatProperty(String name, float value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Float(value));

    }


    /**
     * Sets a <CODE>double</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>double</CODE> property
     * @param value the <CODE>double</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setDoubleProperty(String name, double value) throws JMSException {
        prepareProperty(name);
        this.properties.put(name, new Double(value));
    }


    /**
     * Sets a <CODE>String</CODE> property value with the specified name into
     * the message.
     *
     * @param name  the name of the <CODE>String</CODE> property
     * @param value the <CODE>String</CODE> property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setStringProperty(String name, String value) throws JMSException {
        prepareProperty(name);
        if (value == null) {
            this.properties.remove(name);
        }
        else {
            this.properties.put(name, value);
        }
    }


    /**
     * Sets a Java object property value with the specified name into the
     * message.
     * <p/>
     * <P>Note that this method works only for the objectified primitive
     * object types (<CODE>Integer</CODE>, <CODE>Double</CODE>,
     * <CODE>Long</CODE> ...) and <CODE>String</CODE> objects.
     *
     * @param name  the name of the Java object property
     * @param value the Java object property value to set
     * @throws JMSException                 if the JMS provider fails to set the property
     *                                      due to some internal error.
     * @throws IllegalArgumentException     if the name is null or if the name is
     *                                      an empty string.
     * @throws MessageFormatException       if the object is invalid
     * @throws MessageNotWriteableException if properties are read-only
     */

    public void setObjectProperty(String name, Object value) throws JMSException {
        prepareProperty(name);
        if (value == null) {
            this.properties.remove(name);
        }
        else {
            if (value instanceof Number ||
                    value instanceof Character ||
                    value instanceof Boolean ||
                    value instanceof String) {
                this.properties.put(name, value);
            }
            else {
                throw new MessageFormatException("Cannot set property to type: " + value.getClass().getName());
            }
        }
    }


    /**
     * Acknowledges all consumed messages of the session of this consumed
     * message.
     * <p/>
     * <P>All consumed JMS messages support the <CODE>acknowledge</CODE>
     * method for use when a client has specified that its JMS session's
     * consumed messages are to be explicitly acknowledged.  By invoking
     * <CODE>acknowledge</CODE> on a consumed message, a client acknowledges
     * all messages consumed by the session that the message was delivered to.
     * <p/>
     * <P>Calls to <CODE>acknowledge</CODE> are ignored for both transacted
     * sessions and sessions specified to use implicit acknowledgement modes.
     * <p/>
     * <P>A client may individually acknowledge each message as it is consumed,
     * or it may choose to acknowledge messages as an application-defined group
     * (which is done by calling acknowledge on the last received message of the group,
     * thereby acknowledging all messages consumed by the session.)
     * <p/>
     * <P>Messages that have been received but not acknowledged may be
     * redelivered.
     *
     * @throws JMSException if the JMS provider fails to acknowledge the
     *                      messages due to some internal error.
     * @throws javax.jms.IllegalStateException
     *                      if this method is called on a closed
     *                      session.
     * @see javax.jms.Session#CLIENT_ACKNOWLEDGE
     */

    public void acknowledge() throws JMSException {
        if (this.messageAcknowledge != null) {
            this.messageAcknowledge.acknowledge(this);
        }
    }


    /**
     * Clears out the message body. Clearing a message's body does not clear
     * its header values or property entries.
     * <p/>
     * <P>If this message body was read-only, calling this method leaves
     * the message body in the same state as an empty body in a newly
     * created message.
     *
     * @throws JMSException if the JMS provider fails to clear the message
     *                      body due to some internal error.
     */

    public void clearBody() throws JMSException {
        this.readOnlyMessage = false;
        this.bodyAsBytes = null;
    }

    boolean vanillaToBoolean(HashMap table, String name) throws JMSException {
        boolean result = false;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Boolean) {
                result = ((Boolean) value).booleanValue();
            }
            else if (value instanceof String) {
                // will throw a runtime exception if cannot convert
                result = Boolean.valueOf((String) value).booleanValue();
            }
            else {
                throw new MessageFormatException(name + " not a Boolean type");
            }
        }
        return result;
    }

    byte vanillaToByte(HashMap table, String name) throws JMSException {
        byte result = 0;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Byte) {
                result = ((Byte) value).byteValue();
            }
            else if (value instanceof String) {
                result = Byte.valueOf((String) value).byteValue();
            }
            else {
                throw new MessageFormatException(name + " not a Byte type");
            }
        }
        else {
            //object doesn't exist - so treat as a null ..
            throw new NumberFormatException("Cannot interpret null as a Byte");
        }
        return result;
    }

    short vanillaToShort(HashMap table, String name) throws JMSException {
        short result = 0;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Short) {
                result = ((Short) value).shortValue();
            }
            else if (value instanceof String) {
                return Short.valueOf((String) value).shortValue();
            }
            else if (value instanceof Byte) {
                result = ((Byte) value).byteValue();
            }
            else {
                throw new MessageFormatException(name + " not a Short type");
            }
        }
        else {
            throw new NumberFormatException(name + " is null");
        }
        return result;
    }

    int vanillaToInt(HashMap table, String name) throws JMSException {
        int result = 0;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Integer) {
                result = ((Integer) value).intValue();
            }
            else if (value instanceof String) {
                result = Integer.valueOf((String) value).intValue();
            }
            else if (value instanceof Byte) {
                result = ((Byte) value).intValue();
            }
            else if (value instanceof Short) {
                result = ((Short) value).intValue();
            }
            else {
                throw new MessageFormatException(name + " not an Integer type");
            }
        }
        else {
            throw new NumberFormatException(name + " is null");
        }
        return result;
    }

    long vanillaToLong(HashMap table, String name) throws JMSException {
        long result = 0;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Long) {
                result = ((Long) value).longValue();
            }
            else if (value instanceof String) {
                // will throw a runtime exception if cannot convert
                result = Long.valueOf((String) value).longValue();
            }
            else if (value instanceof Byte) {
                result = ((Byte) value).byteValue();
            }
            else if (value instanceof Short) {
                result = ((Short) value).shortValue();
            }
            else if (value instanceof Integer) {
                result = ((Integer) value).intValue();
            }
            else {
                throw new MessageFormatException(name + " not a Long type");
            }
        }
        else {
            throw new NumberFormatException(name + " is null");
        }
        return result;
    }

    float vanillaToFloat(HashMap table, String name) throws JMSException {
        float result = 0.0f;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Float) {
                result = ((Float) value).floatValue();
            }
            else if (value instanceof String) {
                result = Float.valueOf((String) value).floatValue();
            }
            else {
                throw new MessageFormatException(name + " not a Float type: " + value.getClass());
            }
        }
        else {
            throw new NullPointerException(name + " is null");
        }
        return result;
    }

    double vanillaToDouble(HashMap table, String name) throws JMSException {
        double result = 0.0d;
        Object value = getVanillaProperty(table, name);
        if (value != null) {
            if (value instanceof Double) {
                result = ((Double) value).doubleValue();
            }
            else if (value instanceof String) {
                result = Double.valueOf((String) value).doubleValue();
            }
            else if (value instanceof Float) {
                result = ((Float) value).floatValue();
            }
            else {
                throw new MessageFormatException(name + " not a Double type");
            }
        }
        else {
            throw new NullPointerException(name + " is null");
        }
        return result;
    }
    
    Object getVanillaProperty(HashMap table, String name) {
        Object result = null;
        if (name == null) {
            throw new NullPointerException("name supplied is null");
        }
        result = getReservedProperty(name);
        if (result == null && table != null) {
            result = table.get(name);
        }
        return result;
    }
    
    Object getReservedProperty(String name){
        Object result = null;
        if (name != null && name.equals(DELIVERY_COUNT_NAME)){
            result = new Integer(deliveryCount);
        }
        return result;  
    }


    String vanillaToString(HashMap table, String name) throws JMSException {
        String result = null;
        if (table != null) {
            Object value = table.get(name);
            if (value != null) {
                if (value instanceof String || value instanceof Number || value instanceof Boolean) {
                    result = value.toString();
                }
                else {
                    throw new MessageFormatException(name + " not a String type");
                }
            }
        }
        return result;
    }

    private void prepareProperty(String name) throws JMSException {
        if (name == null) {
            throw new IllegalArgumentException("Invalid property name: cannot be null");
        }
        if (name.length() == 0) {
            throw new IllegalArgumentException("Invalid property name: cannot be empty");
        }
        if (this.readOnlyProperties) {
            throw new MessageNotWriteableException("Properties are read-only");
        }
        if (this.properties == null) {
            this.properties = new HashMap();
        }
    }

    /**
     * @return Returns the entryBrokerName.
     */
    public String getEntryBrokerName() {
        return this.entryBrokerName;
    }

    /**
     * @param newEntryBrokerName The entryBrokerName to set.
     */
    public void setEntryBrokerName(String newEntryBrokerName) {
        this.entryBrokerName = newEntryBrokerName;
    }

    /**
     * @return Returns the entryClusterName.
     */
    public String getEntryClusterName() {
        return this.entryClusterName;
    }

    /**
     * @param newEntryClusterName The entryClusterName to set.
     */
    public void setEntryClusterName(String newEntryClusterName) {
        this.entryClusterName = newEntryClusterName;
    }

    /**
     * @return Returns the consumerNos.
     */
    public int[] getConsumerNos() {
        return this.consumerNos;
    }

    /**
     * @param newConsumerNos The consumerIDs to set.
     */
    public void setConsumerNos(int[] newConsumerNos) {
        this.consumerNos = newConsumerNos;
    }

    /**
     * @return Returns the jmsClientID.
     */
    public String getJMSClientID() {
        return this.jmsClientID;
    }

    /**
     * @param newJmsClientID The jmsClientID to set.
     */
    public void setJMSClientID(String newJmsClientID) {
        this.jmsClientID = newJmsClientID;
    }



    /**
     * @return Returns true if this message is part of a transaction
     */

    public boolean isPartOfTransaction() {
        return this.transactionId != null;
    }

    /**
     * @return Returns the transactionId.
     */
    public Object getTransactionId() {
        return this.transactionId;
    }

    /**
     * @param newTransactionId The transactionId to set.
     */
    public void setTransactionId(Object newTransactionId) {
        this.transactionId = newTransactionId;
        this.xaTransacted  = newTransactionId!=null && newTransactionId.getClass()==ActiveMQXid.class;
    }

    /**
     * @Transient
     * @return Returns the consumerId.
     */
    public String getConsumerIdentifer() {
        return consumerIdentifier;
    }

    /**
     * @param consId The consumerId to set.
     */
    public void setConsumerIdentifer(String consId) {
        this.consumerIdentifier = consId;
    }

    /**
     * @Transient
     * @return Returns the messageConsumed.
     */
    public boolean isMessageConsumed() {
        return messageConsumed;
    }

    /**
     * @param messageConsumed The messageConsumed to set.
     */
    public void setMessageConsumed(boolean messageConsumed) {
        this.messageConsumed = messageConsumed;
    }


    /**
     * Prepare a message body for delivery
     *
     * @throws JMSException
     */
    public void prepareMessageBody() throws JMSException {
    }

    /**
     * Convert the message body to data
     *
     * @throws IOException
     */
    public final void convertBodyToBytes() throws IOException {
        if (bodyAsBytes == null) {
            ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
            DataOutputStream dataOut = new DataOutputStream(bytesOut);
            writeBody(dataOut);
            dataOut.flush();
            bodyAsBytes = new ByteArray(bytesOut.toByteArray());
            dataOut.close();
        }
    }

    /**
     * Builds the message body from data
     *
     * @throws IOException
     */
    public final void buildBodyFromBytes() throws IOException {
        if (bodyAsBytes != null) {
            //inflate bodyAsBytes if needed
            if (ByteArrayCompression.isCompressed(bodyAsBytes)){
                ByteArrayCompression compression = new ByteArrayCompression();
                bodyAsBytes = compression.inflate(bodyAsBytes);
            }
            ByteArrayInputStream bytesIn = new ByteArrayInputStream(bodyAsBytes.getBuf(),bodyAsBytes.getOffset(),bodyAsBytes.getLength());
            DataInputStream dataIn = new DataInputStream(bytesIn);
            readBody(dataIn);
            dataIn.close();
        }
    }

    /**
     * Used serialize the message body to an output stream
     *
     * @param dataOut
     * @throws IOException
     */

    public void writeBody(DataOutput dataOut) throws IOException {

    }

    /**
     * Used to help build the body from an input stream
     *
     * @param dataIn
     * @throws IOException
     */

    public void readBody(DataInput dataIn) throws IOException {

    }

    /**
     * @return Returns the bodyAsBytes.
     * @throws IOException
     */
    public ByteArray getBodyAsBytes() throws IOException {
        if (bodyAsBytes == null) {
            convertBodyToBytes();
        }
        return bodyAsBytes;
    }
    
    /**
    * return the data after applying compression
    * @param compression
    * @return compressed ByteArray
    * @throws IOException
    */
   public ByteArray getBodyAsBytes(ByteArrayCompression compression) throws IOException {
        bodyAsBytes = compression.deflate(getBodyAsBytes());
        return bodyAsBytes;
    }
    
    /**
     * @return true if the body is already stored as bytes
     */
    public boolean isBodyConvertedToBytes(){
        return bodyAsBytes != null;
    }
    

    /**
     * @param data The bodyAsBytes to set.
     * @param offset
     * @param length
     */
    public void setBodyAsBytes(byte[] data,int offset, int length) {
        this.bodyAsBytes = new ByteArray(data);
    }
    
    /**
     * set the body as bytes
     * @param ba
     */
    public void setBodyAsBytes(ByteArray ba){
        this.bodyAsBytes = ba;
    }

    /**
     * write map properties to an output stream
     *
     * @param table
     * @param dataOut
     * @throws IOException
     */

    public void writeMapProperties(HashMap table, DataOutput dataOut) throws IOException {
        if (table != null) {
            dataOut.writeShort(table.size());
            for (Iterator iter = table.keySet().iterator(); iter.hasNext();) {
                String key = iter.next().toString();
                dataOut.writeUTF(key);
                Object value = table.get(key);

                if (value == null) {
                    dataOut.write(ActiveMQMessage.NULL);
                }
                else if (value instanceof byte[]) {
                    byte[] data = (byte[]) value;
                    dataOut.write(ActiveMQMessage.BYTES);
                    if (data != null) {
                        dataOut.writeInt(data.length);
                        dataOut.write(data);
                    }
                    else {
                        dataOut.writeInt(-1);
                    }
                }
                else if (value instanceof Byte) {
                    dataOut.write(ActiveMQMessage.BYTE);
                    Byte v = (Byte) value;
                    dataOut.writeByte(v.byteValue());
                }
                else if (value instanceof Boolean) {
                    dataOut.write(ActiveMQMessage.BOOLEAN);
                    Boolean v = (Boolean) value;
                    dataOut.writeBoolean(v.booleanValue());
                }
                else if (value instanceof String) {
                    dataOut.write(ActiveMQMessage.STRING);
                    dataOut.writeUTF(value.toString());
                }
                else if (value instanceof Character) {
                    dataOut.write(ActiveMQMessage.CHAR);
                    Character v = (Character) value;
                    dataOut.writeChar(v.charValue());
                }
                else if (value instanceof Number) {
                    Number v = (Number) value;

                    if (value instanceof Long) {
                        dataOut.write(ActiveMQMessage.LONG);
                        dataOut.writeLong(v.longValue());
                    }
                    else if (value instanceof Integer) {
                        dataOut.write(ActiveMQMessage.INT);
                        dataOut.writeInt(v.intValue());
                    }
                    else if (value instanceof Short) {
                        dataOut.write(ActiveMQMessage.SHORT);
                        dataOut.writeShort(v.shortValue());
                    }
                    else if (value instanceof Float) {
                        dataOut.write(ActiveMQMessage.FLOAT);
                        dataOut.writeFloat(v.floatValue());
                    }
                    else if (value instanceof Double) {
                        dataOut.write(ActiveMQMessage.DOUBLE);
                        dataOut.writeDouble(v.doubleValue());
                    }
                }
                else {
                    throw new RuntimeException("Do not know how to parse value of type: " + value.getClass());
                }

            }
        }
        else {
            dataOut.writeShort(-1);
        }
    }

    /**
     * @param dataIn
     * @return
     * @throws IOException
     */
    public HashMap readMapProperties(DataInput dataIn) throws IOException {
    	HashMap result = null;
        int size = dataIn.readShort();
        if (size > -1) {
            result = new HashMap();
            for (int i = 0; i < size; i++) {
                String key = dataIn.readUTF();
                Object value = null;
                int type = dataIn.readByte();
                
                if (type == ActiveMQMessage.NULL) {
                    value = null;
                }
                else if (type == ActiveMQMessage.BYTES) {
                    byte[] data = null;
                    int dataSize = dataIn.readInt();
                    if (dataSize > -1) {
                        data = new byte[dataSize];
                        dataIn.readFully(data);
                    }
                    value = data;
                }
                else if (type == ActiveMQMessage.BYTE) {
                    value = new Byte(dataIn.readByte());
                }
                else if (type == ActiveMQMessage.BOOLEAN) {
                    value = (dataIn.readBoolean()) ? Boolean.TRUE : Boolean.FALSE;
                }
                else if (type == ActiveMQMessage.STRING) {
                    value = dataIn.readUTF();
                }
                else if (type == ActiveMQMessage.CHAR) {
                    value = new Character(dataIn.readChar());
                }
                else if (type == ActiveMQMessage.LONG) {
                    value = new Long(dataIn.readLong());
                }
                else if (type == ActiveMQMessage.INT) {
                    value = new Integer(dataIn.readInt());
                }
                else if (type == ActiveMQMessage.SHORT) {
                    value = new Short(dataIn.readShort());
                }
                else if (type == ActiveMQMessage.FLOAT) {
                    value = new Float(dataIn.readFloat());
                }
                else if (type == ActiveMQMessage.DOUBLE) {
                    value = new Double(dataIn.readDouble());
                }
                else {
                    throw new RuntimeException("Do not know how to parse type: " + type);
                }
                result.put(key, value);
            }
        }
        return result;
    }

    /**
     * @return Returns the xaTransacted.
     */
    public boolean isXaTransacted() {
        return xaTransacted;
    }

    /**
     * @return the ActiveMQDestination
     */
    public ActiveMQDestination getJMSActiveMQDestination() {
        return jmsDestination;
    }

    /**
     * @return the message identity, which contains the String messageID
     *         and the lazily populated sequence number
     */
    public MessageIdentity getJMSMessageIdentity() {
        if (jmsMessageIdentity == null) {
            jmsMessageIdentity = new MessageIdentity(this.getJMSMessageID());
        }
        return jmsMessageIdentity;
    }

    /**
     * @param messageIdentity - message identity for this object
     */
    public void setJMSMessageIdentity(MessageIdentity messageIdentity) {
        this.jmsMessageIdentity = messageIdentity;
    }
    
    /**
     * Determine if the message originated in the network from the named broker
     * @param brokerName
     * @return true if entry point matches the brokerName
     */
    public boolean isEntryBroker(String brokerName){
        boolean result = entryBrokerName != null && brokerName != null && entryBrokerName.equals(brokerName);
        return result;
    }
    
    /**
     * Determine if the message originated in the network from the named cluster
     * @param clusterName
     * @return true if the entry point matches the clusterName
     */
    public boolean isEntryCluster(String clusterName){
        boolean result = entryClusterName != null && clusterName != null && entryClusterName.equals(clusterName);
        return result;
    }
    
    /**
     * @Transient
     * @return Returns the transientConsumed.
     */
    public boolean isTransientConsumed() {
        return transientConsumed;
    }
    /**
     * @param transientConsumed The transientConsumed to set.
     */
    public void setTransientConsumed(boolean transientConsumed) {
        this.transientConsumed = transientConsumed;
    }
    
    /**
     * @return Returns the sequenceNumber.
     */
    public long getSequenceNumber() {
        return sequenceNumber;
    }
    /**
     * @param sequenceNumber The sequenceNumber to set.
     */
    public void setSequenceNumber(long sequenceNumber) {
        this.sequenceNumber = sequenceNumber;
    }
    /**
     * @return Returns the deliveryCount.
     */
    public int getDeliveryCount() {
        return deliveryCount;
    }
    /**
     * @param deliveryCount The deliveredCount to set.
     */
    public void setDeliveryCount(int deliveryCount) {
        this.deliveryCount = deliveryCount;
    }
    
    /**
     * Increment the delivery count
     * @return the new value of the delivery count
     */
    public int incrementDeliveryCount(){
        return ++this.deliveryCount;
    }
    
    /**
     * @return true if the delivery mode is persistent
     */
    public boolean isPersistent(){
        return jmsDeliveryMode == DeliveryMode.PERSISTENT;
    }
    
    /**
     * @return Returns the dispatchedFromDLQ.
     */
    public boolean isDispatchedFromDLQ() {
        return dispatchedFromDLQ;
    }
    /**
     * @param dispatchedFromDLQ The dispatchedFromDLQ to set.
     */
    public void setDispatchedFromDLQ(boolean dispatchedFromDLQ) {
        this.dispatchedFromDLQ = dispatchedFromDLQ;
    }
    
    /**
     * @return Returns the messsageHandle.
     */
    public short getMesssageHandle() {
        return messsageHandle;
    }
    /**
     * @param messsageHandle The messsageHandle to set.
     */
    public void setMesssageHandle(short messsageHandle) {
        this.messsageHandle = messsageHandle;
    }
    /**
     * @return Returns the externalMessageId.
     */
    public boolean isExternalMessageId() {
        return externalMessageId;
    }
    /**
     * @param externalMessageId The externalMessageId to set.
     */
    public void setExternalMessageId(boolean externalMessageId) {
        this.externalMessageId = externalMessageId;
    }
    /**
     * @return Returns the producerKey.
     */
    public String getProducerKey() {
        return producerKey;
    }
    /**
     * @param producerKey The producerKey to set.
     */
    public void setProducerKey(String producerKey) {
        this.producerKey = producerKey;
    }
    
    /**
     * reset message fragmentation infomation
     * on this message
     *
     */
    public void resetMessagePart(){
        messagePart = false;
        partNumber = 0;
        parentMessageID = null;
    }
    /**
     * @return Returns the messagePart.
     */
    public boolean isMessagePart() {
        return messagePart;
    }
    
    /**
     * @return true if this is the last part of a fragmented message
     */
    public boolean isLastMessagePart(){
        return numberOfParts -1 == partNumber;
    }
    /**
     * @param messagePart The messagePart to set.
     */
    public void setMessagePart(boolean messagePart) {
        this.messagePart = messagePart;
    }
    /**
     * @return Returns the numberOfParts.
     */
    public short getNumberOfParts() {
        return numberOfParts;
    }
    /**
     * @param numberOfParts The numberOfParts to set.
     */
    public void setNumberOfParts(short numberOfParts) {
        this.numberOfParts = numberOfParts;
    }
    /**
     * @return Returns the partNumber.
     */
    public short getPartNumber() {
        return partNumber;
    }
    /**
     * @param partNumber The partNumber to set.
     */
    public void setPartNumber(short partNumber) {
        this.partNumber = partNumber;
    }
    /**
     * @return Returns the parentMessageId.
     */
    public String getParentMessageID() {
        return parentMessageID;
    }
    /**
     * @param parentMessageId The parentMessageId to set.
     */
    public void setParentMessageID(String parentMessageId) {
        this.parentMessageID = parentMessageId;
    }
}
