View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2000-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: StreamMessageImpl.java,v 1.2 2005/05/24 13:27:10 tanderson Exp $
44   */
45  package org.exolab.jms.message;
46  
47  import java.io.ByteArrayInputStream;
48  import java.io.ByteArrayOutputStream;
49  import java.io.DataInputStream;
50  import java.io.DataOutputStream;
51  import java.io.EOFException;
52  import java.io.IOException;
53  import java.io.ObjectInput;
54  import java.io.ObjectOutput;
55  
56  import javax.jms.JMSException;
57  import javax.jms.MessageEOFException;
58  import javax.jms.MessageFormatException;
59  import javax.jms.MessageNotReadableException;
60  import javax.jms.MessageNotWriteableException;
61  import javax.jms.StreamMessage;
62  
63  
64  /***
65   * This class implements the {@link javax.jms.StreamMessage} interface.
66   * <p>
67   * A StreamMessage is used to send a stream of Java primitives.
68   * It is filled and read sequentially. It inherits from <code>Message</code>
69   * and adds a stream message body. It's methods are based largely on those
70   * found in <code>java.io.DataInputStream</code> and
71   * <code>java.io.DataOutputStream</code>.
72   * <p>
73   * The primitive types can be read or written explicitly using methods
74   * for each type. They may also be read or written generically as objects.
75   * For instance, a call to <code>StreamMessage.writeInt(6)</code> is
76   * equivalent to <code>StreamMessage.writeObject(new Integer(6))</code>.
77   * Both forms are provided because the explicit form is convenient for
78   * static programming and the object form is needed when types are not known
79   * at compile time.
80   * <p>
81   * When the message is first created, and when {@link #clearBody}
82   * is called, the body of the message is in write-only mode. After the
83   * first call to {@link #reset} has been made, the message body is in
84   * read-only mode. When a message has been sent, by definition, the
85   * provider calls <code>reset</code> in order to read it's content, and
86   * when a message has been received, the provider has called
87   * <code>reset</code> so that the message body is in read-only mode for the
88   * client.
89   * <p>
90   * If {@link #clearBody} is called on a message in read-only mode,
91   * the message body is cleared and the message body is in write-only mode.
92   * <p>
93   * If a client attempts to read a message in write-only mode, a
94   * MessageNotReadableException is thrown.
95   * <p>
96   * If a client attempts to write a message in read-only mode, a
97   * MessageNotWriteableException is thrown.
98   * <p>
99   * Stream messages support the following conversion table. The marked cases
100  * must be supported. The unmarked cases must throw a JMSException. The
101  * String to primitive conversions may throw a runtime exception if the
102  * primitives <code>valueOf()</code> method does not accept it as a valid
103  * String representation of the primitive.
104  * <p>
105  * A value written as the row type can be read as the column type.
106  *
107  * <pre>
108  * |        | boolean byte short char int long float double String byte[]
109  * |----------------------------------------------------------------------
110  * |boolean |    X                                            X
111  * |byte    |          X     X         X   X                  X
112  * |short   |                X         X   X                  X
113  * |char    |                     X                           X
114  * |int     |                          X   X                  X
115  * |long    |                              X                  X
116  * |float   |                                    X     X      X
117  * |double  |                                          X      X
118  * |String  |    X     X     X         X   X     X     X      X
119  * |byte[]  |                                                        X
120  * |----------------------------------------------------------------------
121  * </pre>
122  * <p>
123  * Attempting to read a null value as a Java primitive type must be treated
124  * as calling the primitive's corresponding <code>valueOf(String)</code>
125  * conversion method with a null value. Since char does not support a String
126  * conversion, attempting to read a null value as a char must throw
127  * NullPointerException.
128  *
129  * @version     $Revision: 1.2 $ $Date: 2005/05/24 13:27:10 $
130  * @author      <a href="mailto:mourikis@intalio.com">Jim Mourikis</a>
131  * @author      <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
132  * @see         javax.jms.StreamMessage
133  */
134 public final class StreamMessageImpl extends MessageImpl
135     implements StreamMessage {
136 
137     /***
138      * Object version no. for serialization
139      */
140     static final long serialVersionUID = 2;
141 
142     /***
143      * Type codes
144      */
145     private static final byte NULL = 0;
146     private static final byte BOOLEAN = 1;
147     private static final byte BYTE = 2;
148     private static final byte BYTE_ARRAY = 3;
149     private static final byte SHORT = 4;
150     private static final byte CHAR = 5;
151     private static final byte INT = 6;
152     private static final byte LONG = 7;
153     private static final byte FLOAT = 8;
154     private static final byte DOUBLE = 9;
155     private static final byte STRING = 10;
156 
157     /***
158      * String values representing the above type codes, for error reporting
159      * purposes
160      */
161     private static final String[] TYPE_NAMES = {
162         "null", "boolean", "byte", "byte[]", "short", "char", "int", "long",
163         "float", "double", "String"};
164 
165     /***
166      * Empty byte array for initialisation purposes
167      */
168     private static final byte[] EMPTY = new byte[]{};
169 
170     /***
171      * The byte stream to store data
172      */
173     private byte[] _bytes = EMPTY;
174 
175     /***
176      * The stream used for writes
177      */
178     private DataOutputStream _out = null;
179 
180     /***
181      * The byte stream backing the output stream
182      */
183     private ByteArrayOutputStream _byteOut = null;
184 
185     /***
186      * The stream used for reads
187      */
188     private DataInputStream _in = null;
189 
190     /***
191      * The byte stream backing the input stream
192      */
193     private ByteArrayInputStream _byteIn = null;
194 
195     /***
196      * Non-zero if incrementally reading a byte array using
197      * {@link #readBytes(byte[])}
198      */
199     private int _readBytes = 0;
200 
201     /***
202      * The length of the byte array being read using {@link #readBytes(byte[])}
203      */
204     private int _byteArrayLength = 0;
205 
206     /***
207      * The offset of the byte stream to start reading from. This defaults
208      * to 0, and only applies to messages that are cloned from a
209      * read-only instance where part of the stream had already been read.
210      */
211     private int _offset = 0;
212 
213 
214     /***
215      * Construct a new StreamMessage. When first created, the message is in
216      * write-only mode.
217      *
218      * @throws JMSException if the message type can't be set, or an I/O error
219      * occurs
220      */
221     public StreamMessageImpl() throws JMSException {
222         setJMSType("StreamMessage");
223     }
224 
225     /***
226      * Clone an instance of this object
227      *
228      * @return a copy of this object
229      * @throws CloneNotSupportedException if object or attributes aren't
230      * cloneable
231      */
232     public final Object clone() throws CloneNotSupportedException {
233         StreamMessageImpl result = (StreamMessageImpl) super.clone();
234         if (_bodyReadOnly) {
235             result._bytes = new byte[_bytes.length];
236             System.arraycopy(_bytes, 0, result._bytes, 0, _bytes.length);
237             if (_byteIn != null) {
238                 // if a client subsequently reads from the cloned object,
239                 // start reading from offset of the original stream
240                 _offset = _bytes.length - _byteIn.available();
241             }
242             result._byteIn = null;
243             result._in = null;
244         } else {
245             if (_out != null) {
246                 try {
247                     _out.flush();
248                 } catch (IOException exception) {
249                     throw new CloneNotSupportedException(
250                         exception.getMessage());
251                 }
252                 result._bytes = _byteOut.toByteArray();
253                 result._byteOut = null;
254                 result._out = null;
255             } else {
256                 result._bytes = new byte[_bytes.length];
257                 System.arraycopy(_bytes, 0, result._bytes, 0, _bytes.length);
258             }
259         }
260 
261         return result;
262     }
263 
264     /***
265      * Serialize out this message's data
266      *
267      * @param out the stream to serialize out to
268      * @throws IOException if any I/O exceptions occurr
269      */
270     public final void writeExternal(ObjectOutput out) throws IOException {
271         // If it was in write mode, extract the byte array
272         if (!_bodyReadOnly && _out != null) {
273             _out.flush();
274             _bytes = _byteOut.toByteArray();
275         }
276 
277         super.writeExternal(out);
278         out.writeLong(serialVersionUID);
279         out.writeInt(_bytes.length);
280         out.write(_bytes);
281         out.flush();
282     }
283 
284     /***
285      * Serialize in this message's data
286      *
287      * @param in the stream to serialize in from
288      * @throws ClassNotFoundException if the class for an object being
289      * restored cannot be found.
290      * @throws IOException if any I/O exceptions occur
291      */
292     public final void readExternal(ObjectInput in)
293         throws ClassNotFoundException, IOException {
294         super.readExternal(in);
295         long version = in.readLong();
296         if (version == serialVersionUID) {
297             int length = in.readInt();
298             _bytes = new byte[length];
299             in.readFully(_bytes);
300         } else {
301             throw new IOException("Incorrect version enountered: " + version
302                                   + ". This version = " + serialVersionUID);
303         }
304     }
305 
306     /***
307      * Read a <code>boolean</code> from the bytes message stream
308      *
309      * @return the <code>boolean</code> value read
310      * @throws JMSException if JMS fails to read message due to some internal
311      * JMS error
312      * @throws MessageEOFException if end of message stream
313      * @throws MessageFormatException if this type conversion is invalid
314      * @throws MessageNotReadableException if message is in write-only mode
315      */
316     public final boolean readBoolean() throws JMSException {
317         boolean result = false;
318         prepare();
319         try {
320             result = FormatConverter.getBoolean(readNext());
321         } catch (MessageFormatException exception) {
322             revert(exception);
323         }
324         return result;
325     }
326 
327     /***
328      * Read a byte value from the stream message
329      *
330      * @return the next byte from the stream message as an 8-bit
331      * <code>byte</code>
332      * @throws JMSException if JMS fails to read message due to some internal
333      * JMS error
334      * @throws MessageEOFException if end of message stream
335      * @throws MessageFormatException if this type conversion is invalid
336      * @throws MessageNotReadableException if message is in write-only mode
337      * @throws NumberFormatException if numeric conversion is invalid
338      */
339     public final byte readByte() throws JMSException {
340         byte result = 0;
341         prepare();
342         try {
343             result = FormatConverter.getByte(readNext());
344         } catch (MessageFormatException exception) {
345             revert(exception);
346         } catch (NumberFormatException exception) {
347             revert(exception);
348         }
349         return result;
350     }
351 
352     /***
353      * Read a 16-bit number from the stream message.
354      *
355      * @return a 16-bit number from the stream message
356      * @throws JMSException if JMS fails to read message due to some internal
357      * JMS error
358      * @throws MessageEOFException if end of message stream
359      * @throws MessageFormatException if this type conversion is invalid
360      * @throws MessageNotReadableException if message is in write-only mode
361      * @throws NumberFormatException if numeric conversion is invalid
362      */
363     public final short readShort() throws JMSException {
364         short result = 0;
365         prepare();
366         try {
367             result = FormatConverter.getShort(readNext());
368         } catch (MessageFormatException exception) {
369             revert(exception);
370         } catch (NumberFormatException exception) {
371             revert(exception);
372         }
373         return result;
374     }
375 
376     /*
377      * Read a Unicode character value from the stream message
378      *
379      * @return a Unicode character from the stream message
380      * @throws JMSException if JMS fails to read message due to some internal
381      * JMS error
382      * @throws MessageEOFException if end of message stream
383      * @throws MessageFormatException if this type conversion is invalid
384      * @throws MessageNotReadableException if message is in write-only mode
385      */
386     public final char readChar() throws JMSException {
387         char result = 0;
388         prepare();
389         try {
390             result = FormatConverter.getChar(readNext());
391         } catch (MessageFormatException exception) {
392             revert(exception);
393         } catch (NullPointerException exception) {
394             revert(exception);
395         }
396         return result;
397     }
398 
399     /*
400      * Read a 32-bit integer from the stream message
401      *
402      * @return a 32-bit integer value from the stream message, interpreted
403      * as an <code>int</code>
404      * @throws JMSException if JMS fails to read message due to some internal
405      * JMS error
406      * @throws MessageEOFException if end of message stream
407      * @throws MessageFormatException if this type conversion is invalid
408      * @throws MessageNotReadableException if message is in write-only mode
409      * @throws NumberFormatException if numeric conversion is invalid
410      */
411     public final int readInt() throws JMSException {
412         int result = 0;
413         prepare();
414         try {
415             result = FormatConverter.getInt(readNext());
416         } catch (MessageFormatException exception) {
417             revert(exception);
418         } catch (NumberFormatException exception) {
419             revert(exception);
420         }
421         return result;
422     }
423 
424     /*
425      * Read a 64-bit integer from the stream message
426      *
427      * @return a 64-bit integer value from the stream message, interpreted as
428      * a <code>long</code>
429      * @throws JMSException if JMS fails to read message due to some internal
430      * JMS error
431      * @throws MessageEOFException if end of message stream
432      * @throws MessageFormatException if this type conversion is invalid
433      * @throws MessageNotReadableException if message is in write-only mode
434      * @throws NumberFormatException if numeric conversion is invalid
435      */
436     public final long readLong() throws JMSException {
437         long result = 0;
438         prepare();
439         try {
440             result = FormatConverter.getLong(readNext());
441         } catch (MessageFormatException exception) {
442             revert(exception);
443         } catch (NumberFormatException exception) {
444             revert(exception);
445         }
446         return result;
447     }
448 
449     /***
450      * Read a <code>float</code> from the stream message
451      *
452      * @return a <code>float</code> value from the stream message
453      * @throws JMSException if JMS fails to read message due to some internal
454      * JMS error
455      * @throws MessageEOFException if end of message stream
456      * @throws MessageFormatException if this type conversion is invalid
457      * @throws MessageNotReadableException if message is in write-only mode
458      * @throws NullPointerException if the value is null
459      * @throws NumberFormatException if numeric conversion is invalid
460      */
461     public final float readFloat() throws JMSException {
462         float result = 0;
463         prepare();
464         try {
465             result = FormatConverter.getFloat(readNext());
466         } catch (MessageFormatException exception) {
467             revert(exception);
468         } catch (NullPointerException exception) {
469             revert(exception);
470         } catch (NumberFormatException exception) {
471             revert(exception);
472         }
473         return result;
474     }
475 
476     /***
477      * Read a <code>double</code> from the stream message
478      *
479      * @return a <code>double</code> value from the stream message
480      * @throws JMSException if JMS fails to read message due to some internal
481      * JMS error
482      * @throws MessageEOFException if end of message stream
483      * @throws MessageFormatException if this type conversion is invalid
484      * @throws MessageNotReadableException if message is in write-only mode
485      * @throws NullPointerException if the value is null
486      * @throws NumberFormatException if numeric conversion is invalid
487      */
488     public final double readDouble() throws JMSException {
489         double result = 0;
490         prepare();
491         try {
492             result = FormatConverter.getDouble(readNext());
493         } catch (MessageFormatException exception) {
494             revert(exception);
495         } catch (NullPointerException exception) {
496             revert(exception);
497         } catch (NumberFormatException exception) {
498             revert(exception);
499         }
500         return result;
501     }
502 
503     /***
504      * Read in a string from the stream message
505      *
506      * @return a Unicode string from the stream message
507      * @throws JMSException if JMS fails to read message due to some internal
508      * JMS error
509      * @throws MessageEOFException if end of message stream
510      * @throws MessageFormatException if this type conversion is invalid
511      * @throws MessageNotReadableException if message is in write-only mode
512      */
513     public final String readString() throws JMSException {
514         String result = null;
515         prepare();
516         try {
517             result = FormatConverter.getString(readNext());
518         } catch (MessageFormatException exception) {
519             revert(exception);
520         }
521         return result;
522     }
523 
524     /***
525      * Read a byte array field from the stream message into the
526      * specified byte[] object (the read buffer).
527      * <p>
528      * To read the field value, readBytes should be successively called
529      * until it returns a value less than the length of the read buffer.
530      * The value of the bytes in the buffer following the last byte
531      * read are undefined.
532      * <p>
533      * If readBytes returns a value equal to the length of the buffer, a
534      * subsequent readBytes call must be made. If there are no more bytes
535      * to be read this call will return -1.
536      * <p>
537      * If the bytes array field value is null, readBytes returns -1.
538      * <p>
539      * If the bytes array field value is empty, readBytes returns 0.
540      * <p>
541      * Once the first readBytes call on a byte[] field value has been done,
542      * the full value of the field must be read before it is valid to read
543      * the next field. An attempt to read the next field before that has
544      * been done will throw a MessageFormatException.
545      * <p>
546      * To read the byte field value into a new byte[] object, use the
547      * {@link #readObject} method.
548      *
549      * @param value the buffer into which the data is read.
550      * @return the total number of bytes read into the buffer, or -1 if
551      * there is no more data because the end of the byte field has been
552      * reached.
553      * @throws JMSException if JMS fails to read message due to some internal
554      * JMS error
555      * @throws MessageEOFException if an end of message stream
556      * @throws MessageFormatException if this type conversion is invalid
557      * @throws MessageNotReadableException if message is in write-only mode
558      */
559     public final int readBytes(byte[] value) throws JMSException {
560         checkRead();
561         getInputStream();
562         int read = 0; // the number of bytes read
563         if (_readBytes == 0) {
564             // read the next byte array field
565             try {
566                 _in.mark(_bytes.length - _in.available());
567                 byte type = (byte) (_in.readByte() & 0x0F);
568                 if (type == NULL) {
569                     return -1;
570                 } else if (type != BYTE_ARRAY) {
571                     _in.reset();
572                     if (type < TYPE_NAMES.length) {
573                         throw new MessageFormatException(
574                             "Expected type=" + TYPE_NAMES[BYTE_ARRAY]
575                             + ", but got type=" + TYPE_NAMES[type]);
576                     } else {
577                         throw new MessageFormatException(
578                             "StreamMessage corrupted");
579                     }
580                 }
581             } catch (IOException exception) {
582                 raise(exception);
583             }
584             try {
585                 _byteArrayLength = _in.readInt();
586             } catch (IOException exception) {
587                 raise(exception);
588             }
589         }
590 
591         if (_byteArrayLength == 0) {
592             // No bytes to read. Return -1 if this is an incremental read
593             // or 0 if the byte array was empty
594             if (_readBytes != 0) {
595                 // completing an incremental read
596                 read = -1;
597             }
598             _readBytes = 0;   // indicates finished reading the byte array
599         } else if (value.length <= _byteArrayLength) {
600             // bytes to read >= size of target
601             read = value.length;
602             try {
603                 _in.readFully(value);
604             } catch (IOException exception) {
605                 raise(exception);
606             }
607             _byteArrayLength -= value.length;
608             ++_readBytes;
609         } else {
610             // bytes to read < size of target
611             read = _byteArrayLength;
612             try {
613                 _in.readFully(value, 0, _byteArrayLength);
614             } catch (IOException exception) {
615                 raise(exception);
616             }
617             _readBytes = 0;
618         }
619         return read;
620     }
621 
622     /***
623      * Read a Java object from the stream message
624      * <p>
625      * Note that this method can be used to return in objectified format,
626      * an object that had been written to the stream with the equivalent
627      * <code>writeObject</code> method call, or it's equivalent primitive
628      * write<type> method.
629      * <p>
630      * Note that byte values are returned as byte[], not Byte[].
631      *
632      * @return a Java object from the stream message, in objectified
633      * format (eg. if it set as an int, then a Integer is returned).
634      * @throws JMSException if JMS fails to read message due to some internal
635      * JMS error
636      * @throws MessageEOFException if end of message stream
637      * @throws MessageNotReadableException if message is in write-only mode
638      */
639     public final Object readObject() throws JMSException {
640         Object result = null;
641         prepare();
642         try {
643             result = readNext();
644         } catch (MessageFormatException exception) {
645             revert(exception);
646         }
647         return result;
648     }
649 
650     /***
651      * Write a <code>boolean</code> to the stream message.
652      * The value <code>true</code> is written out as the value
653      * <code>(byte)1</code>; the value <code>false</code> is written out as
654      * the value <code>(byte)0</code>.
655      *
656      * @param value the <code>boolean</code> value to be written.
657      * @throws JMSException if JMS fails to write message due to
658      * some internal JMS error
659      * @throws MessageNotWriteableException if message in read-only mode
660      */
661     public final void writeBoolean(boolean value) throws JMSException {
662         checkWrite();
663         try {
664             getOutputStream();
665             // encode the boolean value in the type byte
666             _out.writeByte(BOOLEAN | ((value) ? 1 << 4 : 0));
667         } catch (IOException exception) {
668             raise(exception);
669         }
670     }
671 
672     /***
673      * Write out a <code>byte</code> to the stream message
674      *
675      * @param value the <code>byte</code> value to be written
676      * @throws JMSException if JMS fails to write message due to
677      * some internal JMS error
678      * @throws MessageNotWriteableException if message in read-only mode
679      */
680     public final void writeByte(byte value) throws JMSException {
681         checkWrite();
682         try {
683             getOutputStream();
684             _out.writeByte(BYTE);
685             _out.writeByte(value);
686         } catch (IOException exception) {
687             raise(exception);
688         }
689     }
690 
691     /***
692      * Write a <code>short</code> to the stream message
693      *
694      * @param value the <code>short</code> to be written
695      * @throws JMSException if JMS fails to write message due to
696      * some internal JMS error
697      * @throws MessageNotWriteableException if message in read-only mode
698      */
699     public final void writeShort(short value) throws JMSException {
700         checkWrite();
701         try {
702             getOutputStream();
703             _out.writeByte(SHORT);
704             _out.writeShort(value);
705         } catch (IOException exception) {
706             raise(exception);
707         }
708     }
709 
710     /***
711      * Write a <code>char</code> to the stream message
712      *
713      * @param value the <code>char</code> value to be written
714      * @throws JMSException if JMS fails to write message due to
715      * some internal JMS error
716      * @throws MessageNotWriteableException if message in read-only mode
717      */
718     public final void writeChar(char value) throws JMSException {
719         checkWrite();
720         try {
721             getOutputStream();
722             _out.writeByte(CHAR);
723             _out.writeChar(value);
724         } catch (IOException exception) {
725             raise(exception);
726         }
727     }
728 
729     /***
730      * Write an <code>int</code> to the stream message
731      *
732      * @param value the <code>int</code> to be written
733      * @throws JMSException if JMS fails to write message due to
734      * some internal JMS error
735      * @throws MessageNotWriteableException if message in read-only mode
736      */
737     public final void writeInt(int value) throws JMSException {
738         checkWrite();
739         try {
740             getOutputStream();
741             _out.writeByte(INT);
742             _out.writeInt(value);
743         } catch (IOException exception) {
744             raise(exception);
745         }
746     }
747 
748     /***
749      * Write a <code>long</code> to the stream message
750      *
751      * @param value the <code>long</code> to be written
752      * @throws JMSException if JMS fails to write message due to
753      * some internal JMS error
754      * @throws MessageNotWriteableException if message in read-only mode
755      */
756     public final void writeLong(long value) throws JMSException {
757         checkWrite();
758         try {
759             getOutputStream();
760             _out.writeByte(LONG);
761             _out.writeLong(value);
762         } catch (IOException exception) {
763             raise(exception);
764         }
765     }
766 
767     /***
768      * Write a <code>float</code> to the stream message
769      *
770      * @param value the <code>float</code> value to be written
771      * @throws JMSException if JMS fails to write message due to
772      * some internal JMS error
773      * @throws MessageNotWriteableException if message in read-only mode
774      */
775     public final void writeFloat(float value) throws JMSException {
776         checkWrite();
777         try {
778             getOutputStream();
779             _out.writeByte(FLOAT);
780             _out.writeFloat(value);
781         } catch (IOException exception) {
782             raise(exception);
783         }
784     }
785 
786     /***
787      * Write a <code>double</code> to the stream message
788      *
789      * @param value the <code>double</code> value to be written
790      * @throws JMSException if JMS fails to write message due to
791      * some internal JMS error
792      * @throws MessageNotWriteableException if message in read-only mode
793      */
794     public final void writeDouble(double value) throws JMSException {
795         checkWrite();
796         try {
797             getOutputStream();
798             _out.writeByte(DOUBLE);
799             _out.writeDouble(value);
800         } catch (IOException exception) {
801             raise(exception);
802         }
803     }
804 
805     /***
806      * Write a string to the stream message
807      *
808      * @param value the <code>String</code> value to be written
809      * @throws JMSException if JMS fails to write message due to
810      * some internal JMS error
811      * @throws MessageNotWriteableException if message in read-only mode
812      * @throws NullPointerException if value is null
813      */
814     public final void writeString(String value) throws JMSException {
815         checkWrite();
816         if (value == null) {
817             // could throw IllegalArgumentException, but this is in keeping
818             // with that thrown by DataOutputStream
819             throw new NullPointerException("Argument value is null");
820         }
821         try {
822             getOutputStream();
823             _out.writeByte(STRING);
824             _out.writeUTF(value);
825         } catch (IOException exception) {
826             raise(exception);
827         }
828     }
829 
830     /***
831      * Write a byte array field to the stream message
832      * <p>
833      * The byte array <code>value</code> is written as a byte array field
834      * into the StreamMessage. Consecutively written byte array fields are
835      * treated as two distinct fields when reading byte array fields.
836      *
837      * @param value the byte array to be written
838      * @throws JMSException if JMS fails to write message due to
839      * some internal JMS error
840      * @throws MessageNotWriteableException if message in read-only mode
841      * @throws NullPointerException if value is null
842      */
843     public final void writeBytes(byte[] value) throws JMSException {
844         checkWrite();
845         if (value == null) {
846             // could throw IllegalArgumentException, but this is in keeping
847             // with that thrown by DataOutputStream
848             throw new NullPointerException("Argument value is null");
849         }
850         try {
851             getOutputStream();
852             _out.writeByte(BYTE_ARRAY);
853             _out.writeInt(value.length);
854             _out.write(value);
855         } catch (IOException exception) {
856             raise(exception);
857         }
858     }
859 
860     /***
861      * Write a portion of a byte array as a byte array field to the stream
862      * message
863      * <p>
864      * The a portion of the byte array <code>value</code> is written as a
865      * byte array field into the StreamMessage. Consecutively written byte
866      * array fields are treated as two distinct fields when reading byte
867      * array fields.
868      *
869      * @param value the byte array value to be written
870      * @param offset the initial offset within the byte array
871      * @param length the number of bytes to write
872      * @throws JMSException if JMS fails to write message due to
873      * some internal JMS error
874      * @throws MessageNotWriteableException if message in read-only mode
875      * @throws NullPointerException if value is null
876      */
877     public void writeBytes(byte[] value, int offset, int length)
878         throws JMSException {
879         checkWrite();
880         if (value == null) {
881             // could throw IllegalArgumentException, but this is in keeping
882             // with that thrown by DataOutputStream
883             throw new NullPointerException("Argument value is null");
884         }
885         try {
886             getOutputStream();
887             _out.writeByte(BYTE_ARRAY);
888             _out.writeInt(length);
889             _out.write(value, offset, length);
890         } catch (IOException exception) {
891             raise(exception);
892         }
893     }
894 
895     /***
896      * Write a Java object to the stream message
897      * <p>
898      * Note that this method only works for the objectified primitive
899      * object types (Integer, Double, Long ...), String's and byte arrays.
900      *
901      * @param value the Java object to be written
902      * @throws JMSException if JMS fails to write message due to
903      * some internal JMS error
904      * @throws MessageFormatException if the object is invalid
905      * @throws MessageNotWriteableException if message in read-only mode
906      */
907     public void writeObject(Object value) throws JMSException {
908         if (value == null) {
909             try {
910                 checkWrite();
911                 getOutputStream();
912                 _out.writeByte(NULL);
913             } catch (IOException exception) {
914                 raise(exception);
915             }
916         } else if (value instanceof Boolean) {
917             writeBoolean(((Boolean) value).booleanValue());
918         } else if (value instanceof Byte) {
919             writeByte(((Byte) value).byteValue());
920         } else if (value instanceof byte[]) {
921             writeBytes((byte[]) value);
922         } else if (value instanceof Short) {
923             writeShort(((Short) value).shortValue());
924         } else if (value instanceof Character) {
925             writeChar(((Character) value).charValue());
926         } else if (value instanceof Integer) {
927             writeInt(((Integer) value).intValue());
928         } else if (value instanceof Long) {
929             writeLong(((Long) value).longValue());
930         } else if (value instanceof Float) {
931             writeFloat(((Float) value).floatValue());
932         } else if (value instanceof Double) {
933             writeDouble(((Double) value).doubleValue());
934         } else if (value instanceof String) {
935             writeString((String) value);
936         } else {
937             throw new MessageFormatException(
938                 "Objects of type " + value.getClass().getName()
939                 + " are not supported by StreamMessage");
940         }
941     }
942 
943     /***
944      * Put the message body in read-only mode, and reposition the stream
945      * to the beginning
946      *
947      * @throws JMSException if JMS fails to reset the message due to
948      * some internal JMS error
949      */
950     public void reset() throws JMSException {
951         try {
952             if (!_bodyReadOnly) {
953                 _bodyReadOnly = true;
954                 if (_out != null) {
955                     _out.flush();
956                     _bytes = _byteOut.toByteArray();
957                     _byteOut = null;
958                     _out.close();
959                     _out = null;
960                 }
961             } else {
962                 if (_in != null) {
963                     _byteIn = null;
964                     _in.close();
965                     _in = null;
966                 }
967             }
968             _readBytes = 0;
969         } catch (IOException exception) {
970             raise(exception);
971         }
972     }
973 
974     /***
975      * Overide the super class method to reset the streams, and put the
976      * message body in write only mode
977      *
978      * @throws JMSException if JMS fails to reset the message due to
979      * some internal JMS error.
980      */
981     public void clearBody() throws JMSException {
982         try {
983             if (_bodyReadOnly) {
984                 // in read-only mode
985                 _bodyReadOnly = false;
986                 if (_in != null) {
987                     _byteIn = null;
988                     _in.close();
989                     _in = null;
990                     _offset = 0;
991                 }
992             } else if (_out != null) {
993                 // already in write-only mode
994                 _byteOut = null;
995                 _out.close();
996                 _out = null;
997             }
998             _bytes = EMPTY;
999             _readBytes = 0;
1000         } catch (IOException exception) {
1001             raise(exception);
1002         }
1003     }
1004 
1005     /***
1006      * Set the read-only mode of the message. If read-only, resets the message
1007      * for reading
1008      *
1009      * @param readOnly if true, make the message body and properties
1010      * @throws JMSException if the read-only mode cannot be changed
1011      */
1012     public final void setReadOnly(boolean readOnly) throws JMSException {
1013         if (readOnly) {
1014             reset();
1015         }
1016         super.setReadOnly(readOnly);
1017     }
1018 
1019     /***
1020      * Prepare to do a read
1021      *
1022      * @throws JMSException if the current position in the stream can't be
1023      * marked
1024      * @throws MessageNotReadableException if the message is in write-only mode
1025      */
1026     private final void prepare() throws JMSException {
1027         checkRead();
1028         getInputStream();
1029         try {
1030             _in.mark(_bytes.length - _in.available());
1031         } catch (IOException exception) {
1032             raise(exception);
1033         }
1034     }
1035 
1036     /***
1037      * Reverts the stream to its prior position if a MessageFormatException is
1038      * thrown, and propagates the exception.
1039      *
1040      * @param exception the exception that caused the reset
1041      * @throws MessageFormatException
1042      */
1043     private void revert(MessageFormatException exception)
1044         throws MessageFormatException {
1045         try {
1046             _in.reset();
1047         } catch (IOException ignore) {
1048             // can't reset the stream, but need to propagate the original
1049             // exception
1050         }
1051         throw exception;
1052     }
1053 
1054     /***
1055      * Reverts the stream to its prior position if a NumberFormatException or
1056      * NullPointerException is thrown, and propagates the exception.
1057      *
1058      * @param exception the exception that caused the reset
1059      * @throws NullPointerException
1060      * @throws NumberFormatException
1061      */
1062     private void revert(RuntimeException exception) {
1063         try {
1064             _in.reset();
1065         } catch (IOException ignore) {
1066             // can't reset the stream, but need to propagate the original
1067             // exception
1068         }
1069         throw exception;
1070     }
1071 
1072     /***
1073      * Read the next object from the stream message
1074      *
1075      * @return a Java object from the stream message, in objectified
1076      * format (eg. if it set as an int, then a Integer is returned).
1077      * @throws JMSException if JMS fails to read message due to some internal
1078      * JMS error
1079      * @throws MessageEOFException if end of message stream
1080      * @throws MessageFormatException if a byte array has not been fully read
1081      * by {@link #readBytes(byte[])}
1082      * @throws MessageNotReadableException if the message is in write-only mode
1083      */
1084     private Object readNext() throws JMSException {
1085         if (_readBytes != 0) {
1086             throw new MessageFormatException(
1087                 "Cannot read the next field until the byte array is read");
1088         }
1089 
1090         byte type = 0;
1091         try {
1092             type = _in.readByte();
1093         } catch (IOException exception) {
1094             raise(exception);
1095         }
1096         if ((type & 0x0F) > TYPE_NAMES.length) {
1097             throw new JMSException("StreamMessage corrupted");
1098         }
1099         Object result = null;
1100 
1101         try {
1102             switch (type & 0x0F) {
1103                 case BOOLEAN:
1104                     boolean value = ((type & 0xF0) != 0) ? true : false;
1105                     result = new Boolean(value);
1106                     break;
1107                 case BYTE:
1108                     result = new Byte(_in.readByte());
1109                     break;
1110                 case BYTE_ARRAY:
1111                     int length = _in.readInt();
1112                     byte[] bytes = new byte[length];
1113                     _in.readFully(bytes);
1114                     result = bytes;
1115                     break;
1116                 case SHORT:
1117                     result = new Short(_in.readShort());
1118                     break;
1119                 case CHAR:
1120                     result = new Character(_in.readChar());
1121                     break;
1122                 case INT:
1123                     result = new Integer(_in.readInt());
1124                     break;
1125                 case LONG:
1126                     result = new Long(_in.readLong());
1127                     break;
1128                 case FLOAT:
1129                     result = new Float(_in.readFloat());
1130                     break;
1131                 case DOUBLE:
1132                     result = new Double(_in.readDouble());
1133                     break;
1134                 case STRING:
1135                     result = _in.readUTF();
1136                     break;
1137             }
1138         } catch (IOException exception) {
1139             raise(exception);
1140         }
1141 
1142         return result;
1143     }
1144 
1145     /***
1146      * Initialise the input stream if it hasn't been intialised
1147      *
1148      * @return the input stream
1149      */
1150     private DataInputStream getInputStream() {
1151         if (_in == null) {
1152             _byteIn = new ByteArrayInputStream(_bytes, _offset,
1153                 _bytes.length - _offset);
1154             _in = new DataInputStream(_byteIn);
1155         }
1156         return _in;
1157     }
1158 
1159     /***
1160      * Initialise the output stream if it hasn't been intialised
1161      *
1162      * @return the output stream
1163      * @throws IOException if the output stream can't be created
1164      */
1165     private final DataOutputStream getOutputStream() throws IOException {
1166         if (_out == null) {
1167             _byteOut = new ByteArrayOutputStream();
1168             _out = new DataOutputStream(_byteOut);
1169             _out.write(_bytes);
1170         }
1171         return _out;
1172     }
1173 
1174     /***
1175      * Helper to raise a JMSException when an I/O error occurs
1176      *
1177      * @param exception the exception that caused the failure
1178      * @throws JMSException
1179      */
1180     private final void raise(IOException exception) throws JMSException {
1181         JMSException error = null;
1182         if (exception instanceof EOFException) {
1183             error = new MessageEOFException(exception.getMessage());
1184         } else {
1185             error = new JMSException(exception.getMessage());
1186         }
1187         error.setLinkedException(exception);
1188         throw error;
1189     }
1190 
1191 }