1 | /* |
2 | |
3 | Derby - Class org.apache.derby.iapi.services.io.ArrayInputStream |
4 | |
5 | Copyright 1998, 2004 The Apache Software Foundation or its licensors, as applicable. |
6 | |
7 | Licensed under the Apache License, Version 2.0 (the "License"); |
8 | you may not use this file except in compliance with the License. |
9 | You may obtain a copy of the License at |
10 | |
11 | http://www.apache.org/licenses/LICENSE-2.0 |
12 | |
13 | Unless required by applicable law or agreed to in writing, software |
14 | distributed under the License is distributed on an "AS IS" BASIS, |
15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
16 | See the License for the specific language governing permissions and |
17 | limitations under the License. |
18 | |
19 | */ |
20 | |
21 | package org.apache.derby.iapi.services.io; |
22 | |
23 | import java.io.InputStream; |
24 | import java.io.IOException; |
25 | import java.io.ObjectInput; |
26 | import java.io.EOFException; |
27 | |
28 | import org.apache.derby.iapi.services.sanity.SanityManager; |
29 | |
30 | import org.apache.derby.iapi.services.io.LimitObjectInput; |
31 | import org.apache.derby.iapi.services.io.ErrorObjectInput; |
32 | |
33 | import java.io.UTFDataFormatException; |
34 | |
35 | /** |
36 | An InputStream that allows reading from an array of bytes. The array |
37 | of bytes that is read from can be changed without having to create a new |
38 | instance of this class. |
39 | */ |
40 | public final class ArrayInputStream extends InputStream implements LimitObjectInput { |
41 | |
42 | private byte[] pageData; |
43 | |
44 | private int start; |
45 | private int end; // exclusive |
46 | private int position; |
47 | |
48 | public ArrayInputStream() { |
49 | this(null); |
50 | } |
51 | |
52 | private ErrorObjectInput oi; |
53 | |
54 | public ArrayInputStream(byte[] data) { |
55 | super(); |
56 | setData(data); |
57 | oi = new org.apache.derby.iapi.services.io.FormatIdInputStream(this); |
58 | } |
59 | |
60 | public ArrayInputStream(byte[] data, int offset, int length) throws IOException { |
61 | this(data); |
62 | setLimit(offset, length); |
63 | } |
64 | |
65 | /* |
66 | ** Public methods |
67 | */ |
68 | |
69 | /** |
70 | Set the array of bytes to be read. |
71 | */ |
72 | public void setData(byte[] data) { |
73 | pageData = data; |
74 | clearLimit(); |
75 | } |
76 | |
77 | public void setData(byte[] data, int offset, int length) throws IOException { |
78 | pageData = data; |
79 | setLimit(offset, length); |
80 | } |
81 | |
82 | /** |
83 | Return a reference to the array of bytes this stream is going to read |
84 | from so that caller may load it with stuff |
85 | */ |
86 | public byte[] getData() |
87 | { |
88 | return pageData; |
89 | } |
90 | |
91 | /* |
92 | ** Methods of InputStream |
93 | */ |
94 | |
95 | public int read() throws IOException { |
96 | if (position == end) |
97 | return -1; // end of file |
98 | |
99 | return pageData[position++] & 0xff ; |
100 | |
101 | } |
102 | |
103 | public int read(byte b[], int off, int len) throws IOException { |
104 | |
105 | if ((position + len) > end) { |
106 | |
107 | len = end - position; |
108 | |
109 | if (len == 0) { |
110 | return -1; // end of file |
111 | } |
112 | } |
113 | |
114 | System.arraycopy(pageData, position, b, off, len); |
115 | position += len; |
116 | return len; |
117 | } |
118 | |
119 | public long skip(long count) throws IOException { |
120 | |
121 | if ((position + count) > end) { |
122 | |
123 | count = end - position; |
124 | |
125 | if (count == 0) |
126 | return 0; // end of file |
127 | } |
128 | |
129 | position += count; |
130 | |
131 | return count; |
132 | |
133 | } |
134 | |
135 | public int getPosition() { |
136 | return position; |
137 | } |
138 | |
139 | public final void setPosition(int newPosition) |
140 | throws IOException { |
141 | |
142 | if ((newPosition >= start) && (newPosition < end)) |
143 | position = newPosition; |
144 | else |
145 | throw new EOFException(); |
146 | } |
147 | |
148 | public int available() throws IOException { |
149 | |
150 | return end - position; |
151 | } |
152 | |
153 | |
154 | /** |
155 | A setLimit which also sets the position to be offset. |
156 | |
157 | @exception IOException limit is out of range |
158 | */ |
159 | public int setLimit(int offset, int length) throws IOException { |
160 | |
161 | if ((offset < 0) || (length < 0)) { |
162 | start = end = position = 0; |
163 | throw new EOFException(); |
164 | } |
165 | |
166 | start = offset; |
167 | end = offset + length; |
168 | |
169 | if (end > pageData.length) { |
170 | start = end = position = 0; |
171 | throw new EOFException(); |
172 | } |
173 | |
174 | position = start; |
175 | |
176 | return length; |
177 | } |
178 | |
179 | /* |
180 | ** Methods of Limit |
181 | */ |
182 | |
183 | public final void setLimit(int length) throws IOException { |
184 | |
185 | start = position; |
186 | end = position + length; |
187 | |
188 | if (end <= pageData.length) |
189 | { |
190 | return; |
191 | } |
192 | else |
193 | { |
194 | start = end = position = 0; |
195 | throw new EOFException(); |
196 | } |
197 | } |
198 | |
199 | /** |
200 | Clears the limit by setting the limit to be the entire byte array. |
201 | |
202 | @see Limit#clearLimit |
203 | */ |
204 | public final int clearLimit() { |
205 | |
206 | if (pageData != null) { |
207 | start = 0; |
208 | int remainingBytes = end - position; |
209 | end = pageData.length; |
210 | return remainingBytes; |
211 | } else { |
212 | start = end = position = 0; |
213 | return 0; |
214 | } |
215 | } |
216 | |
217 | /* |
218 | ** Methods of DataInput |
219 | */ |
220 | |
221 | public final void readFully(byte b[]) throws IOException { |
222 | readFully(b, 0, b.length); |
223 | } |
224 | |
225 | public final void readFully(byte b[], int off, int len) throws IOException { |
226 | |
227 | if ((position + len) > end) { |
228 | |
229 | throw new EOFException(); |
230 | } |
231 | |
232 | System.arraycopy(pageData, position, b, off, len); |
233 | position += len; |
234 | } |
235 | |
236 | public final int skipBytes(int n) throws IOException { |
237 | if ((position + n) > end) { |
238 | |
239 | throw new EOFException(); |
240 | } |
241 | position += n; |
242 | return n; |
243 | } |
244 | |
245 | public final boolean readBoolean() throws IOException { |
246 | if (position == end) |
247 | throw new EOFException(); // end of file |
248 | |
249 | return pageData[position++] != 0; |
250 | } |
251 | |
252 | public final byte readByte() throws IOException { |
253 | if (position == end) |
254 | throw new EOFException(); // end of file |
255 | |
256 | return pageData[position++]; |
257 | } |
258 | |
259 | public final int readUnsignedByte() throws IOException { |
260 | if (position == end) |
261 | throw new EOFException(); // end of file |
262 | |
263 | return pageData[position++] & 0xff ; |
264 | } |
265 | |
266 | public final short readShort() throws IOException { |
267 | |
268 | int pos = position; |
269 | byte[] data = pageData; |
270 | |
271 | if (pos >= (end - 1)) |
272 | throw new EOFException(); // end of file |
273 | |
274 | int s = ((data[pos++] & 0xff) << 8) | (data[pos++] & 0xff); |
275 | |
276 | position = pos; |
277 | |
278 | return (short) s; |
279 | } |
280 | |
281 | public final int readUnsignedShort() throws IOException { |
282 | int pos = position; |
283 | byte[] data = pageData; |
284 | |
285 | if (pos >= (end - 1)) |
286 | throw new EOFException(); // end of file |
287 | |
288 | int us = ((data[pos++] & 0xff) << 8) | (data[pos++] & 0xff); |
289 | |
290 | position = pos; |
291 | |
292 | return us; |
293 | } |
294 | |
295 | public final char readChar() throws IOException { |
296 | int pos = position; |
297 | byte[] data = pageData; |
298 | |
299 | if (pos >= (end -1)) |
300 | throw new EOFException(); // end of file |
301 | |
302 | int c = ((data[pos++] & 0xff) << 8) | (data[pos++] & 0xff); |
303 | |
304 | position = pos; |
305 | |
306 | return (char) c; |
307 | } |
308 | |
309 | public final int readInt() throws IOException { |
310 | |
311 | int pos = position; |
312 | byte[] data = pageData; |
313 | |
314 | if (pos >= (end - 3)) |
315 | throw new EOFException(); // end of file |
316 | |
317 | |
318 | |
319 | int i = ((data[pos++] & 0xff) << 24) | |
320 | ((data[pos++] & 0xff) << 16) | |
321 | ((data[pos++] & 0xff) << 8) | |
322 | ((data[pos++] & 0xff) ); |
323 | |
324 | position = pos; |
325 | |
326 | return i; |
327 | } |
328 | |
329 | public final long readLong() throws IOException { |
330 | int pos = position; |
331 | byte[] data = pageData; |
332 | |
333 | if (pos >= (end - 7)) |
334 | throw new EOFException(); // end of file |
335 | |
336 | long l = |
337 | (((long) (data[pos++] & 0xff)) << 56) | |
338 | (((long) (data[pos++] & 0xff)) << 48) | |
339 | (((long) (data[pos++] & 0xff)) << 40) | |
340 | (((long) (data[pos++] & 0xff)) << 32) | |
341 | (((long) (data[pos++] & 0xff)) << 24) | |
342 | (((long) (data[pos++] & 0xff)) << 16) | |
343 | (((long) (data[pos++] & 0xff)) << 8) | |
344 | (((long) (data[pos++] & 0xff)) ); |
345 | |
346 | position = pos; |
347 | |
348 | return l; |
349 | } |
350 | |
351 | public final float readFloat() throws IOException { |
352 | return Float.intBitsToFloat(readInt()); |
353 | } |
354 | |
355 | public final double readDouble() throws IOException { |
356 | return Double.longBitsToDouble(readLong()); |
357 | } |
358 | |
359 | public final String readLine() throws IOException { |
360 | return oi.readLine(); |
361 | } |
362 | public final String readUTF() throws IOException { |
363 | return oi.readUTF(); |
364 | } |
365 | |
366 | /** |
367 | * read in a cloudscape UTF formated string into a char[]. |
368 | * <p> |
369 | * This routine inline's the code to read a UTF format string from a |
370 | * byte[] array (pageData), into a char[] array. The string will |
371 | * be read into the char[] array passed into this routine through |
372 | * rawData_array[0] if it is big enough. If it is not big enough |
373 | * a new char[] will be alocated and returned to the caller by putting |
374 | * it into rawData_array[0]. |
375 | * <p> |
376 | * To see detailed description of the cloudscape UTF format see |
377 | * the writeExternal() routine of SQLChar. |
378 | * <p> |
379 | * The routine returns the number of char's read into the returned |
380 | * char[], note that this length may smaller than the actual length |
381 | * of the char[] array. |
382 | * |
383 | * @return The the number of valid char's in the returned char[]. |
384 | * |
385 | * @param rawData_array This parameter uses a element array to implement |
386 | * an in/out function parameter. The char[] array |
387 | * in rawData_array[0] is used to read the data into |
388 | * unless it is not big enough, then a new array |
389 | * is allocated and the old one discarded. In |
390 | * either case on return rawData_array[0] contains |
391 | * the filled in char[] - caller must allow that |
392 | * the array may or may not be different from the |
393 | * one passed in. |
394 | * |
395 | * @exception StandardException Standard exception policy. |
396 | **/ |
397 | public final int readCloudscapeUTF(char[][] rawData_array) |
398 | throws IOException |
399 | { |
400 | // copy globals locally, to give compiler chance to optimize. |
401 | byte[] data = pageData; |
402 | int end_pos = end; |
403 | int pos = position; |
404 | |
405 | // get header length - stored as an unsigned short. |
406 | |
407 | int utflen; |
408 | if (pos + 1 < end_pos) |
409 | { |
410 | utflen = (((data[pos++] & 0xff) << 8) | (data[pos++] & 0xff)); |
411 | } |
412 | else |
413 | { |
414 | throw new EOFException(); // end of file |
415 | } |
416 | |
417 | /** |
418 | * 3 cases - can they all happen? |
419 | * |
420 | * o utflen == 0 and end is marked E0, 0, 0 |
421 | * o utflen == 0 and there is no data (ie. 0 length string) |
422 | * o utflen != 0, utflen is exact length of following bytes |
423 | **/ |
424 | |
425 | // requiredLength is the amount of bytes to read from the array, |
426 | // either the utflen in the header length, or the number of bytes |
427 | // available in the array. Throw an exception if we know up front |
428 | // that utflen is bigger than number of bytes in the array. |
429 | int requiredLength; |
430 | if (utflen != 0) |
431 | { |
432 | // this is the only place we need to check for end of file, |
433 | // the subsequent loop will not read past bytes_available_in_array. |
434 | |
435 | if (utflen <= (end_pos - pos)) |
436 | { |
437 | requiredLength = utflen; |
438 | } |
439 | else |
440 | { |
441 | throw new EOFException(); |
442 | } |
443 | } |
444 | else |
445 | { |
446 | // the byte header returned 0, so read what is left in the array. |
447 | |
448 | requiredLength = (end_pos - pos); |
449 | } |
450 | |
451 | // Use the passed in char[] array if it is long enough, otherwise |
452 | // allocate a new array, and will pass it back to caller at the end. |
453 | // Note that requiredLength is the worst case length for the array, |
454 | // as the number of char characters must be <= number of bytes (ie. |
455 | // all characters were stored compressed in 1 byte each - the ascii |
456 | // default) - if there are any 2 or 3 byte stored characters then |
457 | // the array will have extra space at the end. "strlen" tracks the |
458 | // real number of char's in str[]. |
459 | char[] str = rawData_array[0]; |
460 | if ((str == null) || (requiredLength > str.length)) |
461 | { |
462 | str = new char[requiredLength]; |
463 | rawData_array[0] = str; |
464 | } |
465 | |
466 | end_pos = pos + requiredLength; |
467 | int strlen = 0; |
468 | |
469 | while (pos < end_pos) |
470 | { |
471 | int char1 = (data[pos++] & 0xff); |
472 | |
473 | // top fours bits of the first unsigned byte that maps to a 1,2 |
474 | // or 3 byte character |
475 | // |
476 | // 0000xxxx - 0 - 1 byte char |
477 | // 0001xxxx - 1 - 1 byte char |
478 | // 0010xxxx - 2 - 1 byte char |
479 | // 0011xxxx - 3 - 1 byte char |
480 | // 0100xxxx - 4 - 1 byte char |
481 | // 0101xxxx - 5 - 1 byte char |
482 | // 0110xxxx - 6 - 1 byte char |
483 | // 0111xxxx - 7 - 1 byte char |
484 | // 1000xxxx - 8 - error |
485 | // 1001xxxx - 9 - error |
486 | // 1010xxxx - 10 - error |
487 | // 1011xxxx - 11 - error |
488 | // 1100xxxx - 12 - 2 byte char |
489 | // 1101xxxx - 13 - 2 byte char |
490 | // 1110xxxx - 14 - 3 byte char |
491 | // 1111xxxx - 15 - error |
492 | |
493 | int char2, char3; |
494 | if ((char1 & 0x80) == 0x00) |
495 | { |
496 | // one byte character |
497 | str[strlen++] = (char) char1; |
498 | } |
499 | else if ((char1 & 0x60) == 0x40) // we know the top bit is set here |
500 | { |
501 | // two byte character, make sure read of next byte is in bounds. |
502 | if (pos >= end_pos) |
503 | throw new UTFDataFormatException(); |
504 | |
505 | char2 = (data[pos++] & 0xff); |
506 | |
507 | if ((char2 & 0xC0) != 0x80) |
508 | throw new UTFDataFormatException(); |
509 | |
510 | str[strlen++] = (char)(((char1 & 0x1F) << 6) | (char2 & 0x3F)); |
511 | } |
512 | else if ((char1 & 0x70) == 0x60) // we know the top bit is set here |
513 | { |
514 | // three byte character |
515 | |
516 | // 3 byte character, make sure read of next 2 bytes in bounds. |
517 | if (pos + 1 >= end_pos) |
518 | throw new UTFDataFormatException(); |
519 | |
520 | char2 = (data[pos++] & 0xff); |
521 | char3 = (data[pos++] & 0xff); |
522 | |
523 | if ((char1 == 0xE0) && |
524 | (char2 == 0) && |
525 | (char3 == 0) && |
526 | (utflen == 0)) |
527 | { |
528 | // we reached the end of a long string, |
529 | // that was terminated with |
530 | // (11100000, 00000000, 00000000) |
531 | break; |
532 | } |
533 | else if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) |
534 | { |
535 | throw new UTFDataFormatException(); |
536 | } |
537 | else |
538 | { |
539 | str[strlen++] = (char) |
540 | (((char1 & 0x0F) << 12) | |
541 | ((char2 & 0x3F) << 6) | |
542 | ((char3 & 0x3F) << 0)); |
543 | } |
544 | } |
545 | else |
546 | { |
547 | throw new UTFDataFormatException(); |
548 | } |
549 | |
550 | } |
551 | |
552 | // update global on successful read exit. |
553 | position = pos; |
554 | |
555 | return(strlen); |
556 | } |
557 | |
558 | /** |
559 | * Read a compressed int from the stream. |
560 | * <p> |
561 | * Read a compressed int from the stream, which is assumed to have |
562 | * been written by a call to CompressNumber.writeInt(). |
563 | * <p> |
564 | * Code from CompressedNumber is inlined here so that these fields can |
565 | * be read from the array with a minimum of function calls. |
566 | * <p> |
567 | * The format of a compressed int is as follows: |
568 | * |
569 | * Formats are (with x representing value bits): |
570 | * <PRE> |
571 | * 1 Byte- 00xxxxxx val <= 63 (0x3f) |
572 | * 2 Byte- 01xxxxxx xxxxxxxx val > 63 && <= 16383 (0x3fff) |
573 | * 4 byte- 1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx val > 16383 && <= MAX_INT |
574 | * </PRE> |
575 | * |
576 | * @exception StandardException Standard exception policy. |
577 | **/ |
578 | public final int readCompressedInt() |
579 | throws IOException |
580 | { |
581 | int pos = position; |
582 | byte[] data = pageData; |
583 | |
584 | try |
585 | { |
586 | int value = data[pos++]; |
587 | |
588 | if ((value & ~0x3f) == 0) |
589 | { |
590 | // entire value is stored in this byte, we also know that the |
591 | // 0x80 bit was not set, so no need to mask off the sign |
592 | // extension from the byte to int conversion. |
593 | } |
594 | else if ((value & 0x80) == 0) |
595 | { |
596 | // value stored in 2 bytes. only use low 6 bits from 1st byte. |
597 | |
598 | if (SanityManager.DEBUG) |
599 | { |
600 | SanityManager.ASSERT((value & 0x40) == 0x40); |
601 | } |
602 | |
603 | // top 8 bits of 2 byte value is stored in this byte, we also |
604 | // know that the 0x80 bit was not set, so no need to mask off |
605 | // the sign extension from the 1st byte to int conversion. |
606 | // Need to mask the byte in data[pos + 1] to account for |
607 | // possible sign extension. |
608 | |
609 | value = |
610 | (((value & 0x3f) << 8) | (data[pos++] & 0xff)); |
611 | } |
612 | else |
613 | { |
614 | // value stored in 4 bytes. only use low 7 bits from 1st byte. |
615 | |
616 | if (SanityManager.DEBUG) |
617 | { |
618 | SanityManager.ASSERT((value & 0x80) == 0x80); |
619 | } |
620 | |
621 | // top 8 bits of 4 byte value is stored in this byte, we also |
622 | // know that the 0x80 bit was set, so need to mask off the |
623 | // sign extension from the 1st byte to int conversion. Need to |
624 | // mask the bytes from the next 3 bytes data[pos + 1,2,3] to |
625 | // account for possible sign extension. |
626 | // |
627 | |
628 | value = |
629 | ((value & 0x7f) << 24) | |
630 | ((data[pos++] & 0xff) << 16) | |
631 | ((data[pos++] & 0xff) << 8) | |
632 | ((data[pos++] & 0xff) ); |
633 | } |
634 | |
635 | position = pos; |
636 | |
637 | return(value); |
638 | } |
639 | catch (java.lang.ArrayIndexOutOfBoundsException ex) |
640 | { |
641 | throw new EOFException(); // end of file |
642 | } |
643 | |
644 | } |
645 | |
646 | /** |
647 | * Read a compressed long from the stream. |
648 | * <p> |
649 | * Read a compressed long from the stream, which is assumed to have |
650 | * been written by a call to CompressNumber.writeLong(). |
651 | * <p> |
652 | * Code from CompressedNumber is inlined here so that these fields can |
653 | * be read from the array with a minimum of function calls. |
654 | * <p> |
655 | * The format of a compressed int is as follows: |
656 | * |
657 | * Formats are (with x representing value bits): |
658 | * <PRE> |
659 | * value <= 16383 (0x3fff): |
660 | * 2 byte - 00xxxxxx xxxxxxxx |
661 | * |
662 | * value > 16383 && <= 0x3fffffff: |
663 | * 4 byte - 01xxxxxx xxxxxxxx xxxxxxxx xxxxxxxx |
664 | * |
665 | * value > 0x3fffffff && <= MAX_LONG: |
666 | * 8 byte - 1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx |
667 | * </PRE> |
668 | * |
669 | **/ |
670 | public final long readCompressedLong() |
671 | throws IOException |
672 | { |
673 | try |
674 | { |
675 | // copy globals locally, to give compiler chance to optimize. |
676 | int pos = position; |
677 | byte[] data = pageData; |
678 | |
679 | // int_value tells whether it is 1, 4, or 8 bytes long. |
680 | int int_value = data[pos++]; |
681 | |
682 | // build up long value and return it through this variable. |
683 | long long_value; |
684 | |
685 | if ((int_value & ~0x3f) == 0) |
686 | { |
687 | // 2 byte representation |
688 | |
689 | // 1st byte of value is stored in int_value, we also know that |
690 | // the 0x80 bit was not set, so no need to mask off the sign |
691 | // extension from the 1st byte to int conversion. |
692 | long_value = ((int_value << 8) | (data[pos++] & 0xff)); |
693 | } |
694 | else if ((int_value & 0x80) == 0) |
695 | { |
696 | // value stored in 4 bytes. only use low 6 bits from 1st byte. |
697 | |
698 | // Need to mask the bytes from the next 3 bytes |
699 | // data[pos + 1,2,3] to account for possible sign extension. |
700 | |
701 | long_value = |
702 | ((int_value & 0x3f) << 24) | |
703 | ((data[pos++] & 0xff) << 16) | |
704 | ((data[pos++] & 0xff) << 8) | |
705 | ((data[pos++] & 0xff) ); |
706 | } |
707 | else |
708 | { |
709 | // top 7 bits of 4 byte value is stored in int_value, we also |
710 | // know that the 0x80 bit was set, so need to mask off the |
711 | // sign extension from the 1st byte to int conversion. Need to |
712 | // mask the bytes from the next 7 bytes data[pos + 1,2,...] to |
713 | // account for possible sign extension. |
714 | // |
715 | |
716 | // value stored in 8 bytes. only use low 6 bits from 1st byte. |
717 | long_value = |
718 | (((long) (int_value & 0x7f)) << 56) | |
719 | (((long) (data[pos++] & 0xff)) << 48) | |
720 | (((long) (data[pos++] & 0xff)) << 40) | |
721 | (((long) (data[pos++] & 0xff)) << 32) | |
722 | (((long) (data[pos++] & 0xff)) << 24) | |
723 | (((long) (data[pos++] & 0xff)) << 16) | |
724 | (((long) (data[pos++] & 0xff)) << 8) | |
725 | (((long) (data[pos++] & 0xff)) ); |
726 | } |
727 | |
728 | position = pos; |
729 | |
730 | return(long_value); |
731 | } |
732 | catch (java.lang.ArrayIndexOutOfBoundsException ex) |
733 | { |
734 | // let java figure out if we went past end of data[] array. |
735 | |
736 | throw new EOFException(); // end of file |
737 | } |
738 | } |
739 | |
740 | public Object readObject() throws ClassNotFoundException, IOException { |
741 | return oi.readObject(); |
742 | } |
743 | |
744 | public String getErrorInfo() { |
745 | return oi.getErrorInfo(); |
746 | } |
747 | |
748 | public Exception getNestedException() { |
749 | return oi.getNestedException(); |
750 | } |
751 | } |