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 *
067 * <h2 class='section'>Notes:</h2><ul>
068 *    <li class='warn'>This class is not thread safe and is typically discarded after one use.
069 * </ul>
070 *
071 */
072@SuppressWarnings({
073   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
074   "resource" // Intentionally not owned; caller retains responsibility for closing the underlying Reader
075})
076public class ReaderInputStream extends InputStream {
077   private static final int DEFAULT_BUFFER_SIZE = 1024;
078
079   private final Reader reader;
080   private final CharsetEncoder encoder;
081
082   /**
083    * CharBuffer used as input for the decoder. It should be reasonably
084    * large as we read data from the underlying Reader into this buffer.
085    */
086   private final CharBuffer encoderIn;
087
088   /**
089    * ByteBuffer used as output for the decoder. This buffer can be small
090    * as it is only used to transfer data from the decoder to the
091    * buffer provided by the caller.
092    */
093   private final ByteBuffer encoderOut;
094
095   private CoderResult lastCoderResult;
096   private boolean endOfInput;
097
098   /**
099    * Construct a new {@link ReaderInputStream} with a default input buffer size of
100    * <c>1024</c> characters.
101    *
102    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
103    * @param charset the charset encoding.  Must not be <jk>null</jk>.
104    */
105   public ReaderInputStream(Reader reader, Charset charset) {
106      this(reader, charset, DEFAULT_BUFFER_SIZE);
107   }
108
109   /**
110    * Construct a new {@link ReaderInputStream}.
111    *
112    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
113    * @param charset the charset encoding.  Must not be <jk>null</jk>.
114    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
115    */
116   public ReaderInputStream(Reader reader, Charset charset, int bufferSize) {
117      // @formatter:off
118      this(reqnn("reader", reader),
119          reqnn("charset", charset).newEncoder()
120               .onMalformedInput(CodingErrorAction.REPLACE)
121               .onUnmappableCharacter(CodingErrorAction.REPLACE),
122          bufferSize
123      );
124      // @formatter:on
125   }
126
127   /**
128    * Construct a new {@link ReaderInputStream}.
129    *
130    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
131    * @param encoder the charset encoder.  Must not be <jk>null</jk>.
132    * @since 2.1
133    */
134   public ReaderInputStream(Reader reader, CharsetEncoder encoder) {
135      this(reader, encoder, DEFAULT_BUFFER_SIZE);
136   }
137
138   /**
139    * Construct a new {@link ReaderInputStream}.
140    *
141    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
142    * @param encoder the charset encoder.  Must not be <jk>null</jk>.
143    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
144    */
145   public ReaderInputStream(Reader reader, CharsetEncoder encoder, int bufferSize) {
146      this.reader = reqnn("reader", reader);
147      this.encoder = reqnn("encoder", encoder);
148      req(bufferSize > 0, "Argument 'bufferSize' must be positive.");
149      this.encoderIn = CharBuffer.allocate(bufferSize);
150      this.encoderIn.flip(); // Fixes Java 11 issue.
151      this.encoderOut = ByteBuffer.allocate(128);
152      this.encoderOut.flip(); // Fixes Java 11 issue.
153   }
154
155   /**
156    * Construct a new {@link ReaderInputStream} with a default input buffer size of
157    * <c>1024</c> characters.
158    *
159    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
160    * @param charsetName the name of the charset encoding.  Must not be <jk>null</jk>.
161    */
162   public ReaderInputStream(Reader reader, String charsetName) {
163      this(reader, charsetName, DEFAULT_BUFFER_SIZE);
164   }
165
166   /**
167    * Construct a new {@link ReaderInputStream}.
168    *
169    * @param reader the target {@link Reader}.  Must not be <jk>null</jk>.
170    * @param charsetName the name of the charset encoding.  Must not be <jk>null</jk>.
171    * @param bufferSize the size of the input buffer in number of characters.  Must be positive.
172    */
173   public ReaderInputStream(Reader reader, String charsetName, int bufferSize) {
174      this(reader, Charset.forName(reqnn("charsetName", charsetName)), bufferSize);
175   }
176
177   /**
178    * Close the stream. This method will cause the underlying {@link Reader}
179    * to be closed.
180    * @throws IOException if an I/O error occurs
181    */
182   @Override
183   public void close() throws IOException {
184      reader.close();
185   }
186
187   /**
188    * Read a single byte.
189    *
190    * @return either the byte read or <code>-1</code> if the end of the stream
191    *     has been reached
192    * @throws IOException if an I/O error occurs
193    */
194   @Override
195   public int read() throws IOException {
196      for (;;) {
197         if (encoderOut.hasRemaining()) {
198            return encoderOut.get() & 0xFF;
199         }
200         fillBuffer();
201         if (endOfInput && ! encoderOut.hasRemaining()) {
202            return -1;
203         }
204      }
205   }
206
207   /**
208    * Read the specified number of bytes into an array.
209    *
210    * @param b the byte array to read into.  Must not be <jk>null</jk>.
211    * @return the number of bytes read or <code>-1</code>
212    *     if the end of the stream has been reached
213    * @throws IOException if an I/O error occurs
214    */
215   @Override
216   public int read(byte[] b) throws IOException {
217      reqnn("b", b);
218      return read(b, 0, b.length);
219   }
220
221   /**
222    * Read the specified number of bytes into an array.
223    *
224    * @param array the byte array to read into.  Must not be <jk>null</jk>.
225    * @param off the offset to start reading bytes into
226    * @param len the number of bytes to read
227    * @return the number of bytes read or <code>-1</code>
228    *     if the end of the stream has been reached
229    * @throws IOException if an I/O error occurs
230    */
231   @Override
232   public int read(byte[] array, int off, int len) throws IOException {
233      reqnn("array", array);
234      if (len < 0 || off < 0 || (off + len) > array.length) {
235         throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + off + ", length=" + len);
236      }
237      int read = 0;
238      if (len == 0) {
239         return 0; // Always return 0 if len == 0
240      }
241      while (len > 0) {
242         if (encoderOut.hasRemaining()) {
243            final int c = Math.min(encoderOut.remaining(), len);
244            encoderOut.get(array, off, c);
245            off += c;
246            len -= c;
247            read += c;
248         } else {
249            fillBuffer();
250            if (endOfInput && ! encoderOut.hasRemaining()) {
251               break;
252            }
253         }
254      }
255      return read == 0 && endOfInput ? -1 : read;
256   }
257
258   /**
259    * Fills the internal char buffer from the reader.
260    *
261    * @throws IOException
262    *        If an I/O error occurs
263    */
264   private void fillBuffer() throws IOException {
265      if (! endOfInput && (lastCoderResult == null || lastCoderResult.isUnderflow())) {
266         encoderIn.compact();
267         final int position = encoderIn.position();
268         // We don't use Reader#read(CharBuffer) here because it is more efficient
269         // to write directly to the underlying char array (the default implementation
270         // copies data to a temporary char array).
271         final int c = reader.read(encoderIn.array(), position, encoderIn.remaining());
272         if (c == -1) {
273            endOfInput = true;
274         } else {
275            encoderIn.position(position + c);
276         }
277         encoderIn.flip();
278      }
279      encoderOut.compact();
280      lastCoderResult = encoder.encode(encoderIn, encoderOut, endOfInput);
281      encoderOut.flip();
282   }
283}