Skip to content

Commit 4c83c48

Browse files
author
Niall Pemberton
committed
IO-158 New ReaderInputStream and WriterOutputStream implementations - thanks to Andreas Veithen for the patch
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@723912 13f79535-47bb-0310-9956-ffa450edef68
1 parent 30fca89 commit 4c83c48

4 files changed

Lines changed: 711 additions & 0 deletions

File tree

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.io.input;
18+
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.Reader;
22+
import java.nio.ByteBuffer;
23+
import java.nio.CharBuffer;
24+
import java.nio.charset.Charset;
25+
import java.nio.charset.CharsetEncoder;
26+
import java.nio.charset.CoderResult;
27+
28+
/**
29+
* {@link InputStream} implementation that reads a character stream from a {@link Reader}
30+
* and transforms it to a byte stream using a specified charset encoding. The stream
31+
* is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
32+
* encodings supported by the JRE are handled correctly. In particular for charsets such as
33+
* UTF-16, the implementation ensures that one and only one byte order marker
34+
* is produced.
35+
* <p>
36+
* Since in general it is not possible to predict the number of characters to be read from the
37+
* {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
38+
* the {@link Reader} are buffered. There is therefore no well defined correlation
39+
* between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
40+
* This also implies that in general there is no need to wrap the underlying {@link Reader}
41+
* in a {@link java.io.BufferedReader}.
42+
* <p>
43+
* {@link ReaderInputStream} implements the inverse transformation of {@link java.io.InputStreamReader};
44+
* in the following example, reading from <tt>in2</tt> would return the same byte
45+
* sequence as reading from <tt>in</tt> (provided that the initial byte sequence is legal
46+
* with respect to the charset encoding):
47+
* <pre>
48+
* InputStream in = ...
49+
* Charset cs = ...
50+
* InputStreamReader reader = new InputStreamReader(in, cs);
51+
* ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
52+
* {@link ReaderInputStream} implements the same transformation as {@link java.io.OutputStreamWriter},
53+
* except that the control flow is reversed: both classes transform a character stream
54+
* into a byte stream, but {@link java.io.OutputStreamWriter} pushes data to the underlying stream,
55+
* while {@link ReaderInputStream} pulls it from the underlying stream.
56+
* <p>
57+
* Note that while there are use cases where there is no alternative to using
58+
* this class, very often the need to use this class is an indication of a flaw
59+
* in the design of the code. This class is typically used in situations where an existing
60+
* API only accepts an {@link InputStream}, but where the most natural way to produce the data
61+
* is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
62+
* where this problem may appear is when implementing the {@link javax.activation.DataSource}
63+
* interface from the Java Activation Framework.
64+
* <p>
65+
* Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
66+
* read operation will block or not, it is not possible to provide a meaningful
67+
* implementation of the {@link InputStream#available()} method. A call to this method
68+
* will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
69+
* <p>
70+
* Instances of {@link ReaderInputStream} are not thread safe.
71+
*
72+
* @see org.apache.commons.io.output.WriterOutputStream
73+
*
74+
* @author <a href="mailto:veithen@apache.org">Andreas Veithen</a>
75+
* @since Commons IO 2.0
76+
*/
77+
public class ReaderInputStream extends InputStream {
78+
private static final int DEFAULT_BUFFER_SIZE = 1024;
79+
80+
private final Reader reader;
81+
private final CharsetEncoder encoder;
82+
83+
/**
84+
* CharBuffer used as input for the decoder. It should be reasonably
85+
* large as we read data from the underlying Reader into this buffer.
86+
*/
87+
private final CharBuffer encoderIn;
88+
89+
/**
90+
* ByteBuffer used as output for the decoder. This buffer can be small
91+
* as it is only used to transfer data from the decoder to the
92+
* buffer provided by the caller.
93+
*/
94+
private final ByteBuffer encoderOut = ByteBuffer.allocate(128);
95+
96+
private CoderResult lastCoderResult;
97+
private boolean endOfInput;
98+
99+
/**
100+
* Construct a new {@link ReaderInputStream}.
101+
*
102+
* @param reader the target {@link Reader}
103+
* @param charset the charset encoding
104+
* @param bufferSize the size of the input buffer in number of characters
105+
*/
106+
public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
107+
this.reader = reader;
108+
encoder = charset.newEncoder();
109+
encoderIn = CharBuffer.allocate(bufferSize);
110+
encoderIn.flip();
111+
}
112+
113+
/**
114+
* Construct a new {@link ReaderInputStream} with a default input buffer size of
115+
* 1024 characters.
116+
*
117+
* @param reader the target {@link Reader}
118+
* @param charset the charset encoding
119+
*/
120+
public ReaderInputStream(Reader reader, Charset charset) {
121+
this(reader, charset, DEFAULT_BUFFER_SIZE);
122+
}
123+
124+
/**
125+
* Construct a new {@link ReaderInputStream}.
126+
*
127+
* @param reader the target {@link Reader}
128+
* @param charsetName the name of the charset encoding
129+
* @param bufferSize the size of the input buffer in number of characters
130+
*/
131+
public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
132+
this(reader, Charset.forName(charsetName), bufferSize);
133+
}
134+
135+
/**
136+
* Construct a new {@link ReaderInputStream} with a default input buffer size of
137+
* 1024 characters.
138+
*
139+
* @param reader the target {@link Reader}
140+
* @param charsetName the name of the charset encoding
141+
*/
142+
public ReaderInputStream(Reader reader, String charsetName) {
143+
this(reader, charsetName, DEFAULT_BUFFER_SIZE);
144+
}
145+
146+
/**
147+
* Construct a new {@link ReaderInputStream} that uses the default character encoding
148+
* with a default input buffer size of 1024 characters.
149+
*
150+
* @param reader the target {@link Reader}
151+
*/
152+
public ReaderInputStream(Reader reader) {
153+
this(reader, Charset.defaultCharset());
154+
}
155+
156+
/**
157+
* Read the specified number of bytes into an array.
158+
*
159+
* @param b the byte array to read into
160+
* @param off the offset to start reading bytes into
161+
* @param len the number of bytes to read
162+
* @return the number of bytes read or <code>-1</code>
163+
* if the end of the stream has been reached
164+
* @throws IOException if an I/O error occurs
165+
*/
166+
@Override
167+
public int read(byte[] b, int off, int len) throws IOException {
168+
int read = 0;
169+
while (len > 0) {
170+
if (encoderOut.position() > 0) {
171+
encoderOut.flip();
172+
int c = Math.min(encoderOut.remaining(), len);
173+
encoderOut.get(b, off, c);
174+
off += c;
175+
len -= c;
176+
read += c;
177+
encoderOut.compact();
178+
} else {
179+
if (!endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
180+
encoderIn.compact();
181+
int position = encoderIn.position();
182+
// We don't use Reader#read(CharBuffer) here because it is more efficient
183+
// to write directly to the underlying char array (the default implementation
184+
// copies data to a temporary char array).
185+
int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
186+
if (c == -1) {
187+
endOfInput = true;
188+
} else {
189+
encoderIn.position(position+c);
190+
}
191+
encoderIn.flip();
192+
}
193+
lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
194+
if (endOfInput && encoderOut.position() == 0) {
195+
break;
196+
}
197+
}
198+
}
199+
return read == 0 && endOfInput ? -1 : read;
200+
}
201+
202+
/**
203+
* Read the specified number of bytes into an array.
204+
*
205+
* @param b the byte array to read into
206+
* @return the number of bytes read or <code>-1</code>
207+
* if the end of the stream has been reached
208+
* @throws IOException if an I/O error occurs
209+
*/
210+
@Override
211+
public int read(byte[] b) throws IOException {
212+
return read(b, 0, b.length);
213+
}
214+
215+
/**
216+
* Read a single byte.
217+
*
218+
* @return either the byte read or <code>-1</code> if the end of the stream
219+
* has been reached
220+
* @throws IOException if an I/O error occurs
221+
*/
222+
@Override
223+
public int read() throws IOException {
224+
byte[] b = new byte[1];
225+
return read(b) == -1 ? -1 : b[0] & 0xFF;
226+
}
227+
228+
/**
229+
* Close the stream. This method will cause the underlying {@link Reader}
230+
* to be closed.
231+
* @throws IOException if an I/O error occurs
232+
*/
233+
@Override
234+
public void close() throws IOException {
235+
reader.close();
236+
}
237+
}

0 commit comments

Comments
 (0)