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
239
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
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
378
379
380
381
382
383
384
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
401
402
403
404
405
406
407
408
409
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
426
427
428
429
430
431
432
433
434
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;
563 if (_readBytes == 0) {
564
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
593
594 if (_readBytes != 0) {
595
596 read = -1;
597 }
598 _readBytes = 0;
599 } else if (value.length <= _byteArrayLength) {
600
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
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
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
818
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
847
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
882
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
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
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
1049
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
1067
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 }