View Javadoc

1   /*** 
2    * 
3    * Copyright 2004 Protique Ltd
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License"); 
6    * you may not use this file except in compliance with the License. 
7    * You may obtain a copy of the License at 
8    * 
9    * http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License. 
16   * 
17   **/
18  
19  package org.codehaus.activemq.message;
20  
21  import javax.jms.JMSException;
22  import javax.jms.MessageEOFException;
23  import javax.jms.MessageFormatException;
24  import javax.jms.MessageNotReadableException;
25  import javax.jms.MessageNotWriteableException;
26  import javax.jms.StreamMessage;
27  import java.io.ByteArrayInputStream;
28  import java.io.ByteArrayOutputStream;
29  import java.io.DataInputStream;
30  import java.io.DataOutputStream;
31  import java.io.EOFException;
32  import java.io.IOException;
33  
34  /***
35   * A <CODE>StreamMessage</CODE> object is used to send a stream of primitive
36   * types in the Java programming language. It is filled and read sequentially.
37   * It inherits from the <CODE>Message</CODE> interface
38   * and adds a stream message body. Its methods are based largely on those
39   * found in <CODE>java.io.DataInputStream</CODE> and
40   * <CODE>java.io.DataOutputStream</CODE>.
41   * <p/>
42   * <P>The primitive types can be read or written explicitly using methods
43   * for each type. They may also be read or written generically as objects.
44   * For instance, a call to <CODE>StreamMessage.writeInt(6)</CODE> is
45   * equivalent to <CODE>StreamMessage.writeObject(new Integer(6))</CODE>.
46   * Both forms are provided, because the explicit form is convenient for
47   * static programming, and the object form is needed when types are not known
48   * at compile time.
49   * <p/>
50   * <P>When the message is first created, and when <CODE>clearBody</CODE>
51   * is called, the body of the message is in write-only mode. After the
52   * first call to <CODE>reset</CODE> has been made, the message body is in
53   * read-only mode.
54   * After a message has been sent, the client that sent it can retain and
55   * modify it without affecting the message that has been sent. The same message
56   * object can be sent multiple times.
57   * When a message has been received, the provider has called
58   * <CODE>reset</CODE> so that the message body is in read-only mode for the client.
59   * <p/>
60   * <P>If <CODE>clearBody</CODE> is called on a message in read-only mode,
61   * the message body is cleared and the message body is in write-only mode.
62   * <p/>
63   * <P>If a client attempts to read a message in write-only mode, a
64   * <CODE>MessageNotReadableException</CODE> is thrown.
65   * <p/>
66   * <P>If a client attempts to write a message in read-only mode, a
67   * <CODE>MessageNotWriteableException</CODE> is thrown.
68   * <p/>
69   * <P><CODE>StreamMessage</CODE> objects support the following conversion
70   * table. The marked cases must be supported. The unmarked cases must throw a
71   * <CODE>JMSException</CODE>. The <CODE>String</CODE>-to-primitive conversions
72   * may throw a runtime exception if the primitive's <CODE>valueOf()</CODE>
73   * method does not accept it as a valid <CODE>String</CODE> representation of
74   * the primitive.
75   * <p/>
76   * <P>A value written as the row type can be read as the column type.
77   * <p/>
78   * <PRE>
79   * |        | boolean byte short char int long float double String byte[]
80   * |----------------------------------------------------------------------
81   * |boolean |    X                                            X
82   * |byte    |          X     X         X   X                  X
83   * |short   |                X         X   X                  X
84   * |char    |                     X                           X
85   * |int     |                          X   X                  X
86   * |long    |                              X                  X
87   * |float   |                                    X     X      X
88   * |double  |                                          X      X
89   * |String  |    X     X     X         X   X     X     X      X
90   * |byte[]  |                                                        X
91   * |----------------------------------------------------------------------
92   * </PRE>
93   * <p/>
94   * <P>Attempting to read a null value as a primitive type must be treated
95   * as calling the primitive's corresponding <code>valueOf(String)</code>
96   * conversion method with a null value. Since <code>char</code> does not
97   * support a <code>String</code> conversion, attempting to read a null value
98   * as a <code>char</code> must throw a <code>NullPointerException</code>.
99   *
100  * @see javax.jms.Session#createStreamMessage()
101  * @see javax.jms.BytesMessage
102  * @see javax.jms.MapMessage
103  * @see javax.jms.Message
104  * @see javax.jms.ObjectMessage
105  * @see javax.jms.TextMessage
106  */
107 
108 public class ActiveMQStreamMessage extends ActiveMQMessage implements StreamMessage {
109 
110     private DataOutputStream dataOut;
111     private ByteArrayOutputStream bytesOut;
112     private DataInputStream dataIn;
113     private int bytesToRead = -1;
114 
115 
116     /***
117      * Return the type of Packet
118      *
119      * @return integer representation of the type of Packet
120      */
121 
122     public int getPacketType() {
123         return ACTIVEMQ_STREAM_MESSAGE;
124     }
125 
126     /***
127      * @return Returns a shallow copy of the message instance
128      * @throws JMSException
129      */
130 
131     public ActiveMQMessage shallowCopy() throws JMSException {
132         ActiveMQStreamMessage other = new ActiveMQStreamMessage();
133         this.initializeOther(other);
134         try {
135             other.setBodyAsBytes(this.getBodyAsBytes());
136         }
137         catch (IOException e) {
138             JMSException jmsEx = new JMSException("setBodyAsBytes() failed");
139             jmsEx.setLinkedException(e);
140             throw jmsEx;
141         }
142         return other;
143     }
144 
145     /***
146      * @return Returns a deep copy of the message - note the header fields are only shallow copied
147      * @throws JMSException
148      */
149 
150     public ActiveMQMessage deepCopy() throws JMSException {
151         ActiveMQStreamMessage other = new ActiveMQStreamMessage();
152         this.initializeOther(other);
153         try {
154             if (this.getBodyAsBytes() != null) {
155                 byte[] data = new byte[this.getBodyAsBytes().length];
156                 System.arraycopy(this.getBodyAsBytes(), 0, data, 0, data.length);
157                 other.setBodyAsBytes(data);
158             }
159         }
160         catch (IOException e) {
161             JMSException jmsEx = new JMSException("setBodyAsBytes() failed");
162             jmsEx.setLinkedException(e);
163             throw jmsEx;
164         }
165         return other;
166     }
167 
168 
169     /***
170      * Clears out the message body. Clearing a message's body does not clear
171      * its header values or property entries.
172      * <p/>
173      * <P>If this message body was read-only, calling this method leaves
174      * the message body in the same state as an empty body in a newly
175      * created message.
176      *
177      * @throws JMSException if the JMS provider fails to clear the message
178      *                      body due to some internal error.
179      */
180 
181     public void clearBody() throws JMSException {
182         super.clearBody();
183         this.dataOut = null;
184         this.dataIn = null;
185         this.bytesOut = null;
186     }
187 
188     /***
189      * Reads a <code>boolean</code> from the stream message.
190      *
191      * @return the <code>boolean</code> value read
192      * @throws JMSException                if the JMS provider fails to read the message
193      *                                     due to some internal error.
194      * @throws MessageEOFException         if unexpected end of message stream has
195      *                                     been reached.
196      * @throws MessageFormatException      if this type conversion is invalid.
197      * @throws MessageNotReadableException if the message is in write-only
198      *                                     mode.
199      */
200 
201     public boolean readBoolean() throws JMSException {
202         initializeReading();
203         try {
204             if (this.dataIn.available() == 0) {
205                 throw new MessageEOFException("reached end of data");
206             }
207             if (this.dataIn.available() < 2) {
208                 throw new MessageFormatException("Not enough data left to read value");
209             }
210 
211             this.dataIn.mark(10);
212             int type = this.dataIn.read();
213             if (type == BOOLEAN) {
214                 return this.dataIn.readBoolean();
215             }
216             if (type == STRING) {
217                 return Boolean.valueOf(this.dataIn.readUTF()).booleanValue();
218             }
219             else {
220                 this.dataIn.reset();
221                 throw new MessageFormatException(" not a boolean type");
222             }
223         }
224         catch (EOFException e) {
225             JMSException jmsEx = new MessageEOFException(e.getMessage());
226             jmsEx.setLinkedException(e);
227             throw jmsEx;
228         }
229         catch (IOException e) {
230             JMSException jmsEx = new MessageFormatException(e.getMessage());
231             jmsEx.setLinkedException(e);
232             throw jmsEx;
233         }
234     }
235 
236 
237     /***
238      * Reads a <code>byte</code> value from the stream message.
239      *
240      * @return the next byte from the stream message as a 8-bit
241      *         <code>byte</code>
242      * @throws JMSException                if the JMS provider fails to read the message
243      *                                     due to some internal error.
244      * @throws MessageEOFException         if unexpected end of message stream has
245      *                                     been reached.
246      * @throws MessageFormatException      if this type conversion is invalid.
247      * @throws MessageNotReadableException if the message is in write-only
248      *                                     mode.
249      */
250 
251     public byte readByte() throws JMSException {
252         initializeReading();
253         try {
254             if (this.dataIn.available() == 0) {
255                 throw new MessageEOFException("reached end of data");
256             }
257             if (this.dataIn.available() < 2) {
258                 throw new MessageFormatException("Not enough data left to read value");
259             }
260 
261             this.dataIn.mark(10);
262             int type = this.dataIn.read();
263             if (type == BYTE) {
264                 return this.dataIn.readByte();
265             }
266             if (type == STRING) {
267                 return Byte.valueOf(this.dataIn.readUTF()).byteValue();
268             }
269             else {
270                 this.dataIn.reset();
271                 throw new MessageFormatException(" not a byte type");
272             }
273         }
274         catch (NumberFormatException mfe) {
275             try {
276                 this.dataIn.reset();
277             }
278             catch (IOException ioe) {
279                 JMSException jmsEx = new JMSException("reset failed");
280                 jmsEx.setLinkedException(ioe);
281             }
282             throw mfe;
283 
284         }
285         catch (EOFException e) {
286             JMSException jmsEx = new MessageEOFException(e.getMessage());
287             jmsEx.setLinkedException(e);
288             throw jmsEx;
289         }
290         catch (IOException e) {
291             JMSException jmsEx = new MessageFormatException(e.getMessage());
292             jmsEx.setLinkedException(e);
293             throw jmsEx;
294         }
295     }
296 
297 
298     /***
299      * Reads a 16-bit integer from the stream message.
300      *
301      * @return a 16-bit integer from the stream message
302      * @throws JMSException                if the JMS provider fails to read the message
303      *                                     due to some internal error.
304      * @throws MessageEOFException         if unexpected end of message stream has
305      *                                     been reached.
306      * @throws MessageFormatException      if this type conversion is invalid.
307      * @throws MessageNotReadableException if the message is in write-only
308      *                                     mode.
309      */
310 
311     public short readShort() throws JMSException {
312         initializeReading();
313         try {
314             if (this.dataIn.available() == 0) {
315                 throw new MessageEOFException("reached end of data");
316             }
317             if (this.dataIn.available() < 2) {
318                 throw new MessageFormatException("Not enough data left to read value");
319             }
320 
321             this.dataIn.mark(17);
322             int type = this.dataIn.read();
323             if (type == SHORT) {
324                 return this.dataIn.readShort();
325             }
326             if (type == BYTE) {
327                 return this.dataIn.readByte();
328             }
329             if (type == STRING) {
330                 return Short.valueOf(this.dataIn.readUTF()).shortValue();
331             }
332             else {
333                 this.dataIn.reset();
334                 throw new MessageFormatException(" not a short type");
335             }
336         }
337         catch (NumberFormatException mfe) {
338             try {
339                 this.dataIn.reset();
340             }
341             catch (IOException ioe) {
342                 JMSException jmsEx = new JMSException("reset failed");
343                 jmsEx.setLinkedException(ioe);
344             }
345             throw mfe;
346 
347         }
348         catch (EOFException e) {
349             JMSException jmsEx = new MessageEOFException(e.getMessage());
350             jmsEx.setLinkedException(e);
351             throw jmsEx;
352         }
353         catch (IOException e) {
354             JMSException jmsEx = new MessageFormatException(e.getMessage());
355             jmsEx.setLinkedException(e);
356             throw jmsEx;
357         }
358 
359     }
360 
361 
362     /***
363      * Reads a Unicode character value from the stream message.
364      *
365      * @return a Unicode character from the stream message
366      * @throws JMSException                if the JMS provider fails to read the message
367      *                                     due to some internal error.
368      * @throws MessageEOFException         if unexpected end of message stream has
369      *                                     been reached.
370      * @throws MessageFormatException      if this type conversion is invalid
371      * @throws MessageNotReadableException if the message is in write-only
372      *                                     mode.
373      */
374 
375     public char readChar() throws JMSException {
376         initializeReading();
377         try {
378             if (this.dataIn.available() == 0) {
379                 throw new MessageEOFException("reached end of data");
380             }
381             if (this.dataIn.available() < 2) {
382                 throw new MessageFormatException("Not enough data left to read value");
383             }
384 
385             this.dataIn.mark(17);
386             int type = this.dataIn.read();
387             if (type == CHAR) {
388                 return this.dataIn.readChar();
389             }
390             else {
391                 this.dataIn.reset();
392                 throw new MessageFormatException(" not a char type");
393             }
394         }
395         catch (NumberFormatException mfe) {
396             try {
397                 this.dataIn.reset();
398             }
399             catch (IOException ioe) {
400                 JMSException jmsEx = new JMSException("reset failed");
401                 jmsEx.setLinkedException(ioe);
402             }
403             throw mfe;
404 
405         }
406         catch (EOFException e) {
407             JMSException jmsEx = new MessageEOFException(e.getMessage());
408             jmsEx.setLinkedException(e);
409             throw jmsEx;
410         }
411         catch (IOException e) {
412             JMSException jmsEx = new MessageFormatException(e.getMessage());
413             jmsEx.setLinkedException(e);
414             throw jmsEx;
415         }
416     }
417 
418 
419     /***
420      * Reads a 32-bit integer from the stream message.
421      *
422      * @return a 32-bit integer value from the stream message, interpreted
423      *         as an <code>int</code>
424      * @throws JMSException                if the JMS provider fails to read the message
425      *                                     due to some internal error.
426      * @throws MessageEOFException         if unexpected end of message stream has
427      *                                     been reached.
428      * @throws MessageFormatException      if this type conversion is invalid.
429      * @throws MessageNotReadableException if the message is in write-only
430      *                                     mode.
431      */
432 
433     public int readInt() throws JMSException {
434         initializeReading();
435         try {
436             if (this.dataIn.available() == 0) {
437                 throw new MessageEOFException("reached end of data");
438             }
439             if (this.dataIn.available() < 2) {
440                 throw new MessageFormatException("Not enough data left to read value");
441             }
442 
443             this.dataIn.mark(33);
444             int type = this.dataIn.read();
445             if (type == INT) {
446                 return this.dataIn.readInt();
447             }
448             if (type == SHORT) {
449                 return this.dataIn.readShort();
450             }
451             if (type == BYTE) {
452                 return this.dataIn.readByte();
453             }
454             if (type == STRING) {
455                 return Integer.valueOf(this.dataIn.readUTF()).intValue();
456             }
457             else {
458                 this.dataIn.reset();
459                 throw new MessageFormatException(" not an int type");
460             }
461         }
462         catch (NumberFormatException mfe) {
463             try {
464                 this.dataIn.reset();
465             }
466             catch (IOException ioe) {
467                 JMSException jmsEx = new JMSException("reset failed");
468                 jmsEx.setLinkedException(ioe);
469             }
470             throw mfe;
471 
472         }
473         catch (EOFException e) {
474             JMSException jmsEx = new MessageEOFException(e.getMessage());
475             jmsEx.setLinkedException(e);
476             throw jmsEx;
477         }
478         catch (IOException e) {
479             JMSException jmsEx = new MessageFormatException(e.getMessage());
480             jmsEx.setLinkedException(e);
481             throw jmsEx;
482         }
483     }
484 
485 
486     /***
487      * Reads a 64-bit integer from the stream message.
488      *
489      * @return a 64-bit integer value from the stream message, interpreted as
490      *         a <code>long</code>
491      * @throws JMSException                if the JMS provider fails to read the message
492      *                                     due to some internal error.
493      * @throws MessageEOFException         if unexpected end of message stream has
494      *                                     been reached.
495      * @throws MessageFormatException      if this type conversion is invalid.
496      * @throws MessageNotReadableException if the message is in write-only
497      *                                     mode.
498      */
499 
500     public long readLong() throws JMSException {
501         initializeReading();
502         try {
503             if (this.dataIn.available() == 0) {
504                 throw new MessageEOFException("reached end of data");
505             }
506             if (this.dataIn.available() < 2) {
507                 throw new MessageFormatException("Not enough data left to read value");
508             }
509 
510             this.dataIn.mark(65);
511             int type = this.dataIn.read();
512             if (type == LONG) {
513                 return this.dataIn.readLong();
514             }
515             if (type == INT) {
516                 return this.dataIn.readInt();
517             }
518             if (type == SHORT) {
519                 return this.dataIn.readShort();
520             }
521             if (type == BYTE) {
522                 return this.dataIn.readByte();
523             }
524             if (type == STRING) {
525                 return Long.valueOf(this.dataIn.readUTF()).longValue();
526             }
527             else {
528                 this.dataIn.reset();
529                 throw new MessageFormatException(" not a long type");
530             }
531         }
532         catch (NumberFormatException mfe) {
533             try {
534                 this.dataIn.reset();
535             }
536             catch (IOException ioe) {
537                 JMSException jmsEx = new JMSException("reset failed");
538                 jmsEx.setLinkedException(ioe);
539             }
540             throw mfe;
541 
542         }
543         catch (EOFException e) {
544             JMSException jmsEx = new MessageEOFException(e.getMessage());
545             jmsEx.setLinkedException(e);
546             throw jmsEx;
547         }
548         catch (IOException e) {
549             JMSException jmsEx = new MessageFormatException(e.getMessage());
550             jmsEx.setLinkedException(e);
551             throw jmsEx;
552         }
553     }
554 
555 
556     /***
557      * Reads a <code>float</code> from the stream message.
558      *
559      * @return a <code>float</code> value from the stream message
560      * @throws JMSException                if the JMS provider fails to read the message
561      *                                     due to some internal error.
562      * @throws MessageEOFException         if unexpected end of message stream has
563      *                                     been reached.
564      * @throws MessageFormatException      if this type conversion is invalid.
565      * @throws MessageNotReadableException if the message is in write-only
566      *                                     mode.
567      */
568 
569     public float readFloat() throws JMSException {
570         initializeReading();
571         try {
572             if (this.dataIn.available() == 0) {
573                 throw new MessageEOFException("reached end of data");
574             }
575             if (this.dataIn.available() < 2) {
576                 throw new MessageFormatException("Not enough data left to read value");
577             }
578 
579             this.dataIn.mark(33);
580             int type = this.dataIn.read();
581             if (type == FLOAT) {
582                 return this.dataIn.readFloat();
583             }
584             if (type == STRING) {
585                 return Float.valueOf(this.dataIn.readUTF()).floatValue();
586             }
587             else {
588                 this.dataIn.reset();
589                 throw new MessageFormatException(" not a float type");
590             }
591         }
592         catch (NumberFormatException mfe) {
593             try {
594                 this.dataIn.reset();
595             }
596             catch (IOException ioe) {
597                 JMSException jmsEx = new JMSException("reset failed");
598                 jmsEx.setLinkedException(ioe);
599             }
600             throw mfe;
601 
602         }
603         catch (EOFException e) {
604             JMSException jmsEx = new MessageEOFException(e.getMessage());
605             jmsEx.setLinkedException(e);
606             throw jmsEx;
607         }
608         catch (IOException e) {
609             JMSException jmsEx = new MessageFormatException(e.getMessage());
610             jmsEx.setLinkedException(e);
611             throw jmsEx;
612         }
613     }
614 
615 
616     /***
617      * Reads a <code>double</code> from the stream message.
618      *
619      * @return a <code>double</code> value from the stream message
620      * @throws JMSException                if the JMS provider fails to read the message
621      *                                     due to some internal error.
622      * @throws MessageEOFException         if unexpected end of message stream has
623      *                                     been reached.
624      * @throws MessageFormatException      if this type conversion is invalid.
625      * @throws MessageNotReadableException if the message is in write-only
626      *                                     mode.
627      */
628 
629     public double readDouble() throws JMSException {
630         initializeReading();
631         try {
632             if (this.dataIn.available() == 0) {
633                 throw new MessageEOFException("reached end of data");
634             }
635             if (this.dataIn.available() < 2) {
636                 throw new MessageFormatException("Not enough data left to read value");
637             }
638 
639             this.dataIn.mark(65);
640             int type = this.dataIn.read();
641             if (type == DOUBLE) {
642                 return this.dataIn.readDouble();
643             }
644             if (type == FLOAT) {
645                 return this.dataIn.readFloat();
646             }
647             if (type == STRING) {
648                 return Double.valueOf(this.dataIn.readUTF()).doubleValue();
649             }
650             else {
651                 this.dataIn.reset();
652                 throw new MessageFormatException(" not a double type");
653             }
654         }
655         catch (NumberFormatException mfe) {
656             try {
657                 this.dataIn.reset();
658             }
659             catch (IOException ioe) {
660                 JMSException jmsEx = new JMSException("reset failed");
661                 jmsEx.setLinkedException(ioe);
662             }
663             throw mfe;
664 
665         }
666         catch (EOFException e) {
667             JMSException jmsEx = new MessageEOFException(e.getMessage());
668             jmsEx.setLinkedException(e);
669             throw jmsEx;
670         }
671         catch (IOException e) {
672             JMSException jmsEx = new MessageFormatException(e.getMessage());
673             jmsEx.setLinkedException(e);
674             throw jmsEx;
675         }
676     }
677 
678 
679     /***
680      * Reads a <CODE>String</CODE> from the stream message.
681      *
682      * @return a Unicode string from the stream message
683      * @throws JMSException                if the JMS provider fails to read the message
684      *                                     due to some internal error.
685      * @throws MessageEOFException         if unexpected end of message stream has
686      *                                     been reached.
687      * @throws MessageFormatException      if this type conversion is invalid.
688      * @throws MessageNotReadableException if the message is in write-only
689      *                                     mode.
690      */
691 
692     public String readString() throws JMSException {
693         initializeReading();
694         try {
695             if (this.dataIn.available() == 0) {
696                 throw new MessageEOFException("reached end of data");
697             }
698             if (this.dataIn.available() < 2) {
699                 throw new MessageFormatException("Not enough data left to read value");
700             }
701 
702             this.dataIn.mark(65);
703             int type = this.dataIn.read();
704             if (type == NULL) {
705                 return null;
706             }
707             if (type == STRING) {
708                 return this.dataIn.readUTF();
709             }
710             if (type == LONG) {
711                 return new Long(this.dataIn.readLong()).toString();
712             }
713             if (type == INT) {
714                 return new Integer(this.dataIn.readInt()).toString();
715             }
716             if (type == SHORT) {
717                 return new Short(this.dataIn.readShort()).toString();
718             }
719             if (type == BYTE) {
720                 return new Byte(this.dataIn.readByte()).toString();
721             }
722             if (type == FLOAT) {
723                 return new Float(this.dataIn.readFloat()).toString();
724             }
725             if (type == DOUBLE) {
726                 return new Double(this.dataIn.readDouble()).toString();
727             }
728             if (type == BOOLEAN) {
729                 return (this.dataIn.readBoolean() ? Boolean.TRUE : Boolean.FALSE).toString();
730             }
731             if (type == CHAR) {
732                 return new Character(this.dataIn.readChar()).toString();
733             }
734             else {
735                 this.dataIn.reset();
736                 throw new MessageFormatException(" not a String type");
737             }
738         }
739         catch (NumberFormatException mfe) {
740             try {
741                 this.dataIn.reset();
742             }
743             catch (IOException ioe) {
744                 JMSException jmsEx = new JMSException("reset failed");
745                 jmsEx.setLinkedException(ioe);
746             }
747             throw mfe;
748 
749         }
750         catch (EOFException e) {
751             JMSException jmsEx = new MessageEOFException(e.getMessage());
752             jmsEx.setLinkedException(e);
753             throw jmsEx;
754         }
755         catch (IOException e) {
756             JMSException jmsEx = new MessageFormatException(e.getMessage());
757             jmsEx.setLinkedException(e);
758             throw jmsEx;
759         }
760     }
761 
762 
763     /***
764      * Reads a byte array field from the stream message into the
765      * specified <CODE>byte[]</CODE> object (the read buffer).
766      * <p/>
767      * <P>To read the field value, <CODE>readBytes</CODE> should be
768      * successively called
769      * until it returns a value less than the length of the read buffer.
770      * The value of the bytes in the buffer following the last byte
771      * read is undefined.
772      * <p/>
773      * <P>If <CODE>readBytes</CODE> returns a value equal to the length of the
774      * buffer, a subsequent <CODE>readBytes</CODE> call must be made. If there
775      * are no more bytes to be read, this call returns -1.
776      * <p/>
777      * <P>If the byte array field value is null, <CODE>readBytes</CODE>
778      * returns -1.
779      * <p/>
780      * <P>If the byte array field value is empty, <CODE>readBytes</CODE>
781      * returns 0.
782      * <p/>
783      * <P>Once the first <CODE>readBytes</CODE> call on a <CODE>byte[]</CODE>
784      * field value has been made,
785      * the full value of the field must be read before it is valid to read
786      * the next field. An attempt to read the next field before that has
787      * been done will throw a <CODE>MessageFormatException</CODE>.
788      * <p/>
789      * <P>To read the byte field value into a new <CODE>byte[]</CODE> object,
790      * use the <CODE>readObject</CODE> method.
791      *
792      * @param value the buffer into which the data is read
793      * @return the total number of bytes read into the buffer, or -1 if
794      *         there is no more data because the end of the byte field has been
795      *         reached
796      * @throws JMSException                if the JMS provider fails to read the message
797      *                                     due to some internal error.
798      * @throws MessageEOFException         if unexpected end of message stream has
799      *                                     been reached.
800      * @throws MessageFormatException      if this type conversion is invalid.
801      * @throws MessageNotReadableException if the message is in write-only
802      *                                     mode.
803      * @see #readObject()
804      */
805 
806     public int readBytes(byte[] value) throws JMSException {
807         initializeReading();
808         try {
809             if (value == null) {
810                 throw new NullPointerException();
811             }
812             if (bytesToRead == 0) {
813                 bytesToRead = -1;
814                 return -1;
815             }
816             else if (bytesToRead > 0) {
817                 if (value.length >= bytesToRead) {
818                     bytesToRead = 0;
819                     return dataIn.read(value, 0, bytesToRead);
820                 }
821                 else {
822                     bytesToRead -= value.length;
823                     return dataIn.read(value);
824                 }
825             }
826             else {
827                 if (this.dataIn.available() == 0) {
828                     throw new MessageEOFException("reached end of data");
829                 }
830                 if (this.dataIn.available() < 1) {
831                     throw new MessageFormatException("Not enough data left to read value");
832                 }
833                 this.dataIn.mark(value.length + 1);
834                 int type = this.dataIn.read();
835                 if (this.dataIn.available() < 1) {
836                     return -1;
837                 }
838                 if (type != BYTES) {
839                     throw new MessageFormatException("Not a byte array");
840                 }
841                 int len = this.dataIn.readInt();
842 
843                 if (len >= value.length) {
844                     bytesToRead = len - value.length;
845                     return this.dataIn.read(value);
846                 }
847                 else {
848                     bytesToRead = 0;
849                     return this.dataIn.read(value, 0, len);
850                 }
851             }
852         }
853         catch (EOFException e) {
854             JMSException jmsEx = new MessageEOFException(e.getMessage());
855             jmsEx.setLinkedException(e);
856             throw jmsEx;
857         }
858         catch (IOException e) {
859             JMSException jmsEx = new MessageFormatException(e.getMessage());
860             jmsEx.setLinkedException(e);
861             throw jmsEx;
862         }
863     }
864 
865 
866     /***
867      * Reads an object from the stream message.
868      * <p/>
869      * <P>This method can be used to return, in objectified format,
870      * an object in the Java programming language ("Java object") that has
871      * been written to the stream with the equivalent
872      * <CODE>writeObject</CODE> method call, or its equivalent primitive
873      * <CODE>write<I>type</I></CODE> method.
874      * <p/>
875      * <P>Note that byte values are returned as <CODE>byte[]</CODE>, not
876      * <CODE>Byte[]</CODE>.
877      * <p/>
878      * <P>An attempt to call <CODE>readObject</CODE> to read a byte field
879      * value into a new <CODE>byte[]</CODE> object before the full value of the
880      * byte field has been read will throw a
881      * <CODE>MessageFormatException</CODE>.
882      *
883      * @return a Java object from the stream message, in objectified
884      *         format (for example, if the object was written as an <CODE>int</CODE>,
885      *         an <CODE>Integer</CODE> is returned)
886      * @throws JMSException                if the JMS provider fails to read the message
887      *                                     due to some internal error.
888      * @throws MessageEOFException         if unexpected end of message stream has
889      *                                     been reached.
890      * @throws MessageFormatException      if this type conversion is invalid.
891      * @throws MessageNotReadableException if the message is in write-only
892      *                                     mode.
893      * @see #readBytes(byte[] value)
894      */
895 
896     public Object readObject() throws JMSException {
897         initializeReading();
898         try {
899             if (this.dataIn.available() == 0) {
900                 throw new MessageEOFException("reached end of data");
901             }
902             if (this.dataIn.available() < 1) {
903                 throw new MessageFormatException("Not enough data left to read value - avaialable = " + dataIn.available());
904             }
905 
906             this.dataIn.mark(65);
907             int type = this.dataIn.read();
908             if (type == NULL) {
909                 return null;
910             }
911             if (type == STRING) {
912                 return this.dataIn.readUTF();
913             }
914             if (type == LONG) {
915                 return new Long(this.dataIn.readLong());
916             }
917             if (type == INT) {
918                 return new Integer(this.dataIn.readInt());
919             }
920             if (type == SHORT) {
921                 return new Short(this.dataIn.readShort());
922             }
923             if (type == BYTE) {
924                 return new Byte(this.dataIn.readByte());
925             }
926             if (type == FLOAT) {
927                 return new Float(this.dataIn.readFloat());
928             }
929             if (type == DOUBLE) {
930                 return new Double(this.dataIn.readDouble());
931             }
932             if (type == BOOLEAN) {
933                 return this.dataIn.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
934             }
935             if (type == CHAR) {
936                 return new Character(this.dataIn.readChar());
937             }
938             if (type == BYTES) {
939                 int len = this.dataIn.readInt();
940                 byte[] value = new byte[len];
941                 this.dataIn.read(value);
942                 return value;
943             }
944             else {
945                 this.dataIn.reset();
946                 throw new MessageFormatException("unknown type");
947             }
948         }
949         catch (NumberFormatException mfe) {
950             try {
951                 this.dataIn.reset();
952             }
953             catch (IOException ioe) {
954                 JMSException jmsEx = new JMSException("reset failed");
955                 jmsEx.setLinkedException(ioe);
956             }
957             throw mfe;
958 
959         }
960         catch (EOFException e) {
961             JMSException jmsEx = new MessageEOFException(e.getMessage());
962             jmsEx.setLinkedException(e);
963             throw jmsEx;
964         }
965         catch (IOException e) {
966             JMSException jmsEx = new MessageFormatException(e.getMessage());
967             jmsEx.setLinkedException(e);
968             throw jmsEx;
969         }
970     }
971 
972 
973     /***
974      * Writes a <code>boolean</code> to the stream message.
975      * The value <code>true</code> is written as the value
976      * <code>(byte)1</code>; the value <code>false</code> is written as
977      * the value <code>(byte)0</code>.
978      *
979      * @param value the <code>boolean</code> value to be written
980      * @throws JMSException                 if the JMS provider fails to write the message
981      *                                      due to some internal error.
982      * @throws MessageNotWriteableException if the message is in read-only
983      *                                      mode.
984      */
985 
986     public void writeBoolean(boolean value) throws JMSException {
987         initializeWriting();
988         try {
989             this.dataOut.write(BOOLEAN);
990             this.dataOut.writeBoolean(value);
991         }
992         catch (IOException ioe) {
993             JMSException jmsEx = new JMSException(ioe.getMessage());
994             jmsEx.setLinkedException(ioe);
995             throw jmsEx;
996         }
997     }
998 
999 
1000     /***
1001      * Writes a <code>byte</code> to the stream message.
1002      *
1003      * @param value the <code>byte</code> value to be written
1004      * @throws JMSException                 if the JMS provider fails to write the message
1005      *                                      due to some internal error.
1006      * @throws MessageNotWriteableException if the message is in read-only
1007      *                                      mode.
1008      */
1009 
1010     public void writeByte(byte value) throws JMSException {
1011         initializeWriting();
1012         try {
1013             this.dataOut.write(BYTE);
1014             this.dataOut.writeByte(value);
1015         }
1016         catch (IOException ioe) {
1017             JMSException jmsEx = new JMSException(ioe.getMessage());
1018             jmsEx.setLinkedException(ioe);
1019             throw jmsEx;
1020         }
1021     }
1022 
1023 
1024     /***
1025      * Writes a <code>short</code> to the stream message.
1026      *
1027      * @param value the <code>short</code> value to be written
1028      * @throws JMSException                 if the JMS provider fails to write the message
1029      *                                      due to some internal error.
1030      * @throws MessageNotWriteableException if the message is in read-only
1031      *                                      mode.
1032      */
1033 
1034     public void writeShort(short value) throws JMSException {
1035         initializeWriting();
1036         try {
1037             this.dataOut.write(SHORT);
1038             this.dataOut.writeShort(value);
1039         }
1040         catch (IOException ioe) {
1041             JMSException jmsEx = new JMSException(ioe.getMessage());
1042             jmsEx.setLinkedException(ioe);
1043             throw jmsEx;
1044         }
1045     }
1046 
1047 
1048     /***
1049      * Writes a <code>char</code> to the stream message.
1050      *
1051      * @param value the <code>char</code> value to be written
1052      * @throws JMSException                 if the JMS provider fails to write the message
1053      *                                      due to some internal error.
1054      * @throws MessageNotWriteableException if the message is in read-only
1055      *                                      mode.
1056      */
1057 
1058     public void writeChar(char value) throws JMSException {
1059         initializeWriting();
1060         try {
1061             this.dataOut.write(CHAR);
1062             this.dataOut.writeChar(value);
1063         }
1064         catch (IOException ioe) {
1065             JMSException jmsEx = new JMSException(ioe.getMessage());
1066             jmsEx.setLinkedException(ioe);
1067             throw jmsEx;
1068         }
1069     }
1070 
1071 
1072     /***
1073      * Writes an <code>int</code> to the stream message.
1074      *
1075      * @param value the <code>int</code> value to be written
1076      * @throws JMSException                 if the JMS provider fails to write the message
1077      *                                      due to some internal error.
1078      * @throws MessageNotWriteableException if the message is in read-only
1079      *                                      mode.
1080      */
1081 
1082     public void writeInt(int value) throws JMSException {
1083         initializeWriting();
1084         try {
1085             this.dataOut.write(INT);
1086             this.dataOut.writeInt(value);
1087         }
1088         catch (IOException ioe) {
1089             JMSException jmsEx = new JMSException(ioe.getMessage());
1090             jmsEx.setLinkedException(ioe);
1091             throw jmsEx;
1092         }
1093     }
1094 
1095 
1096     /***
1097      * Writes a <code>long</code> to the stream message.
1098      *
1099      * @param value the <code>long</code> value to be written
1100      * @throws JMSException                 if the JMS provider fails to write the message
1101      *                                      due to some internal error.
1102      * @throws MessageNotWriteableException if the message is in read-only
1103      *                                      mode.
1104      */
1105 
1106     public void writeLong(long value) throws JMSException {
1107         initializeWriting();
1108         try {
1109             this.dataOut.write(LONG);
1110             this.dataOut.writeLong(value);
1111         }
1112         catch (IOException ioe) {
1113             JMSException jmsEx = new JMSException(ioe.getMessage());
1114             jmsEx.setLinkedException(ioe);
1115             throw jmsEx;
1116         }
1117     }
1118 
1119 
1120     /***
1121      * Writes a <code>float</code> to the stream message.
1122      *
1123      * @param value the <code>float</code> value to be written
1124      * @throws JMSException                 if the JMS provider fails to write the message
1125      *                                      due to some internal error.
1126      * @throws MessageNotWriteableException if the message is in read-only
1127      *                                      mode.
1128      */
1129 
1130     public void writeFloat(float value) throws JMSException {
1131         initializeWriting();
1132         try {
1133             this.dataOut.write(FLOAT);
1134             this.dataOut.writeFloat(value);
1135         }
1136         catch (IOException ioe) {
1137             JMSException jmsEx = new JMSException(ioe.getMessage());
1138             jmsEx.setLinkedException(ioe);
1139             throw jmsEx;
1140         }
1141     }
1142 
1143 
1144     /***
1145      * Writes a <code>double</code> to the stream message.
1146      *
1147      * @param value the <code>double</code> value to be written
1148      * @throws JMSException                 if the JMS provider fails to write the message
1149      *                                      due to some internal error.
1150      * @throws MessageNotWriteableException if the message is in read-only
1151      *                                      mode.
1152      */
1153 
1154     public void writeDouble(double value) throws JMSException {
1155         initializeWriting();
1156         try {
1157             this.dataOut.write(DOUBLE);
1158             this.dataOut.writeDouble(value);
1159         }
1160         catch (IOException ioe) {
1161             JMSException jmsEx = new JMSException(ioe.getMessage());
1162             jmsEx.setLinkedException(ioe);
1163             throw jmsEx;
1164         }
1165     }
1166 
1167 
1168     /***
1169      * Writes a <code>String</code> to the stream message.
1170      *
1171      * @param value the <code>String</code> value to be written
1172      * @throws JMSException                 if the JMS provider fails to write the message
1173      *                                      due to some internal error.
1174      * @throws MessageNotWriteableException if the message is in read-only
1175      *                                      mode.
1176      */
1177 
1178     public void writeString(String value) throws JMSException {
1179         initializeWriting();
1180         try {
1181             if (value == null) {
1182                 this.dataOut.write(NULL);
1183             }
1184             else {
1185                 this.dataOut.write(STRING);
1186                 this.dataOut.writeUTF(value);
1187             }
1188         }
1189         catch (IOException ioe) {
1190             JMSException jmsEx = new JMSException(ioe.getMessage());
1191             jmsEx.setLinkedException(ioe);
1192             throw jmsEx;
1193         }
1194     }
1195 
1196 
1197     /***
1198      * Writes a byte array field to the stream message.
1199      * <p/>
1200      * <P>The byte array <code>value</code> is written to the message
1201      * as a byte array field. Consecutively written byte array fields are
1202      * treated as two distinct fields when the fields are read.
1203      *
1204      * @param value the byte array value to be written
1205      * @throws JMSException                 if the JMS provider fails to write the message
1206      *                                      due to some internal error.
1207      * @throws MessageNotWriteableException if the message is in read-only
1208      *                                      mode.
1209      */
1210 
1211     public void writeBytes(byte[] value) throws JMSException {
1212         writeBytes(value, 0, value.length);
1213     }
1214 
1215 
1216     /***
1217      * Writes a portion of a byte array as a byte array field to the stream
1218      * message.
1219      * <p/>
1220      * <P>The a portion of the byte array <code>value</code> is written to the
1221      * message as a byte array field. Consecutively written byte
1222      * array fields are treated as two distinct fields when the fields are
1223      * read.
1224      *
1225      * @param value  the byte array value to be written
1226      * @param offset the initial offset within the byte array
1227      * @param length the number of bytes to use
1228      * @throws JMSException                 if the JMS provider fails to write the message
1229      *                                      due to some internal error.
1230      * @throws MessageNotWriteableException if the message is in read-only
1231      *                                      mode.
1232      */
1233 
1234     public void writeBytes(byte[] value, int offset, int length) throws JMSException {
1235         initializeWriting();
1236         try {
1237             this.dataOut.write(BYTES);
1238             this.dataOut.writeInt(length);
1239             this.dataOut.write(value, offset, length);
1240         }
1241         catch (IOException ioe) {
1242             JMSException jmsEx = new JMSException(ioe.getMessage());
1243             jmsEx.setLinkedException(ioe);
1244             throw jmsEx;
1245         }
1246     }
1247 
1248 
1249     /***
1250      * Writes an object to the stream message.
1251      * <p/>
1252      * <P>This method works only for the objectified primitive
1253      * object types (<code>Integer</code>, <code>Double</code>,
1254      * <code>Long</code>&nbsp;...), <code>String</code> objects, and byte
1255      * arrays.
1256      *
1257      * @param value the Java object to be written
1258      * @throws JMSException                 if the JMS provider fails to write the message
1259      *                                      due to some internal error.
1260      * @throws MessageFormatException       if the object is invalid.
1261      * @throws MessageNotWriteableException if the message is in read-only
1262      *                                      mode.
1263      */
1264 
1265     public void writeObject(Object value) throws JMSException {
1266         initializeWriting();
1267         if (value == null) {
1268             try {
1269                 this.dataOut.write(NULL);
1270             }
1271             catch (IOException ioe) {
1272                 JMSException jmsEx = new JMSException(ioe.getMessage());
1273                 jmsEx.setLinkedException(ioe);
1274                 throw jmsEx;
1275             }
1276         }
1277         else if (value instanceof String) {
1278             writeString(value.toString());
1279         }
1280         else if (value instanceof Character) {
1281             writeChar(((Character) value).charValue());
1282         }
1283         else if (value instanceof Boolean) {
1284             writeBoolean(((Boolean) value).booleanValue());
1285         }
1286         else if (value instanceof Byte) {
1287             writeByte(((Byte) value).byteValue());
1288         }
1289         else if (value instanceof Short) {
1290             writeShort(((Short) value).shortValue());
1291         }
1292         else if (value instanceof Integer) {
1293             writeInt(((Integer) value).intValue());
1294         }
1295         else if (value instanceof Float) {
1296             writeFloat(((Float) value).floatValue());
1297         }
1298         else if (value instanceof Double) {
1299             writeDouble(((Double) value).doubleValue());
1300         }
1301         else if (value instanceof byte[]) {
1302             writeBytes((byte[]) value);
1303         }
1304     }
1305 
1306 
1307     /***
1308      * Puts the message body in read-only mode and repositions the stream of
1309      * bytes to the beginning.
1310      *
1311      * @throws JMSException if an internal error occurs
1312      */
1313 
1314     public void reset() throws JMSException {
1315         super.readOnlyMessage = true;
1316         if (this.dataOut != null) {
1317             try {
1318                 this.dataOut.flush();
1319                 super.setBodyAsBytes(this.bytesOut.toByteArray());
1320                 dataOut.close();
1321             }
1322             catch (IOException ioe) {
1323                 JMSException jmsEx = new JMSException("reset failed: " + ioe.getMessage());
1324                 jmsEx.setLinkedException(ioe);
1325                 throw jmsEx;
1326             }
1327         }
1328         this.bytesOut = null;
1329         this.dataIn = null;
1330         this.dataOut = null;
1331     }
1332 
1333     /***
1334      * @param bodyAsBytes The bodyAsBytes to set.
1335      */
1336     public void setBodyAsBytes(byte[] bodyAsBytes) {
1337         super.setBodyAsBytes(bodyAsBytes);
1338         dataOut = null;
1339         dataIn = null;
1340     }
1341 
1342     /***
1343      * @return Returns the data body
1344      * @throws IOException if an exception occurs retreiving the data
1345      */
1346     public byte[] getBodyAsBytes() throws IOException {
1347         if (this.dataOut != null) {
1348             this.dataOut.flush();
1349             super.setBodyAsBytes(this.bytesOut.toByteArray());
1350             dataOut.close();
1351             dataOut = null;
1352         }
1353         return super.getBodyAsBytes();
1354     }
1355 
1356 
1357     private void initializeWriting() throws MessageNotWriteableException {
1358         if (super.readOnlyMessage) {
1359             throw new MessageNotWriteableException("This message is in read-only mode");
1360         }
1361         if (this.dataOut == null) {
1362             this.bytesOut = new ByteArrayOutputStream();
1363             this.dataOut = new DataOutputStream(this.bytesOut);
1364         }
1365     }
1366 
1367 
1368     private void initializeReading() throws MessageNotReadableException {
1369         if (!super.readOnlyMessage) {
1370             throw new MessageNotReadableException("This message is in write-only mode");
1371         }
1372         try {
1373             byte[] data = super.getBodyAsBytes();
1374             if (this.dataIn == null && data != null) {
1375                 ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
1376                 this.dataIn = new DataInputStream(bytesIn);
1377             }
1378         }
1379         catch (IOException e) {
1380             MessageNotReadableException mnr = new MessageNotReadableException("getBodyAsBytes failed");
1381             mnr.setLinkedException(e);
1382             throw mnr;
1383         }
1384     }
1385 }