38 |
|
|
39 |
package java.io; |
package java.io; |
40 |
|
|
41 |
import java.nio.channels.Channels; |
import java.nio.charset.UnsupportedCharsetException; |
42 |
|
import java.nio.charset.CharacterCodingException; |
43 |
|
import java.nio.charset.IllegalCharsetNameException; |
44 |
|
import java.nio.charset.CoderResult; |
45 |
|
import java.nio.charset.CodingErrorAction; |
46 |
import java.nio.charset.Charset; |
import java.nio.charset.Charset; |
47 |
import java.nio.charset.CharsetDecoder; |
import java.nio.charset.CharsetDecoder; |
48 |
|
import java.nio.CharBuffer; |
49 |
import gnu.java.io.EncodingManager; |
import java.nio.ByteBuffer; |
50 |
import gnu.java.io.decode.Decoder; |
import gnu.java.nio.charset.EncodingHelper; |
51 |
|
|
52 |
/** |
/** |
53 |
* This class reads characters from a byte input stream. The characters |
* This class reads characters from a byte input stream. The characters |
90 |
* @see BufferedReader |
* @see BufferedReader |
91 |
* @see InputStream |
* @see InputStream |
92 |
* |
* |
93 |
|
* @author Robert Schuster |
94 |
* @author Aaron M. Renn (arenn@urbanophile.com) |
* @author Aaron M. Renn (arenn@urbanophile.com) |
95 |
* @author Per Bothner (bothner@cygnus.com) |
* @author Per Bothner (bothner@cygnus.com) |
96 |
* @date April 22, 1998. |
* @date April 22, 1998. |
97 |
*/ |
*/ |
98 |
public class InputStreamReader extends Reader |
public class InputStreamReader extends Reader |
99 |
{ |
{ |
100 |
/* |
/** |
101 |
* This is the byte-character decoder class that does the reading and |
* The input stream. |
102 |
* translation of bytes from the underlying stream. |
*/ |
103 |
|
private InputStream in; |
104 |
|
|
105 |
|
/** |
106 |
|
* The charset decoder. |
107 |
|
*/ |
108 |
|
private CharsetDecoder decoder; |
109 |
|
|
110 |
|
/** |
111 |
|
* End of stream reached. |
112 |
|
*/ |
113 |
|
private boolean isDone = false; |
114 |
|
|
115 |
|
/** |
116 |
|
* Need this. |
117 |
*/ |
*/ |
118 |
private Reader in; |
private float maxBytesPerChar; |
119 |
|
|
120 |
|
/** |
121 |
|
* Buffer holding surplus loaded bytes (if any) |
122 |
|
*/ |
123 |
|
private ByteBuffer byteBuffer; |
124 |
|
|
125 |
|
/** |
126 |
|
* java.io canonical name of the encoding. |
127 |
|
*/ |
128 |
private String encoding; |
private String encoding; |
129 |
|
|
130 |
|
/** |
131 |
|
* We might decode to a 2-char UTF-16 surrogate, which won't fit in the |
132 |
|
* output buffer. In this case we need to save the surrogate char. |
133 |
|
*/ |
134 |
|
private char savedSurrogate; |
135 |
|
private boolean hasSavedSurrogate = false; |
136 |
|
|
137 |
/** |
/** |
138 |
* This method initializes a new instance of <code>InputStreamReader</code> |
* This method initializes a new instance of <code>InputStreamReader</code> |
139 |
* to read from the specified stream using the default encoding. |
* to read from the specified stream using the default encoding. |
144 |
{ |
{ |
145 |
if (in == null) |
if (in == null) |
146 |
throw new NullPointerException(); |
throw new NullPointerException(); |
147 |
|
this.in = in; |
148 |
Decoder decoder = EncodingManager.getDecoder(in); |
try |
149 |
encoding = decoder.getSchemeName(); |
{ |
150 |
|
encoding = System.getProperty("file.encoding"); |
151 |
this.in = decoder; |
// Don't use NIO if avoidable |
152 |
|
if(EncodingHelper.isISOLatin1(encoding)) |
153 |
|
{ |
154 |
|
encoding = "ISO8859_1"; |
155 |
|
maxBytesPerChar = 1f; |
156 |
|
decoder = null; |
157 |
|
return; |
158 |
|
} |
159 |
|
Charset cs = EncodingHelper.getCharset(encoding); |
160 |
|
decoder = cs.newDecoder(); |
161 |
|
encoding = EncodingHelper.getOldCanonical(cs.name()); |
162 |
|
try { |
163 |
|
maxBytesPerChar = cs.newEncoder().maxBytesPerChar(); |
164 |
|
} catch(UnsupportedOperationException _){ |
165 |
|
maxBytesPerChar = 1f; |
166 |
|
} |
167 |
|
decoder.onMalformedInput(CodingErrorAction.REPLACE); |
168 |
|
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); |
169 |
|
decoder.reset(); |
170 |
|
} catch(RuntimeException e) { |
171 |
|
encoding = "ISO8859_1"; |
172 |
|
maxBytesPerChar = 1f; |
173 |
|
decoder = null; |
174 |
|
} catch(UnsupportedEncodingException e) { |
175 |
|
encoding = "ISO8859_1"; |
176 |
|
maxBytesPerChar = 1f; |
177 |
|
decoder = null; |
178 |
|
} |
179 |
} |
} |
180 |
|
|
181 |
/** |
/** |
197 |
|| encoding_name == null) |
|| encoding_name == null) |
198 |
throw new NullPointerException(); |
throw new NullPointerException(); |
199 |
|
|
200 |
Decoder decoder = EncodingManager.getDecoder(in, encoding_name); |
this.in = in; |
201 |
encoding = decoder.getSchemeName(); |
// Don't use NIO if avoidable |
202 |
|
if(EncodingHelper.isISOLatin1(encoding_name)) |
203 |
this.in = decoder; |
{ |
204 |
|
encoding = "ISO8859_1"; |
205 |
|
maxBytesPerChar = 1f; |
206 |
|
decoder = null; |
207 |
|
return; |
208 |
|
} |
209 |
|
try { |
210 |
|
Charset cs = EncodingHelper.getCharset(encoding_name); |
211 |
|
try { |
212 |
|
maxBytesPerChar = cs.newEncoder().maxBytesPerChar(); |
213 |
|
} catch(UnsupportedOperationException _){ |
214 |
|
maxBytesPerChar = 1f; |
215 |
|
} |
216 |
|
|
217 |
|
decoder = cs.newDecoder(); |
218 |
|
decoder.onMalformedInput(CodingErrorAction.REPLACE); |
219 |
|
decoder.onUnmappableCharacter(CodingErrorAction.REPLACE); |
220 |
|
decoder.reset(); |
221 |
|
|
222 |
|
// The encoding should be the old name, if such exists. |
223 |
|
encoding = EncodingHelper.getOldCanonical(cs.name()); |
224 |
|
} catch(RuntimeException e) { |
225 |
|
encoding = "ISO8859_1"; |
226 |
|
maxBytesPerChar = 1f; |
227 |
|
decoder = null; |
228 |
|
} |
229 |
} |
} |
230 |
|
|
231 |
/** |
/** |
232 |
* Creates an InputStreamReader that uses a decoder of the given |
* Creates an InputStreamReader that uses a decoder of the given |
233 |
* charset to decode the bytes in the InputStream into |
* charset to decode the bytes in the InputStream into |
234 |
* characters. |
* characters. |
|
* @since 1.4 |
|
235 |
*/ |
*/ |
236 |
public InputStreamReader(InputStream in, Charset charset) |
public InputStreamReader(InputStream in, Charset charset) { |
237 |
{ |
this.in = in; |
238 |
/* FIXME: InputStream is wrapped in Channel which is read by a |
decoder = charset.newDecoder(); |
239 |
* Reader-implementation for channels. However to fix this we |
|
240 |
* need to completely move to NIO-style character |
// JDK reports errors, so we do the same. |
241 |
* encoding/decoding. |
decoder.onMalformedInput(CodingErrorAction.REPORT); |
242 |
*/ |
decoder.onUnmappableCharacter(CodingErrorAction.REPORT); |
243 |
this.in = Channels.newReader(Channels.newChannel(in), charset.newDecoder(), |
decoder.reset(); |
244 |
-1); |
encoding = EncodingHelper.getOldCanonical(charset.name()); |
|
encoding = charset.name(); |
|
245 |
} |
} |
246 |
|
|
247 |
/** |
/** |
248 |
* Creates an InputStreamReader that uses the given charset decoder |
* Creates an InputStreamReader that uses the given charset decoder |
249 |
* to decode the bytes in the InputStream into characters. |
* to decode the bytes in the InputStream into characters. |
|
* @since 1.4 |
|
250 |
*/ |
*/ |
251 |
public InputStreamReader(InputStream in, CharsetDecoder decoder) |
public InputStreamReader(InputStream in, CharsetDecoder decoder) { |
252 |
{ |
this.in = in; |
253 |
// FIXME: see {@link InputStreamReader(InputStream, Charset) |
this.decoder = decoder; |
254 |
this.in = Channels.newReader(Channels.newChannel(in), decoder, -1); |
|
255 |
encoding = decoder.charset().name(); |
try { |
256 |
|
maxBytesPerChar = decoder.charset().newEncoder().maxBytesPerChar(); |
257 |
|
} catch(UnsupportedOperationException _){ |
258 |
|
maxBytesPerChar = 1f; |
259 |
|
} |
260 |
|
|
261 |
|
// JDK reports errors, so we do the same. |
262 |
|
decoder.onMalformedInput(CodingErrorAction.REPORT); |
263 |
|
decoder.onUnmappableCharacter(CodingErrorAction.REPORT); |
264 |
|
decoder.reset(); |
265 |
|
encoding = EncodingHelper.getOldCanonical(decoder.charset().name()); |
266 |
} |
} |
267 |
|
|
268 |
/** |
/** |
275 |
{ |
{ |
276 |
synchronized (lock) |
synchronized (lock) |
277 |
{ |
{ |
278 |
|
// Makes sure all intermediate data is released by the decoder. |
279 |
|
if (decoder != null) |
280 |
|
decoder.reset(); |
281 |
if (in != null) |
if (in != null) |
282 |
in.close(); |
in.close(); |
283 |
in = null; |
in = null; |
284 |
|
isDone = true; |
285 |
|
decoder = null; |
286 |
} |
} |
287 |
} |
} |
288 |
|
|
299 |
} |
} |
300 |
|
|
301 |
/** |
/** |
302 |
* This method checks to see if the stream is read to be read. It |
* This method checks to see if the stream is ready to be read. It |
303 |
* will return <code>true</code> if is, or <code>false</code> if it is not. |
* will return <code>true</code> if is, or <code>false</code> if it is not. |
304 |
* If the stream is not ready to be read, it could (although is not required |
* If the stream is not ready to be read, it could (although is not required |
305 |
* to) block on the next read attempt. |
* to) block on the next read attempt. |
314 |
if (in == null) |
if (in == null) |
315 |
throw new IOException("Reader has been closed"); |
throw new IOException("Reader has been closed"); |
316 |
|
|
317 |
return in.ready(); |
return in.available() != 0; |
318 |
} |
} |
319 |
|
|
320 |
/** |
/** |
330 |
* |
* |
331 |
* @exception IOException If an error occurs |
* @exception IOException If an error occurs |
332 |
*/ |
*/ |
333 |
public int read (char[] buf, int offset, int length) throws IOException |
public int read(char[] buf, int offset, int length) throws IOException |
334 |
{ |
{ |
335 |
if (in == null) |
if (in == null) |
336 |
throw new IOException("Reader has been closed"); |
throw new IOException("Reader has been closed"); |
337 |
|
if (isDone) |
338 |
return in.read(buf, offset, length); |
return -1; |
339 |
|
|
340 |
|
if(decoder != null){ |
341 |
|
int totalBytes = (int)((double)length * maxBytesPerChar); |
342 |
|
byte[] bytes = new byte[totalBytes]; |
343 |
|
|
344 |
|
int remaining = 0; |
345 |
|
if(byteBuffer != null) |
346 |
|
{ |
347 |
|
remaining = byteBuffer.remaining(); |
348 |
|
byteBuffer.get(bytes, 0, remaining); |
349 |
|
} |
350 |
|
int read; |
351 |
|
if(totalBytes - remaining > 0) |
352 |
|
{ |
353 |
|
read = in.read(bytes, remaining, totalBytes - remaining); |
354 |
|
if(read == -1){ |
355 |
|
read = remaining; |
356 |
|
isDone = true; |
357 |
|
} else |
358 |
|
read += remaining; |
359 |
|
} else |
360 |
|
read = remaining; |
361 |
|
byteBuffer = ByteBuffer.wrap(bytes, 0, read); |
362 |
|
CharBuffer cb = CharBuffer.wrap(buf, offset, length); |
363 |
|
|
364 |
|
if(hasSavedSurrogate){ |
365 |
|
hasSavedSurrogate = false; |
366 |
|
cb.put(savedSurrogate); |
367 |
|
read++; |
368 |
|
} |
369 |
|
|
370 |
|
CoderResult cr = decoder.decode(byteBuffer, cb, isDone); |
371 |
|
decoder.reset(); |
372 |
|
|
373 |
|
// 1 char remains which is the first half of a surrogate pair. |
374 |
|
if(cr.isOverflow() && cb.hasRemaining()){ |
375 |
|
CharBuffer overflowbuf = CharBuffer.allocate(2); |
376 |
|
cr = decoder.decode(byteBuffer, overflowbuf, isDone); |
377 |
|
overflowbuf.flip(); |
378 |
|
cb.put(overflowbuf.get()); |
379 |
|
savedSurrogate = overflowbuf.get(); |
380 |
|
hasSavedSurrogate = true; |
381 |
|
isDone = false; |
382 |
|
} |
383 |
|
|
384 |
|
if(byteBuffer.hasRemaining()) { |
385 |
|
byteBuffer.compact(); |
386 |
|
byteBuffer.flip(); |
387 |
|
isDone = false; |
388 |
|
} else |
389 |
|
byteBuffer = null; |
390 |
|
|
391 |
|
return (read == 0)?-1:cb.position(); |
392 |
|
} else { |
393 |
|
byte[] bytes = new byte[length]; |
394 |
|
int read = in.read(bytes); |
395 |
|
for(int i=0;i<read;i++) |
396 |
|
buf[offset+i] = (char)(bytes[i]&0xFF); |
397 |
|
return read; |
398 |
|
} |
399 |
} |
} |
400 |
|
|
401 |
/** |
/** |
402 |
* This method reads a single character of data from the stream. |
* Reads an char from the input stream and returns it |
403 |
|
* as an int in the range of 0-65535. This method also will return -1 if |
404 |
|
* the end of the stream has been reached. |
405 |
|
* <p> |
406 |
|
* This method will block until the char can be read. |
407 |
* |
* |
408 |
* @return The char read, as an int, or -1 if end of stream. |
* @return The char read or -1 if end of stream |
409 |
* |
* |
410 |
* @exception IOException If an error occurs |
* @exception IOException If an error occurs |
411 |
*/ |
*/ |
412 |
public int read() throws IOException |
public int read() throws IOException |
413 |
{ |
{ |
414 |
if (in == null) |
char[] buf = new char[1]; |
415 |
throw new IOException("Reader has been closed"); |
int count = read(buf, 0, 1); |
416 |
|
return count > 0 ? buf[0] : -1; |
|
return in.read(); |
|
417 |
} |
} |
418 |
|
|
419 |
/** |
/** |
420 |
* Skips the specified number of chars in the stream. It |
* Skips the specified number of chars in the stream. It |
421 |
* returns the actual number of chars skipped, which may be less than the |
* returns the actual number of chars skipped, which may be less than the |
422 |
* requested amount. |
* requested amount. |
423 |
* |
* |
424 |
* @param count The requested number of chars to skip |
* @param count The requested number of chars to skip |
425 |
* |
* |
426 |
* @return The actual number of chars skipped. |
* @return The actual number of chars skipped. |
427 |
* |
* |
428 |
* @exception IOException If an error occurs |
* @exception IOException If an error occurs |
429 |
*/ |
*/ |
430 |
public long skip(long count) throws IOException |
public long skip(long count) throws IOException |
431 |
{ |
{ |
432 |
if (in == null) |
if (in == null) |
433 |
throw new IOException("Reader has been closed"); |
throw new IOException("Reader has been closed"); |
434 |
|
|
435 |
return super.skip(count); |
return super.skip(count); |
436 |
} |
} |
437 |
} |
} |