/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // BEGIN android-note // We've made several changes including: // - delayed buffer creation until pipe connection // - throw an IOException when a pipe is closed during a write // - improved consistency with PipedReader // END android-note package java.io; import org.apache.harmony.luni.util.Msg; /** * Receives information from a communications pipe. When two threads want to * pass data back and forth, one creates a piped output stream and the other one * creates a piped input stream. * * @see PipedOutputStream */ public class PipedInputStream extends InputStream { private Thread lastReader; private Thread lastWriter; private boolean isClosed; /** * The circular buffer through which data is passed. Data is read from the * range {@code [out, in)} and written to the range {@code [in, out)}. * Data in the buffer is either sequential: <pre> * { - - - X X X X X X X - - - - - } * ^ ^ * | | * out in</pre> * ...or wrapped around the buffer's end: <pre> * { X X X X - - - - - - - - X X X } * ^ ^ * | | * in out</pre> * When the buffer is empty, {@code in == -1}. Reading when the buffer is * empty will block until data is available. When the buffer is full, * {@code in == out}. Writing when the buffer is full will block until free * space is available. */ protected byte buffer[]; /** * The index in {@code buffer} where the next byte will be written. */ protected int in = -1; /** * The index in {@code buffer} where the next byte will be read. */ protected int out; /** * The size of the default pipe in bytes. */ protected static final int PIPE_SIZE = 1024; /** * Indicates if this pipe is connected. */ boolean isConnected; /** * Constructs a new unconnected {@code PipedInputStream}. The resulting * stream must be connected to a {@link PipedOutputStream} before data may * be read from it. */ public PipedInputStream() {} /** * Constructs a new {@code PipedInputStream} connected to the * {@link PipedOutputStream} {@code out}. Any data written to the output * stream can be read from the this input stream. * * @param out * the piped output stream to connect to. * @throws IOException * if this stream or {@code out} are already connected. */ public PipedInputStream(PipedOutputStream out) throws IOException { connect(out); } /** * {@inheritDoc} * * <p>Unlike most streams, {@code PipedInputStream} returns 0 rather than throwing * {@code IOException} if the stream has been closed. Unconnected and broken pipes also * return 0. * * @throws IOException if an I/O error occurs */ @Override public synchronized int available() throws IOException { if (buffer == null || in == -1) { return 0; } return in <= out ? buffer.length - out + in : in - out; } /** * Closes this stream. This implementation releases the buffer used for the * pipe and notifies all threads waiting to read or write. * * @throws IOException * if an error occurs while closing this stream. */ @Override public synchronized void close() throws IOException { buffer = null; notifyAll(); } /** * Connects this {@code PipedInputStream} to a {@link PipedOutputStream}. * Any data written to the output stream becomes readable in this input * stream. * * @param src * the source output stream. * @throws IOException * if either stream is already connected. */ public void connect(PipedOutputStream src) throws IOException { src.connect(this); } /** * Establishes the connection to the PipedOutputStream. * * @throws IOException * If this Reader is already connected. */ synchronized void establishConnection() throws IOException { if (isConnected) { throw new IOException(Msg.getString("K007a")); //$NON-NLS-1$ } buffer = new byte[PipedInputStream.PIPE_SIZE]; isConnected = true; } /** * Reads a single byte from this stream and returns it as an integer in the * range from 0 to 255. Returns -1 if the end of this stream has been * reached. If there is no data in the pipe, this method blocks until data * is available, the end of the stream is detected or an exception is * thrown. * <p> * Separate threads should be used to read from a {@code PipedInputStream} * and to write to the connected {@link PipedOutputStream}. If the same * thread is used, a deadlock may occur. * * @return the byte read or -1 if the end of the source stream has been * reached. * @throws IOException * if this stream is closed or not connected to an output * stream, or if the thread writing to the connected output * stream is no longer alive. */ @Override public synchronized int read() throws IOException { if (!isConnected) { // K0074=Not connected throw new IOException(Msg.getString("K0074")); //$NON-NLS-1$ } if (buffer == null) { // K0075=InputStream is closed throw new IOException(Msg.getString("K0075")); //$NON-NLS-1$ } // BEGIN android-removed // eagerly throwing prevents checking isClosed and returning normally // if (lastWriter != null && !lastWriter.isAlive() && (in < 0)) { // // KA030=Write end dead // throw new IOException(Msg.getString("KA030")); //$NON-NLS-1$ // } // END android-removed /** * Set the last thread to be reading on this PipedInputStream. If * lastReader dies while someone is waiting to write an IOException of * "Pipe broken" will be thrown in receive() */ lastReader = Thread.currentThread(); try { int attempts = 3; while (in == -1) { // Are we at end of stream? if (isClosed) { return -1; } if ((attempts-- <= 0) && lastWriter != null && !lastWriter.isAlive()) { // K0076=Pipe broken throw new IOException(Msg.getString("K0076")); //$NON-NLS-1$ } // Notify callers of receive() notifyAll(); wait(1000); } } catch (InterruptedException e) { throw new InterruptedIOException(); } // BEGIN android-changed int result = buffer[out++] & 0xff; if (out == buffer.length) { out = 0; } if (out == in) { // empty buffer in = -1; out = 0; } // let blocked writers write to the newly available buffer space notifyAll(); return result; // END android-changed } /** * Reads at most {@code count} bytes from this stream and stores them in the * byte array {@code bytes} starting at {@code offset}. Blocks until at * least one byte has been read, the end of the stream is detected or an * exception is thrown. * <p> * Separate threads should be used to read from a {@code PipedInputStream} * and to write to the connected {@link PipedOutputStream}. If the same * thread is used, a deadlock may occur. * * @param bytes * the array in which to store the bytes read. * @param offset * the initial position in {@code bytes} to store the bytes * read from this stream. * @param count * the maximum number of bytes to store in {@code bytes}. * @return the number of bytes actually read or -1 if the end of the stream * has been reached. * @throws IndexOutOfBoundsException * if {@code offset < 0} or {@code count < 0}, or if {@code * offset + count} is greater than the size of {@code bytes}. * @throws InterruptedIOException * if the thread reading from this stream is interrupted. * @throws IOException * if this stream is closed or not connected to an output * stream, or if the thread writing to the connected output * stream is no longer alive. * @throws NullPointerException * if {@code bytes} is {@code null}. */ @Override public synchronized int read(byte[] bytes, int offset, int count) throws IOException { // BEGIN android-changed if (bytes == null) { throw new NullPointerException(Msg.getString("K0047")); //$NON-NLS-1$ } // Exception priorities (in case of multiple errors) differ from // RI, but are spec-compliant. // removed redundant check, used (offset | count) < 0 // instead of (offset < 0) || (count < 0) to safe one operation if ((offset | count) < 0 || count > bytes.length - offset) { throw new IndexOutOfBoundsException(Msg.getString("K002f")); //$NON-NLS-1$ } // END android-changed if (count == 0) { return 0; } if (!isConnected) { // K0074=Not connected throw new IOException(Msg.getString("K0074")); //$NON-NLS-1$ } if (buffer == null) { // K0075=InputStream is closed throw new IOException(Msg.getString("K0075")); //$NON-NLS-1$ } // BEGIN android-removed // eagerly throwing prevents checking isClosed and returning normally // if (lastWriter != null && !lastWriter.isAlive() && (in < 0)) { // // KA030=Write end dead // throw new IOException(Msg.getString("KA030")); //$NON-NLS-1$ // } // END android-removed /** * Set the last thread to be reading on this PipedInputStream. If * lastReader dies while someone is waiting to write an IOException of * "Pipe broken" will be thrown in receive() */ lastReader = Thread.currentThread(); try { int attempts = 3; while (in == -1) { // Are we at end of stream? if (isClosed) { return -1; } if ((attempts-- <= 0) && lastWriter != null && !lastWriter.isAlive()) { // K0076=Pipe broken throw new IOException(Msg.getString("K0076")); //$NON-NLS-1$ } // Notify callers of receive() notifyAll(); wait(1000); } } catch (InterruptedException e) { throw new InterruptedIOException(); } // BEGIN android-changed int totalCopied = 0; // copy bytes from out thru the end of buffer if (out >= in) { int leftInBuffer = buffer.length - out; int length = leftInBuffer < count ? leftInBuffer : count; System.arraycopy(buffer, out, bytes, offset, length); out += length; if (out == buffer.length) { out = 0; } if (out == in) { // empty buffer in = -1; out = 0; } totalCopied += length; } // copy bytes from out thru in if (totalCopied < count && in != -1) { int leftInBuffer = in - out; int leftToCopy = count - totalCopied; int length = leftToCopy < leftInBuffer ? leftToCopy : leftInBuffer; System.arraycopy(buffer, out, bytes, offset + totalCopied, length); out += length; if (out == in) { // empty buffer in = -1; out = 0; } totalCopied += length; } // let blocked writers write to the newly available buffer space notifyAll(); return totalCopied; // END android-changed } /** * Receives a byte and stores it in this stream's {@code buffer}. This * method is called by {@link PipedOutputStream#write(int)}. The least * significant byte of the integer {@code oneByte} is stored at index * {@code in} in the {@code buffer}. * <p> * This method blocks as long as {@code buffer} is full. * * @param oneByte * the byte to store in this pipe. * @throws InterruptedIOException * if the {@code buffer} is full and the thread that has called * this method is interrupted. * @throws IOException * if this stream is closed or the thread that has last read * from this stream is no longer alive. */ protected synchronized void receive(int oneByte) throws IOException { if (buffer == null || isClosed) { throw new IOException(Msg.getString("K0078")); //$NON-NLS-1$ } // BEGIN android-removed // eagerly throwing causes us to fail even if the buffer's not full // if (lastReader != null && !lastReader.isAlive()) { // throw new IOException(Msg.getString("K0076")); //$NON-NLS-1$ // } // END android-removed /** * Set the last thread to be writing on this PipedInputStream. If * lastWriter dies while someone is waiting to read an IOException of * "Pipe broken" will be thrown in read() */ lastWriter = Thread.currentThread(); try { while (buffer != null && out == in) { // BEGIN android-changed // moved has-last-reader-died check to be before wait() if (lastReader != null && !lastReader.isAlive()) { throw new IOException(Msg.getString("K0076")); //$NON-NLS-1$ } notifyAll(); wait(1000); // END android-changed } } catch (InterruptedException e) { throw new InterruptedIOException(); } if (buffer == null) { throw new IOException(Msg.getString("K0078")); //$NON-NLS-1$ } if (in == -1) { in = 0; } buffer[in++] = (byte) oneByte; if (in == buffer.length) { in = 0; } // BEGIN android-added // let blocked readers read the newly available data notifyAll(); // END android-added } synchronized void done() { isClosed = true; notifyAll(); } }