All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jruby.embed.io.ReaderInputStream Maven / Gradle / Ivy

There is a newer version: 9.4.7.0
Show newest version
/**
 * **** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2009-2010 Yoko Harada 
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 * **** END LICENSE BLOCK *****
 */
package org.jruby.embed.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.ArrayList;
import java.util.List;

/**
 * A ReaderInputStream converts java.io.Reader to java.io.InputStream. The
 * ReaderInputStream reads data in a given Reader object into its internal buffer
 * so that users of this class can access the file that Reader read by using methods
 * defined in java.io.InputStream.
 *
 * @author Yoko Harada 
 */
public class ReaderInputStream extends InputStream {

    private static final int DEFAULT_CHAR_BUFFER_SIZE = 8192;
    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
    private final Reader reader;
    private byte[] bytes = null;
    private int totalBytes = 0;
    private int position = 0;
    private int markedIndex = -1;
    private int readlimit = 0;
    private boolean isOpen = true;
    private CharsetEncoder encoder;
    private final Object lock = new Object();

    /**
     * Creates ReaderInputStream from a given Reader type object with a default encoding.
     *
     * @param reader java.io.Reader object to be read data from.
     */
    public ReaderInputStream(Reader reader) {
        this(reader, null);
    }

    /**
     * Creates ReaderInputStream from a given Reader type object with a specifed encoding.
     *
     * @param reader java.io.Reader object to be read data from.
     * @param encoding an encoding of the created stream.
     */
    public ReaderInputStream(Reader reader, String encoding) {
        this.reader = reader;
        if (encoding == null) {
            if (reader instanceof InputStreamReader) {
                encoding = ((InputStreamReader) reader).getEncoding();
            } else {
                encoding = Charset.defaultCharset().name();
            }
        } else if (!Charset.isSupported(encoding)) {
            throw new IllegalArgumentException(encoding + " is not supported");
        }
        encoder = Charset.forName(encoding).newEncoder();
        encoder.onMalformedInput(CodingErrorAction.REPLACE);
        encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
        try {
            fillByteBuffer(reader);
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private void fillByteBuffer(Reader reader) throws IOException {
        CharBuffer cbuf = CharBuffer.allocate(DEFAULT_CHAR_BUFFER_SIZE);
        ByteBuffer bbuf = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
        List list = new ArrayList();
        while (true) {
            cbuf.clear();
            int size = reader.read(cbuf);
            if (size <= 0) {
                break;
            }
            cbuf.limit(cbuf.position());
            cbuf.rewind();
            boolean eof = false;
            while (!eof) {
                CoderResult cr = encoder.encode(cbuf, bbuf, eof);
                if (cr.isError()) {
                    cr.throwException();
                } else if (cr.isUnderflow()) {
                    appendBytes(list, bbuf);
                    eof = true;
                } else if (cr.isOverflow()) {
                    appendBytes(list, bbuf);
                    bbuf.clear();
                }
            }
        }
        getByteArray(list);
    }

    private void appendBytes(List list, ByteBuffer bb) {
        bb.flip();
        int length = bb.limit();
        totalBytes += length;
        byte[] dst = new byte[length];
        System.arraycopy(bb.array(), bb.position(), dst, 0, length);
        list.add(dst);
    }

    private void getByteArray(List list) {
        bytes = new byte[totalBytes];
        int index = 0;
        for (byte[] bb : list) {
            for (int i=0; i Note that while some implementations of {@code InputStream} will return
     * the total number of bytes in the stream, many will not.  It is
     * never correct to use the return value of this method to allocate
     * a buffer intended to hold all data in this stream.
     *
     * 

A subclass' implementation of this method may choose to throw an * {@link IOException} if this input stream has been closed by * invoking the {@link #close()} method. * *

The {@code available} method for class {@code InputStream} always * returns {@code 0}. * * @return an estimate of the number of bytes that can be read (or skipped * over) from this input stream without blocking or {@code 0} when * it reaches the end of the input stream. * @exception IOException if an I/O error occurs. */ @Override public int available() throws IOException { synchronized (lock) { confirmOpen(); if (bytes == null) { throw new IOException("This stream is not available."); } return totalBytes - position; } } /** * Closes this input stream and releases any system resources associated * with the stream. * * @exception IOException if an I/O error occurs. */ @Override public void close() throws IOException { synchronized (lock) { confirmOpen(); isOpen = false; encoder = null; bytes = null; //reader.close(); } } /** * Marks the current position in this input stream. A subsequent call to * the reset method repositions this stream at the last marked * position so that subsequent reads re-read the same bytes. * *

The readlimit arguments tells this input stream to * allow that many bytes to be read before the mark position gets * invalidated. * *

The general contract of mark is that, if the method * markSupported returns true, the stream somehow * remembers all the bytes read after the call to mark and * stands ready to supply those same bytes again if and whenever the method * reset is called. However, the stream is not required to * remember any data at all if more than readlimit bytes are * read from the stream before reset is called. * *

Marking a closed stream should not have any effect on the stream. * *

The mark method of InputStream does * nothing. * * @param readlimit the maximum limit of bytes that can be read before * the mark position becomes invalid. * @see java.io.InputStream#reset() */ @Override public synchronized void mark(int readlimit) { if (readlimit < 0) { throw new IllegalArgumentException("Read limit < 0"); } synchronized (lock) { if (isOpen) { this.readlimit = readlimit; markedIndex = position; } } } /** * Tests if this input stream supports the mark and * reset methods. Whether or not mark and * reset are supported is an invariant property of a * particular input stream instance. * * @return true if this stream instance supports the mark * and reset methods; false otherwise. * @see java.io.InputStream#mark(int) * @see java.io.InputStream#reset() */ @Override public boolean markSupported() { return true; } /** * Reads the next byte of data from the input stream. The value byte is * returned as an int in the range 0 to * 255. If no byte is available because the end of the stream * has been reached, the value -1 is returned. This method * blocks until input data is available, the end of the stream is detected, * or an exception is thrown. * *

A subclass must provide an implementation of this method. * * @return the next byte of data, or -1 if the end of the * stream is reached. * @exception IOException if an I/O error occurs. */ @Override public int read() throws IOException { synchronized (lock) { confirmOpen(); if (position >= totalBytes) { return -1; } else { return bytes[position++]; } } } /** * Reads some number of bytes from the input stream and stores them into * the buffer array b. The number of bytes actually read is * returned as an integer. This method blocks until input data is * available, end of file is detected, or an exception is thrown. * *

If the length of b is zero, then no bytes are read and * 0 is returned; otherwise, there is an attempt to read at * least one byte. If no byte is available because the stream is at the * end of the file, the value -1 is returned; otherwise, at * least one byte is read and stored into b. * *

The first byte read is stored into element b[0], the * next one into b[1], and so on. The number of bytes read is, * at most, equal to the length of b. Let k be the * number of bytes actually read; these bytes will be stored in elements * b[0] through b[k-1], * leaving elements b[k] through * b[b.length-1] unaffected. * *

The read(b) method for class InputStream * has the same effect as:

 read(b, 0, b.length) 
* * @param b the buffer into which the data is read. * @return the total number of bytes read into the buffer, or * -1 is there is no more data because the end of * the stream has been reached. * @exception IOException If the first byte cannot be read for any reason * other than the end of the file, if the input stream has been closed, or * if some other I/O error occurs. * @exception NullPointerException if b is null. * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } /** * Reads up to len bytes of data from the input stream into * an array of bytes. An attempt is made to read as many as * len bytes, but a smaller number may be read. * The number of bytes actually read is returned as an integer. * *

This method blocks until input data is available, end of file is * detected, or an exception is thrown. * *

If len is zero, then no bytes are read and * 0 is returned; otherwise, there is an attempt to read at * least one byte. If no byte is available because the stream is at end of * file, the value -1 is returned; otherwise, at least one * byte is read and stored into b. * *

The first byte read is stored into element b[off], the * next one into b[off+1], and so on. The number of bytes read * is, at most, equal to len. Let k be the number of * bytes actually read; these bytes will be stored in elements * b[off] through b[off+k-1], * leaving elements b[off+k] through * b[off+len-1] unaffected. * *

In every case, elements b[0] through * b[off] and elements b[off+len] through * b[b.length-1] are unaffected. * *

The read(b, off, len) method * for class InputStream simply calls the method * read() repeatedly. If the first such call results in an * IOException, that exception is returned from the call to * the read(b, off, len) method. If * any subsequent call to read() results in a * IOException, the exception is caught and treated as if it * were end of file; the bytes read up to that point are stored into * b and the number of bytes read before the exception * occurred is returned. The default implementation of this method blocks * until the requested amount of input data len has been read, * end of file is detected, or an exception is thrown. Subclasses are encouraged * to provide a more efficient implementation of this method. * * @param b the buffer into which the data is read. * @param off the start offset in array b * at which the data is written. * @param len the maximum number of bytes to read. * @return the total number of bytes read into the buffer, or * -1 if there is no more data because the end of * the stream has been reached. * @exception IOException If the first byte cannot be read for any reason * other than end of file, or if the input stream has been closed, or if * some other I/O error occurs. * @exception NullPointerException If b is null. * @exception IndexOutOfBoundsException If off is negative, * len is negative, or len is greater than * b.length - off * @see java.io.InputStream#read() */ @Override public int read(byte[] b, int off, int len) throws IOException { synchronized (lock) { confirmOpen(); if (len == 0) { return 0; } if (position >= totalBytes) { return -1; } if (off < 0 || off > totalBytes || len < 0) { throw new IllegalArgumentException("Either one of, or both of off and len are invalid."); } int start = position + off; start = start < totalBytes ? start : totalBytes - 1; int end = start + len; end = end < totalBytes ? end : totalBytes - 1; int actuallyRead = Math.min((end - start) + 1, len); System.arraycopy(bytes, start, b, 0, actuallyRead); position += actuallyRead; return actuallyRead; } } /** * Repositions this stream to the position at the time the * mark method was last called on this input stream. * *

The general contract of reset is: * *

    * *
  • If the method markSupported returns * true, then: * *
    • If the method mark has not been called since * the stream was created, or the number of bytes read from the stream * since mark was last called is larger than the argument * to mark at that last call, then an * IOException might be thrown. * *
    • If such an IOException is not thrown, then the * stream is reset to a state such that all the bytes read since the * most recent call to mark (or since the start of the * file, if mark has not been called) will be resupplied * to subsequent callers of the read method, followed by * any bytes that otherwise would have been the next input data as of * the time of the call to reset.
    * *
  • If the method markSupported returns * false, then: * *
    • The call to reset may throw an * IOException. * *
    • If an IOException is not thrown, then the stream * is reset to a fixed state that depends on the particular type of the * input stream and how it was created. The bytes that will be supplied * to subsequent callers of the read method depend on the * particular type of the input stream.
* * @exception IOException if this stream has not been marked or if the * mark has been invalidated. * @see java.io.InputStream#mark(int) * @see java.io.IOException */ @Override public synchronized void reset() throws IOException { synchronized (lock) { if (!isOpen) { throw new IOException("This stream has been closed."); } if (markedIndex < 0) { throw new IOException("This stream is not marked."); } if ((position - markedIndex) > readlimit) { throw new IOException("Mark is invalidated."); } position = markedIndex; } } /** * Skips over and discards n bytes of data from this input * stream. The skip method may, for a variety of reasons, end * up skipping over some smaller number of bytes, possibly 0. * This may result from any of a number of conditions; reaching end of file * before n bytes have been skipped is only one possibility. * The actual number of bytes skipped is returned. If n is * negative, no bytes are skipped. * * @param n the number of bytes to be skipped. * @return the actual number of bytes skipped. * @exception IOException if the stream does not support seek, * or if some other I/O error occurs. */ @Override public long skip(long n) throws IOException { if (n < 0L) { throw new IllegalArgumentException("Negarive skip"); } synchronized (lock) { if (!isOpen) { throw new IOException("This stream has been closed."); } long skipped; if ((totalBytes - position) < n) { skipped = totalBytes - position; position = totalBytes; } else { skipped = n; position += n; } return skipped; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy