51 |
* @see DataInput |
* @see DataInput |
52 |
* |
* |
53 |
* @author Warren Levy <warrenl@cygnus.com> |
* @author Warren Levy <warrenl@cygnus.com> |
54 |
* @author Aaron M. Renn (arenn@urbanophile.com) |
* @author Aaron M. Renn <arenn@urbanophile.com> |
55 |
* @date October 20, 1998. |
* @date October 20, 1998. |
56 |
*/ |
*/ |
57 |
public class DataInputStream extends FilterInputStream implements DataInput |
public class DataInputStream extends FilterInputStream implements DataInput |
62 |
boolean ignoreInitialNewline = false; |
boolean ignoreInitialNewline = false; |
63 |
|
|
64 |
// Byte buffer, used to make primitive read calls more efficient. |
// Byte buffer, used to make primitive read calls more efficient. |
65 |
byte[] buf = new byte[8]; |
byte[] buf = new byte [8]; |
66 |
|
|
67 |
/** |
/** |
68 |
* This constructor initializes a new <code>DataInputStream</code> |
* This constructor initializes a new <code>DataInputStream</code> |
70 |
* |
* |
71 |
* @param in The subordinate <code>InputStream</code> to read from |
* @param in The subordinate <code>InputStream</code> to read from |
72 |
*/ |
*/ |
73 |
public DataInputStream(InputStream in) |
public DataInputStream (InputStream in) |
74 |
{ |
{ |
75 |
super(in); |
super (in); |
76 |
} |
} |
77 |
|
|
78 |
/** |
/** |
88 |
* |
* |
89 |
* @exception IOException If an error occurs. |
* @exception IOException If an error occurs. |
90 |
*/ |
*/ |
91 |
public final int read(byte[] b) throws IOException |
public final int read (byte[] b) throws IOException |
92 |
{ |
{ |
93 |
return in.read(b, 0, b.length); |
return in.read (b, 0, b.length); |
94 |
} |
} |
95 |
|
|
96 |
/** |
/** |
109 |
* |
* |
110 |
* @exception IOException If an error occurs. |
* @exception IOException If an error occurs. |
111 |
*/ |
*/ |
112 |
public final int read(byte[] b, int off, int len) throws IOException |
public final int read (byte[] b, int off, int len) throws IOException |
113 |
{ |
{ |
114 |
return in.read(b, off, len); |
return in.read (b, off, len); |
115 |
} |
} |
116 |
|
|
117 |
/** |
/** |
132 |
* |
* |
133 |
* @see DataOutput#writeBoolean |
* @see DataOutput#writeBoolean |
134 |
*/ |
*/ |
135 |
public final boolean readBoolean() throws IOException |
public final boolean readBoolean () throws IOException |
136 |
{ |
{ |
137 |
int b = in.read(); |
return convertToBoolean (in.read ()); |
|
if (b < 0) |
|
|
throw new EOFException(); |
|
|
else |
|
|
return(b != 0); |
|
138 |
} |
} |
139 |
|
|
140 |
/** |
/** |
152 |
* |
* |
153 |
* @see DataOutput#writeByte |
* @see DataOutput#writeByte |
154 |
*/ |
*/ |
155 |
public final byte readByte() throws IOException |
public final byte readByte () throws IOException |
156 |
{ |
{ |
157 |
int i = in.read(); |
return convertToByte (in.read ()); |
|
if (i < 0) |
|
|
throw new EOFException(); |
|
|
return (byte) i; |
|
158 |
} |
} |
159 |
|
|
160 |
/** |
/** |
169 |
* respectively, they will be transformed to a <code>char</code> in |
* respectively, they will be transformed to a <code>char</code> in |
170 |
* the following manner: |
* the following manner: |
171 |
* <p> |
* <p> |
172 |
* <code>(char)(((byte1 & 0xFF) << 8) | (byte2 & 0xFF)</code> |
* <code>(char)(((byte1 & 0xFF) << 8) | (byte2 & 0xFF)</code> |
173 |
* <p> |
* <p> |
174 |
* This method can read a <code>char</code> written by an object |
* This method can read a <code>char</code> written by an object |
175 |
* implementing the <code>writeChar()</code> method in the |
* implementing the <code>writeChar()</code> method in the |
182 |
* |
* |
183 |
* @see DataOutput#writeChar |
* @see DataOutput#writeChar |
184 |
*/ |
*/ |
185 |
public synchronized final char readChar () throws IOException |
public final char readChar () throws IOException |
186 |
{ |
{ |
187 |
readFully (buf, 0, 2); |
readFully (buf, 0, 2); |
188 |
return (char) ((buf[0] << 8) | (buf[1] & 0xff)); |
return convertToChar (buf); |
189 |
} |
} |
190 |
|
|
191 |
/** |
/** |
209 |
* @see DataOutput#writeDouble |
* @see DataOutput#writeDouble |
210 |
* @see java.lang.Double#longBitsToDouble |
* @see java.lang.Double#longBitsToDouble |
211 |
*/ |
*/ |
212 |
public final double readDouble() throws IOException |
public final double readDouble () throws IOException |
213 |
{ |
{ |
214 |
return Double.longBitsToDouble(readLong()); |
return Double.longBitsToDouble (readLong ()); |
215 |
} |
} |
216 |
|
|
217 |
/** |
/** |
234 |
* @see DataOutput#writeFloat |
* @see DataOutput#writeFloat |
235 |
* @see java.lang.Float#intBitsToFloat |
* @see java.lang.Float#intBitsToFloat |
236 |
*/ |
*/ |
237 |
public final float readFloat() throws IOException |
public final float readFloat () throws IOException |
238 |
{ |
{ |
239 |
return Float.intBitsToFloat(readInt()); |
return Float.intBitsToFloat (readInt ()); |
240 |
} |
} |
241 |
|
|
242 |
/** |
/** |
253 |
* buffer |
* buffer |
254 |
* @exception IOException If any other error occurs |
* @exception IOException If any other error occurs |
255 |
*/ |
*/ |
256 |
public final void readFully(byte[] b) throws IOException |
public final void readFully (byte[] b) throws IOException |
257 |
{ |
{ |
258 |
readFully(b, 0, b.length); |
readFully (b, 0, b.length); |
259 |
} |
} |
260 |
|
|
261 |
/** |
/** |
277 |
* buffer |
* buffer |
278 |
* @exception IOException If any other error occurs |
* @exception IOException If any other error occurs |
279 |
*/ |
*/ |
280 |
public final void readFully(byte[] b, int off, int len) throws IOException |
public final void readFully (byte[] b, int off, int len) throws IOException |
281 |
{ |
{ |
282 |
while (len > 0) |
while (len > 0) |
283 |
{ |
{ |
284 |
// in.read will block until some data is available. |
// in.read will block until some data is available. |
285 |
int numread = in.read(b, off, len); |
int numread = in.read (b, off, len); |
286 |
if (numread < 0) |
if (numread < 0) |
287 |
throw new EOFException(); |
throw new EOFException (); |
288 |
len -= numread; |
len -= numread; |
289 |
off += numread; |
off += numread; |
290 |
} |
} |
301 |
* the first four bytes read from the stream, they will be |
* the first four bytes read from the stream, they will be |
302 |
* transformed to an <code>int</code> in the following manner: |
* transformed to an <code>int</code> in the following manner: |
303 |
* <p> |
* <p> |
304 |
* <code>(int)(((byte1 & 0xFF) << 24) + ((byte2 & 0xFF) << 16) + |
* <code>(int)(((byte1 & 0xFF) << 24) + ((byte2 & 0xFF) << 16) + |
305 |
* ((byte3 & 0xFF)<< 8) + (byte4 & 0xFF)))</code> |
* ((byte3 & 0xFF)<< 8) + (byte4 & 0xFF)))</code> |
306 |
* <p> |
* <p> |
307 |
* The value returned is in the range of -2147483648 to 2147483647. |
* The value returned is in the range of -2147483648 to 2147483647. |
308 |
* <p> |
* <p> |
317 |
* |
* |
318 |
* @see DataOutput#writeInt |
* @see DataOutput#writeInt |
319 |
*/ |
*/ |
320 |
public synchronized final int readInt () throws IOException |
public final int readInt () throws IOException |
321 |
{ |
{ |
322 |
readFully (buf, 0, 4); |
readFully (buf, 0, 4); |
323 |
return (((buf[0] & 0xff) << 24) | |
return convertToInt (buf); |
|
((buf[1] & 0xff) << 16) | |
|
|
((buf[2] & 0xff) << 8) | |
|
|
(buf[3] & 0xff)); |
|
324 |
} |
} |
325 |
|
|
326 |
/** |
/** |
349 |
* |
* |
350 |
* @deprecated |
* @deprecated |
351 |
*/ |
*/ |
352 |
public final String readLine() throws IOException |
public final String readLine () throws IOException |
353 |
{ |
{ |
354 |
StringBuffer strb = new StringBuffer(); |
StringBuffer strb = new StringBuffer (); |
355 |
|
|
356 |
readloop: while (true) |
readloop: while (true) |
357 |
{ |
{ |
363 |
getnext = false; |
getnext = false; |
364 |
c = in.read(); |
c = in.read(); |
365 |
if (c < 0) // got an EOF |
if (c < 0) // got an EOF |
366 |
return strb.length() > 0 ? strb.toString() : null; |
return strb.length () > 0 ? strb.toString () : null; |
367 |
ch = (char) c; |
ch = (char) c; |
368 |
if ((ch &= 0xFF) == '\n') |
if ((ch &= 0xFF) == '\n') |
369 |
// hack to correctly handle '\r\n' sequences |
// hack to correctly handle '\r\n' sequences |
447 |
* the first eight bytes read from the stream, they will be |
* the first eight bytes read from the stream, they will be |
448 |
* transformed to an <code>long</code> in the following manner: |
* transformed to an <code>long</code> in the following manner: |
449 |
* <p> |
* <p> |
450 |
* <code>(long)(((byte1 & 0xFF) << 56) + ((byte2 & 0xFF) << 48) + |
* <code>(long)(((byte1 & 0xFF) << 56) + ((byte2 & 0xFF) << 48) + |
451 |
* ((byte3 & 0xFF) << 40) + ((byte4 & 0xFF) << 32) + |
* ((byte3 & 0xFF) << 40) + ((byte4 & 0xFF) << 32) + |
452 |
* ((byte5 & 0xFF) << 24) + ((byte6 & 0xFF) << 16) + |
* ((byte5 & 0xFF) << 24) + ((byte6 & 0xFF) << 16) + |
453 |
* ((byte7 & 0xFF) << 8) + (byte8 & 0xFF))) |
* ((byte7 & 0xFF) << 8) + (byte8 & 0xFF))) |
454 |
* </code> |
* </code> |
455 |
* <p> |
* <p> |
456 |
* The value returned is in the range of -9223372036854775808 to |
* The value returned is in the range of -9223372036854775808 to |
467 |
* |
* |
468 |
* @see DataOutput#writeLong |
* @see DataOutput#writeLong |
469 |
*/ |
*/ |
470 |
public synchronized final long readLong () throws IOException |
public final long readLong () throws IOException |
471 |
{ |
{ |
472 |
readFully (buf, 0, 8); |
readFully (buf, 0, 8); |
473 |
return (((long)(buf[0] & 0xff) << 56) | |
return convertToLong (buf); |
|
((long)(buf[1] & 0xff) << 48) | |
|
|
((long)(buf[2] & 0xff) << 40) | |
|
|
((long)(buf[3] & 0xff) << 32) | |
|
|
((long)(buf[4] & 0xff) << 24) | |
|
|
((long)(buf[5] & 0xff) << 16) | |
|
|
((long)(buf[6] & 0xff) << 8) | |
|
|
((long)(buf[7] & 0xff))); |
|
474 |
} |
} |
475 |
|
|
476 |
/** |
/** |
485 |
* respectively, they will be transformed to a <code>short</code>. in |
* respectively, they will be transformed to a <code>short</code>. in |
486 |
* the following manner: |
* the following manner: |
487 |
* <p> |
* <p> |
488 |
* <code>(short)(((byte1 & 0xFF) << 8) | (byte2 & 0xFF))</code> |
* <code>(short)(((byte1 & 0xFF) << 8) | (byte2 & 0xFF))</code> |
489 |
* <p> |
* <p> |
490 |
* The value returned is in the range of -32768 to 32767. |
* The value returned is in the range of -32768 to 32767. |
491 |
* <p> |
* <p> |
500 |
* |
* |
501 |
* @see DataOutput#writeShort |
* @see DataOutput#writeShort |
502 |
*/ |
*/ |
503 |
public synchronized final short readShort() throws IOException |
public final short readShort () throws IOException |
504 |
{ |
{ |
505 |
readFully (buf, 0, 2); |
readFully (buf, 0, 2); |
506 |
return (short) ((buf[0] << 8) | (buf[1] & 0xff)); |
return convertToShort (buf); |
507 |
} |
} |
508 |
|
|
509 |
/** |
/** |
522 |
* |
* |
523 |
* @see DataOutput#writeByte |
* @see DataOutput#writeByte |
524 |
*/ |
*/ |
525 |
public final int readUnsignedByte() throws IOException |
public final int readUnsignedByte () throws IOException |
|
{ |
|
|
int i = in.read(); |
|
|
if (i < 0) |
|
|
throw new EOFException(); |
|
|
return (i & 0xFF); |
|
|
} |
|
|
|
|
|
// FIXME: This method should be re-thought. I suspect we have multiple |
|
|
// UTF-8 decoders floating around. We should use the standard charset |
|
|
// converters, maybe and adding a direct call into one of the new |
|
|
// NIO converters for a super-fast UTF8 decode. |
|
|
static String convertFromUTF(byte[] buf) |
|
|
throws EOFException, UTFDataFormatException |
|
526 |
{ |
{ |
527 |
// Give StringBuffer an initial estimated size to avoid |
return convertToUnsignedByte (in.read ()); |
|
// enlarge buffer frequently |
|
|
StringBuffer strbuf = new StringBuffer(buf.length/2 + 2); |
|
|
|
|
|
for (int i = 0; i < buf.length; ) |
|
|
{ |
|
|
if ((buf[i] & 0x80) == 0) // bit pattern 0xxxxxxx |
|
|
strbuf.append((char) (buf[i++] & 0xFF)); |
|
|
else if ((buf[i] & 0xE0) == 0xC0) // bit pattern 110xxxxx |
|
|
{ |
|
|
if (i + 1 >= buf.length || (buf[i+1] & 0xC0) != 0x80) |
|
|
throw new UTFDataFormatException(); |
|
|
|
|
|
strbuf.append((char) (((buf[i++] & 0x1F) << 6) | |
|
|
(buf[i++] & 0x3F))); |
|
|
} |
|
|
else if ((buf[i] & 0xF0) == 0xE0) // bit pattern 1110xxxx |
|
|
{ |
|
|
if (i + 2 >= buf.length || |
|
|
(buf[i+1] & 0xC0) != 0x80 || (buf[i+2] & 0xC0) != 0x80) |
|
|
throw new UTFDataFormatException(); |
|
|
|
|
|
strbuf.append((char) (((buf[i++] & 0x0F) << 12) | |
|
|
((buf[i++] & 0x3F) << 6) | |
|
|
(buf[i++] & 0x3F))); |
|
|
} |
|
|
else // must be ((buf[i] & 0xF0) == 0xF0 || (buf[i] & 0xC0) == 0x80) |
|
|
throw new UTFDataFormatException(); // bit patterns 1111xxxx or |
|
|
// 10xxxxxx |
|
|
} |
|
|
|
|
|
return strbuf.toString(); |
|
528 |
} |
} |
529 |
|
|
530 |
/** |
/** |
539 |
* respectively, they will be transformed to an <code>int</code> in |
* respectively, they will be transformed to an <code>int</code> in |
540 |
* the following manner: |
* the following manner: |
541 |
* <p> |
* <p> |
542 |
* <code>(int)(((byte1 & 0xFF) << 8) + (byte2 & 0xFF))</code> |
* <code>(int)(((byte1 & 0xFF) << 8) + (byte2 & 0xFF))</code> |
543 |
* <p> |
* <p> |
544 |
* The value returned is in the range of 0 to 65535. |
* The value returned is in the range of 0 to 65535. |
545 |
* <p> |
* <p> |
554 |
* |
* |
555 |
* @see DataOutput#writeShort |
* @see DataOutput#writeShort |
556 |
*/ |
*/ |
557 |
public final synchronized int readUnsignedShort() throws IOException |
public final int readUnsignedShort () throws IOException |
558 |
{ |
{ |
559 |
readFully (buf, 0, 2); |
readFully (buf, 0, 2); |
560 |
return (((buf[0] & 0xff) << 8) | (buf[1] & 0xff)); |
return convertToUnsignedShort (buf); |
561 |
} |
} |
562 |
|
|
563 |
/** |
/** |
631 |
* |
* |
632 |
* @see DataOutput#writeUTF |
* @see DataOutput#writeUTF |
633 |
*/ |
*/ |
634 |
public final String readUTF() throws IOException |
public final String readUTF () throws IOException |
635 |
{ |
{ |
636 |
int UTFlen = readUnsignedShort(); |
return readUTF (this); |
|
byte[] buf = new byte[UTFlen]; |
|
|
|
|
|
// This blocks until the entire string is available rather than |
|
|
// doing partial processing on the bytes that are available and then |
|
|
// blocking. An advantage of the latter is that Exceptions |
|
|
// could be thrown earlier. The former is a bit cleaner. |
|
|
readFully(buf, 0, UTFlen); |
|
|
|
|
|
// FIXME: This should probably be replaced with something like |
|
|
// new String(buf, "UTF-8"), however that might take a performance |
|
|
// due to time looking up encoders. But having a private UTF |
|
|
// converter may not be the way to go either. |
|
|
return (convertFromUTF(buf)); |
|
637 |
} |
} |
638 |
|
|
639 |
/** |
/** |
648 |
* |
* |
649 |
* @see DataInput#readUTF |
* @see DataInput#readUTF |
650 |
*/ |
*/ |
651 |
public final static String readUTF(DataInput in) throws IOException |
public final static String readUTF (DataInput in) throws IOException |
652 |
{ |
{ |
653 |
final int UTFlen = in.readUnsignedShort(); |
final int UTFlen = in.readUnsignedShort (); |
654 |
byte[] buf = new byte[UTFlen]; |
byte[] buf = new byte [UTFlen]; |
655 |
|
|
656 |
// This blocks until the entire string is available rather than |
// This blocks until the entire string is available rather than |
657 |
// doing partial processing on the bytes that are available and then |
// doing partial processing on the bytes that are available and then |
658 |
// blocking. An advantage of the latter is that Exceptions |
// blocking. An advantage of the latter is that Exceptions |
659 |
// could be thrown earlier. The former is a bit cleaner. |
// could be thrown earlier. The former is a bit cleaner. |
660 |
in.readFully(buf, 0, UTFlen); |
in.readFully (buf, 0, UTFlen); |
661 |
|
|
662 |
return convertFromUTF(buf); |
return convertFromUTF (buf); |
663 |
} |
} |
664 |
|
|
665 |
/** |
/** |
678 |
* EOFException. Neither of these appear to be true in the JDK 1.3's |
* EOFException. Neither of these appear to be true in the JDK 1.3's |
679 |
* implementation. This tries to implement the actual JDK behaviour. |
* implementation. This tries to implement the actual JDK behaviour. |
680 |
*/ |
*/ |
681 |
public final int skipBytes(int n) throws IOException |
public final int skipBytes (int n) throws IOException |
682 |
{ |
{ |
683 |
if (n <= 0) |
if (n <= 0) |
684 |
return 0; |
return 0; |
685 |
try |
try |
686 |
{ |
{ |
687 |
return (int) in.skip(n); |
return (int) in.skip (n); |
688 |
} |
} |
689 |
catch (EOFException x) |
catch (EOFException x) |
690 |
{ |
{ |
692 |
} |
} |
693 |
return n; |
return n; |
694 |
} |
} |
695 |
|
|
696 |
|
static boolean convertToBoolean (int b) throws EOFException |
697 |
|
{ |
698 |
|
if (b < 0) |
699 |
|
throw new EOFException (); |
700 |
|
|
701 |
|
return (b != 0); |
702 |
|
} |
703 |
|
|
704 |
|
static byte convertToByte (int i) throws EOFException |
705 |
|
{ |
706 |
|
if (i < 0) |
707 |
|
throw new EOFException (); |
708 |
|
|
709 |
|
return (byte) i; |
710 |
|
} |
711 |
|
|
712 |
|
static int convertToUnsignedByte (int i) throws EOFException |
713 |
|
{ |
714 |
|
if (i < 0) |
715 |
|
throw new EOFException (); |
716 |
|
|
717 |
|
return (i & 0xFF); |
718 |
|
} |
719 |
|
|
720 |
|
static char convertToChar (byte[] buf) |
721 |
|
{ |
722 |
|
return (char) ((buf [0] << 8) |
723 |
|
| (buf [1] & 0xff)); |
724 |
|
} |
725 |
|
|
726 |
|
static short convertToShort (byte[] buf) |
727 |
|
{ |
728 |
|
return (short) ((buf [0] << 8) |
729 |
|
| (buf [1] & 0xff)); |
730 |
|
} |
731 |
|
|
732 |
|
static int convertToUnsignedShort (byte[] buf) |
733 |
|
{ |
734 |
|
return (((buf [0] & 0xff) << 8) |
735 |
|
| (buf [1] & 0xff)); |
736 |
|
} |
737 |
|
|
738 |
|
static int convertToInt (byte[] buf) |
739 |
|
{ |
740 |
|
return (((buf [0] & 0xff) << 24) |
741 |
|
| ((buf [1] & 0xff) << 16) |
742 |
|
| ((buf [2] & 0xff) << 8) |
743 |
|
| (buf [3] & 0xff)); |
744 |
|
} |
745 |
|
|
746 |
|
static long convertToLong (byte[] buf) |
747 |
|
{ |
748 |
|
return (((long)(buf [0] & 0xff) << 56) | |
749 |
|
((long)(buf [1] & 0xff) << 48) | |
750 |
|
((long)(buf [2] & 0xff) << 40) | |
751 |
|
((long)(buf [3] & 0xff) << 32) | |
752 |
|
((long)(buf [4] & 0xff) << 24) | |
753 |
|
((long)(buf [5] & 0xff) << 16) | |
754 |
|
((long)(buf [6] & 0xff) << 8) | |
755 |
|
((long)(buf [7] & 0xff))); |
756 |
|
} |
757 |
|
|
758 |
|
// FIXME: This method should be re-thought. I suspect we have multiple |
759 |
|
// UTF-8 decoders floating around. We should use the standard charset |
760 |
|
// converters, maybe and adding a direct call into one of the new |
761 |
|
// NIO converters for a super-fast UTF8 decode. |
762 |
|
static String convertFromUTF (byte[] buf) |
763 |
|
throws EOFException, UTFDataFormatException |
764 |
|
{ |
765 |
|
// Give StringBuffer an initial estimated size to avoid |
766 |
|
// enlarge buffer frequently |
767 |
|
StringBuffer strbuf = new StringBuffer (buf.length / 2 + 2); |
768 |
|
|
769 |
|
for (int i = 0; i < buf.length; ) |
770 |
|
{ |
771 |
|
if ((buf [i] & 0x80) == 0) // bit pattern 0xxxxxxx |
772 |
|
strbuf.append ((char) (buf [i++] & 0xFF)); |
773 |
|
else if ((buf [i] & 0xE0) == 0xC0) // bit pattern 110xxxxx |
774 |
|
{ |
775 |
|
if (i + 1 >= buf.length |
776 |
|
|| (buf [i + 1] & 0xC0) != 0x80) |
777 |
|
throw new UTFDataFormatException (); |
778 |
|
|
779 |
|
strbuf.append((char) (((buf [i++] & 0x1F) << 6) |
780 |
|
| (buf [i++] & 0x3F))); |
781 |
|
} |
782 |
|
else if ((buf [i] & 0xF0) == 0xE0) // bit pattern 1110xxxx |
783 |
|
{ |
784 |
|
if (i + 2 >= buf.length |
785 |
|
|| (buf [i + 1] & 0xC0) != 0x80 |
786 |
|
|| (buf [i + 2] & 0xC0) != 0x80) |
787 |
|
throw new UTFDataFormatException (); |
788 |
|
|
789 |
|
strbuf.append ((char) (((buf [i++] & 0x0F) << 12) |
790 |
|
| ((buf [i++] & 0x3F) << 6) |
791 |
|
| (buf [i++] & 0x3F))); |
792 |
|
} |
793 |
|
else // must be ((buf [i] & 0xF0) == 0xF0 || (buf [i] & 0xC0) == 0x80) |
794 |
|
throw new UTFDataFormatException (); // bit patterns 1111xxxx or |
795 |
|
// 10xxxxxx |
796 |
|
} |
797 |
|
|
798 |
|
return strbuf.toString (); |
799 |
|
} |
800 |
} |
} |