001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *   http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.io;
018
019
020import java.io.*;
021import java.nio.*;
022import java.nio.charset.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024
025/**
026 * {@link InputStream} implementation that reads a character stream from a {@link Reader}
027 * and transforms it to a byte stream using a specified charset encoding. The stream
028 * is transformed using a {@link CharsetEncoder} object, guaranteeing that all charset
029 * encodings supported by the JRE are handled correctly. In particular for charsets such as
030 * UTF-16, the implementation ensures that one and only one byte order marker
031 * is produced.
032 * <p>
033 * Since in general it is not possible to predict the number of characters to be read from the
034 * {@link Reader} to satisfy a read request on the {@link ReaderInputStream}, all reads from
035 * the {@link Reader} are buffered. There is therefore no well defined correlation
036 * between the current position of the {@link Reader} and that of the {@link ReaderInputStream}.
037 * This also implies that in general there is no need to wrap the underlying {@link Reader}
038 * in a {@link BufferedReader}.
039 * <p>
040 * {@link ReaderInputStream} implements the inverse transformation of {@link InputStreamReader};
041 * in the following example, reading from {@code in2} would return the same byte
042 * sequence as reading from {@code in} (provided that the initial byte sequence is legal
043 * with respect to the charset encoding):
044 * <pre>
045 * InputStream in = ...
046 * Charset cs = ...
047 * InputStreamReader reader = new InputStreamReader(in, cs);
048 * ReaderInputStream in2 = new ReaderInputStream(reader, cs);</pre>
049 * {@link ReaderInputStream} implements the same transformation as {@link OutputStreamWriter},
050 * except that the control flow is reversed: both classes transform a character stream
051 * into a byte stream, but {@link OutputStreamWriter} pushes data to the underlying stream,
052 * while {@link ReaderInputStream} pulls it from the underlying stream.
053 * <p>
054 * Note that while there are use cases where there is no alternative to using
055 * this class, very often the need to use this class is an indication of a flaw
056 * in the design of the code. This class is typically used in situations where an existing
057 * API only accepts an {@link InputStream}, but where the most natural way to produce the data
058 * is as a character stream, i.e. by providing a {@link Reader} instance. An example of a situation
059 * where this problem may appear is when implementing the {@code javax.activation.DataSource}
060 * interface from the Java Activation Framework.
061 * <p>
062 * Given the fact that the {@link Reader} class doesn't provide any way to predict whether the next
063 * read operation will block or not, it is not possible to provide a meaningful
064 * implementation of the {@link InputStream#available()} method. A call to this method
065 * will always return 0. Also, this class doesn't support {@link InputStream#mark(int)}.
066 * <p>
067 *
068 * <h5 class='section'>Notes:</h5><ul>
069 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
070 * </ul>
071 *
072 */
073@SuppressWarnings({
074   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
075   "resource" // Intentionally not owned; caller retains responsibility for closing the underlying Reader
076})
077public class ReaderInputStream extends InputStream {
078   private static final int DEFAULT_BUFFER_SIZE = 1024;
079
080   private final Reader reader;
081   private final CharsetEncoder encoder;
082
083   /**
084    * CharBuffer used as input for the decoder. It should be reasonably
085    * large as we read data from the underlying Reader into this buffer.
086    */
087   private final CharBuffer encoderIn;
088
089   /**
090    * ByteBuffer used as output for the decoder. This buffer can be small
091    * as it is only used to transfer data from the decoder to the
092    * buffer provided by the caller.
093    */
094   private final ByteBuffer encoderOut;
095
096   private CoderResult lastCoderResult;
097   private boolean endOfInput;
098
099   /**
100    * Construct a new {@link ReaderInputStream} with a default input buffer size of
101    * <c>1024</c> characters.
102    *
103    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
104    * @param charset the charset encoding.  Must not be <jk>null</jk>.
105    */
106   public ReaderInputStream(Reader reader, Charset charset) {
107      this(reader, charset, DEFAULT_BUFFER_SIZE);
108   }
109
110   /**
111    * Construct a new {@link ReaderInputStream}.
112    *
113    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
114    * @param charset the charset encoding.  Must not be <jk>null</jk>.
115    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
116    */
117   public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
118      // @formatter:off
119      this(reqnn("reader", reader),
120          reqnn("charset", charset).newEncoder()
121               .onMalformedInput(CodingErrorAction.REPLACE)
122               .onUnmappableCharacter(CodingErrorAction.REPLACE),
123          bufferSize
124      );
125      // @formatter:on
126   }
127
128   /**
129    * Construct a new {@link ReaderInputStream}.
130    *
131    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
132    * @param encoder the charset encoder.  Must not be <jk>null</jk>.
133    * @since 2.1
134    */
135   public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
136      this(reader, encoder, DEFAULT_BUFFER_SIZE);
137   }
138
139   /**
140    * Construct a new {@link ReaderInputStream}.
141    *
142    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
143    * @param encoder the charset encoder.  Must not be <jk>null</jk>.
144    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
145    */
146   public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
147      this.reader = reqnn("reader", reader);
148      this.encoder = reqnn("encoder", encoder);
149      req(bufferSize > 0, "Argument 'bufferSize' must be positive.");
150      this.encoderIn = CharBuffer.allocate(bufferSize);
151      this.encoderIn.flip(); // Fixes Java 11 issue.
152      this.encoderOut = ByteBuffer.allocate(128);
153      this.encoderOut.flip(); // Fixes Java 11 issue.
154   }
155
156   /**
157    * Construct a new {@link ReaderInputStream} with a default input buffer size of
158    * <c>1024</c> characters.
159    *
160    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
161    * @param charsetName the name of the charset encoding.  Must not be <jk>null</jk>.
162    */
163   public ReaderInputStream(Reader reader, String charsetName) {
164      this(reader, charsetName, DEFAULT_BUFFER_SIZE);
165   }
166
167   /**
168    * Construct a new {@link ReaderInputStream}.
169    *
170    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
171    * @param charsetName the name of the charset encoding.  Must not be <jk>null</jk>.
172    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
173    */
174   public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
175      this(reader, Charset.forName(reqnn("charsetName", charsetName)), bufferSize);
176   }
177
178   /**
179    * Close the stream. This method will cause the underlying {@link Reader}
180    * to be closed.
181    * @throws IOException if an I/O error occurs
182    */
183   @Override
184   public void close() throws IOException {
185      reader.close();
186   }
187
188   /**
189    * Read a single byte.
190    *
191    * @return either the byte read or <code>-1</code> if the end of the stream
192    *     has been reached
193    * @throws IOException if an I/O error occurs
194    */
195   @Override
196   public int read() throws IOException {
197      for (;;) {
198         if (encoderOut.hasRemaining()) {
199            return encoderOut.get() & 0xFF;
200         }
201         fillBuffer();
202         if (endOfInput && ! encoderOut.hasRemaining()) {
203            return -1;
204         }
205      }
206   }
207
208   /**
209    * Read the specified number of bytes into an array.
210    *
211    * @param b the byte array to read into.  Must not be <jk>null</jk>.
212    * @return the number of bytes read or <code>-1</code>
213    *     if the end of the stream has been reached
214    * @throws IOException if an I/O error occurs
215    */
216   @Override
217   public int read(byte[] b) throws IOException {
218      reqnn("b", b);
219      return read(b, 0, b.length);
220   }
221
222   /**
223    * Read the specified number of bytes into an array.
224    *
225    * @param array the byte array to read into.  Must not be <jk>null</jk>.
226    * @param off the offset to start reading bytes into
227    * @param len the number of bytes to read
228    * @return the number of bytes read or <code>-1</code>
229    *     if the end of the stream has been reached
230    * @throws IOException if an I/O error occurs
231    */
232   @Override
233   public int read(byte[] array, int off, int len) throws IOException {
234      reqnn("array", array);
235      if (len < 0 || off < 0 || (off + len) > array.length) {
236         throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len);
237      }
238      int read = 0;
239      if (len == 0) {
240         return 0; // Always return 0 if len == 0
241      }
242      while (len > 0) {
243         if (encoderOut.hasRemaining()) {
244            final int c = Math.min(encoderOut.remaining(), len);
245            encoderOut.get(array, off, c);
246            off += c;
247            len -= c;
248            read += c;
249         } else {
250            fillBuffer();
251            if (endOfInput && ! encoderOut.hasRemaining()) {
252               break;
253            }
254         }
255      }
256      return read == 0 && endOfInput ? -1 : read;
257   }
258
259   /**
260    * Fills the internal char buffer from the reader.
261    *
262    * @throws IOException
263    *        If an I/O error occurs
264    */
265   private void fillBuffer() throws IOException {
266      if (! endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
267         encoderIn.compact();
268         final int position = encoderIn.position();
269         // We don't use Reader#read(CharBuffer) here because it is more efficient
270         // to write directly to the underlying char array (the default implementation
271         // copies data to a temporary char array).
272         final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
273         if (c == -1) {
274            endOfInput = true;
275         } else {
276            encoderIn.position(position + c);
277         }
278         encoderIn.flip();
279      }
280      encoderOut.compact();
281      lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
282      encoderOut.flip();
283   }
284}