1 /*
   2  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.io;
  27 
  28 
  29 /**
  30  * Piped character-input streams.
  31  *
  32  * @author      Mark Reinhold
  33  * @since       1.1
  34  */
  35 
  36 public class PipedReader extends Reader {
  37     boolean closedByWriter = false;
  38     boolean closedByReader = false;
  39     boolean connected = false;
  40 
  41     /* REMIND: identification of the read and write sides needs to be
  42        more sophisticated.  Either using thread groups (but what about
  43        pipes within a thread?) or using finalization (but it may be a
  44        long time until the next GC). */
  45     Thread readSide;
  46     Thread writeSide;
  47 
  48    /**
  49     * The size of the pipe's circular input buffer.
  50     */
  51     private static final int DEFAULT_PIPE_SIZE = 1024;
  52 
  53     /**
  54      * The circular buffer into which incoming data is placed.
  55      */
  56     char buffer[];
  57 
  58     /**
  59      * The index of the position in the circular buffer at which the
  60      * next character of data will be stored when received from the connected
  61      * piped writer. <code>in&lt;0</code> implies the buffer is empty,
  62      * <code>in==out</code> implies the buffer is full
  63      */
  64     int in = -1;
  65 
  66     /**
  67      * The index of the position in the circular buffer at which the next
  68      * character of data will be read by this piped reader.
  69      */
  70     int out = 0;
  71 
  72     /**
  73      * Creates a <code>PipedReader</code> so
  74      * that it is connected to the piped writer
  75      * <code>src</code>. Data written to <code>src</code>
  76      * will then be available as input from this stream.
  77      *
  78      * @param      src   the stream to connect to.
  79      * @exception  IOException  if an I/O error occurs.
  80      */
  81     public PipedReader(PipedWriter src) throws IOException {
  82         this(src, DEFAULT_PIPE_SIZE);
  83     }
  84 
  85     /**
  86      * Creates a <code>PipedReader</code> so that it is connected
  87      * to the piped writer <code>src</code> and uses the specified
  88      * pipe size for the pipe's buffer. Data written to <code>src</code>
  89      * will then be  available as input from this stream.
  90 
  91      * @param      src       the stream to connect to.
  92      * @param      pipeSize  the size of the pipe's buffer.
  93      * @exception  IOException  if an I/O error occurs.
  94      * @exception  IllegalArgumentException if {@code pipeSize <= 0}.
  95      * @since      1.6
  96      */
  97     public PipedReader(PipedWriter src, int pipeSize) throws IOException {
  98         initPipe(pipeSize);
  99         connect(src);
 100     }
 101 
 102 
 103     /**
 104      * Creates a <code>PipedReader</code> so
 105      * that it is not yet {@linkplain #connect(java.io.PipedWriter)
 106      * connected}. It must be {@linkplain java.io.PipedWriter#connect(
 107      * java.io.PipedReader) connected} to a <code>PipedWriter</code>
 108      * before being used.
 109      */
 110     public PipedReader() {
 111         initPipe(DEFAULT_PIPE_SIZE);
 112     }
 113 
 114     /**
 115      * Creates a <code>PipedReader</code> so that it is not yet
 116      * {@link #connect(java.io.PipedWriter) connected} and uses
 117      * the specified pipe size for the pipe's buffer.
 118      * It must be  {@linkplain java.io.PipedWriter#connect(
 119      * java.io.PipedReader) connected} to a <code>PipedWriter</code>
 120      * before being used.
 121      *
 122      * @param   pipeSize the size of the pipe's buffer.
 123      * @exception  IllegalArgumentException if {@code pipeSize <= 0}.
 124      * @since      1.6
 125      */
 126     public PipedReader(int pipeSize) {
 127         initPipe(pipeSize);
 128     }
 129 
 130     private void initPipe(int pipeSize) {
 131         if (pipeSize <= 0) {
 132             throw new IllegalArgumentException("Pipe size <= 0");
 133         }
 134         buffer = new char[pipeSize];
 135     }
 136 
 137     /**
 138      * Causes this piped reader to be connected
 139      * to the piped  writer <code>src</code>.
 140      * If this object is already connected to some
 141      * other piped writer, an <code>IOException</code>
 142      * is thrown.
 143      * <p>
 144      * If <code>src</code> is an
 145      * unconnected piped writer and <code>snk</code>
 146      * is an unconnected piped reader, they
 147      * may be connected by either the call:
 148      *
 149      * <pre><code>snk.connect(src)</code> </pre>
 150      * <p>
 151      * or the call:
 152      *
 153      * <pre><code>src.connect(snk)</code> </pre>
 154      * <p>
 155      * The two calls have the same effect.
 156      *
 157      * @param      src   The piped writer to connect to.
 158      * @exception  IOException  if an I/O error occurs.
 159      */
 160     public void connect(PipedWriter src) throws IOException {
 161         src.connect(this);
 162     }
 163 
 164     /**
 165      * Receives a char of data. This method will block if no input is
 166      * available.
 167      */
 168     synchronized void receive(int c) throws IOException {
 169         if (!connected) {
 170             throw new IOException("Pipe not connected");
 171         } else if (closedByWriter || closedByReader) {
 172             throw new IOException("Pipe closed");
 173         } else if (readSide != null && !readSide.isAlive()) {
 174             throw new IOException("Read end dead");
 175         }
 176 
 177         writeSide = Thread.currentThread();
 178         while (in == out) {
 179             if ((readSide != null) && !readSide.isAlive()) {
 180                 throw new IOException("Pipe broken");
 181             }
 182             /* full: kick any waiting readers */
 183             notifyAll();
 184             try {
 185                 wait(1000);
 186             } catch (InterruptedException ex) {
 187                 throw new java.io.InterruptedIOException();
 188             }
 189         }
 190         if (in < 0) {
 191             in = 0;
 192             out = 0;
 193         }
 194         buffer[in++] = (char) c;
 195         if (in >= buffer.length) {
 196             in = 0;
 197         }
 198     }
 199 
 200     /**
 201      * Receives data into an array of characters.  This method will
 202      * block until some input is available.
 203      */
 204     synchronized void receive(char c[], int off, int len)  throws IOException {
 205         while (--len >= 0) {
 206             receive(c[off++]);
 207         }
 208     }
 209 
 210     /**
 211      * Notifies all waiting threads that the last character of data has been
 212      * received.
 213      */
 214     synchronized void receivedLast() {
 215         closedByWriter = true;
 216         notifyAll();
 217     }
 218 
 219     /**
 220      * Reads the next character of data from this piped stream.
 221      * If no character is available because the end of the stream
 222      * has been reached, the value <code>-1</code> is returned.
 223      * This method blocks until input data is available, the end of
 224      * the stream is detected, or an exception is thrown.
 225      *
 226      * @return     the next character of data, or <code>-1</code> if the end of the
 227      *             stream is reached.
 228      * @exception  IOException  if the pipe is
 229      *          <a href=PipedInputStream.html#BROKEN> <code>broken</code></a>,
 230      *          {@link #connect(java.io.PipedWriter) unconnected}, closed,
 231      *          or an I/O error occurs.
 232      */
 233     public synchronized int read()  throws IOException {
 234         if (!connected) {
 235             throw new IOException("Pipe not connected");
 236         } else if (closedByReader) {
 237             throw new IOException("Pipe closed");
 238         } else if (writeSide != null && !writeSide.isAlive()
 239                    && !closedByWriter && (in < 0)) {
 240             throw new IOException("Write end dead");
 241         }
 242 
 243         readSide = Thread.currentThread();
 244         int trials = 2;
 245         while (in < 0) {
 246             if (closedByWriter) {
 247                 /* closed by writer, return EOF */
 248                 return -1;
 249             }
 250             if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
 251                 throw new IOException("Pipe broken");
 252             }
 253             /* might be a writer waiting */
 254             notifyAll();
 255             try {
 256                 wait(1000);
 257             } catch (InterruptedException ex) {
 258                 throw new java.io.InterruptedIOException();
 259             }
 260         }
 261         int ret = buffer[out++];
 262         if (out >= buffer.length) {
 263             out = 0;
 264         }
 265         if (in == out) {
 266             /* now empty */
 267             in = -1;
 268         }
 269         return ret;
 270     }
 271 
 272     /**
 273      * Reads up to <code>len</code> characters of data from this piped
 274      * stream into an array of characters. Less than <code>len</code> characters
 275      * will be read if the end of the data stream is reached or if
 276      * <code>len</code> exceeds the pipe's buffer size. This method
 277      * blocks until at least one character of input is available.
 278      *
 279      * @param      cbuf     the buffer into which the data is read.
 280      * @param      off   the start offset of the data.
 281      * @param      len   the maximum number of characters read.
 282      * @return     the total number of characters read into the buffer, or
 283      *             <code>-1</code> if there is no more data because the end of
 284      *             the stream has been reached.
 285      * @exception  IOException  if the pipe is
 286      *                  <a href=PipedInputStream.html#BROKEN> <code>broken</code></a>,
 287      *                  {@link #connect(java.io.PipedWriter) unconnected}, closed,
 288      *                  or an I/O error occurs.
 289      */
 290     public synchronized int read(char cbuf[], int off, int len)  throws IOException {
 291         if (!connected) {
 292             throw new IOException("Pipe not connected");
 293         } else if (closedByReader) {
 294             throw new IOException("Pipe closed");
 295         } else if (writeSide != null && !writeSide.isAlive()
 296                    && !closedByWriter && (in < 0)) {
 297             throw new IOException("Write end dead");
 298         }
 299 
 300         if ((off < 0) || (off > cbuf.length) || (len < 0) ||
 301             ((off + len) > cbuf.length) || ((off + len) < 0)) {
 302             throw new IndexOutOfBoundsException();
 303         } else if (len == 0) {
 304             return 0;
 305         }
 306 
 307         /* possibly wait on the first character */
 308         int c = read();
 309         if (c < 0) {
 310             return -1;
 311         }
 312         cbuf[off] =  (char)c;
 313         int rlen = 1;
 314         while ((in >= 0) && (--len > 0)) {
 315             cbuf[off + rlen] = buffer[out++];
 316             rlen++;
 317             if (out >= buffer.length) {
 318                 out = 0;
 319             }
 320             if (in == out) {
 321                 /* now empty */
 322                 in = -1;
 323             }
 324         }
 325         return rlen;
 326     }
 327 
 328     /**
 329      * Tell whether this stream is ready to be read.  A piped character
 330      * stream is ready if the circular buffer is not empty.
 331      *
 332      * @exception  IOException  if the pipe is
 333      *                  <a href=PipedInputStream.html#BROKEN> <code>broken</code></a>,
 334      *                  {@link #connect(java.io.PipedWriter) unconnected}, or closed.
 335      */
 336     public synchronized boolean ready() throws IOException {
 337         if (!connected) {
 338             throw new IOException("Pipe not connected");
 339         } else if (closedByReader) {
 340             throw new IOException("Pipe closed");
 341         } else if (writeSide != null && !writeSide.isAlive()
 342                    && !closedByWriter && (in < 0)) {
 343             throw new IOException("Write end dead");
 344         }
 345         if (in < 0) {
 346             return false;
 347         } else {
 348             return true;
 349         }
 350     }
 351 
 352     /**
 353      * Closes this piped stream and releases any system resources
 354      * associated with the stream.
 355      *
 356      * @exception  IOException  if an I/O error occurs.
 357      */
 358     public void close()  throws IOException {
 359         in = -1;
 360         closedByReader = true;
 361     }
 362 }