content: /*
   * Copyright (C) 2014 The Android Open Source Project
   * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package java.nio.channels;
  
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.Reader;
  import java.io.Writer;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.nio.charset.Charset;
  import java.nio.charset.CharsetDecoder;
  import java.nio.charset.CharsetEncoder;
  import java.nio.charset.UnsupportedCharsetException;
  import java.nio.channels.spi.AbstractInterruptibleChannel;
  import java.util.concurrent.ExecutionException;
  import sun.nio.ch.ChannelInputStream;
  import sun.nio.cs.StreamDecoder;
  import sun.nio.cs.StreamEncoder;
  
  
  /**
   * Utility methods for channels and streams.
   *
   * <p> This class defines static methods that support the interoperation of the
   * stream classes of the <tt>{@link java.io}</tt> package with the channel
   * classes of this package.  </p>
   *
   *
   * @author Mark Reinhold
   * @author Mike McCloskey
   * @author JSR-51 Expert Group
   * @since 1.4
   */
  
  public final class Channels {
  
      private Channels() { }              // No instantiation
  
      private static void checkNotNull(Object o, String name) {
          if (o == null)
              throw new NullPointerException("\"" + name + "\" is null!");
      }
  
      /**
       * Write all remaining bytes in buffer to the given channel.
       * If the channel is selectable then it must be configured blocking.
       */
      private static void writeFullyImpl(WritableByteChannel ch, ByteBuffer bb)
          throws IOException
      {
          while (bb.remaining() > 0) {
              int n = ch.write(bb);
              if (n <= 0)
                  throw new RuntimeException("no bytes written");
          }
      }
  
      /**
       * Write all remaining bytes in buffer to the given channel.
       *
       * @throws  IllegalBlockingModeException
       *          If the channel is selectable and configured non-blocking.
       */
      private static void writeFully(WritableByteChannel ch, ByteBuffer bb)
          throws IOException
      {
          if (ch instanceof SelectableChannel) {
              SelectableChannel sc = (SelectableChannel)ch;
              synchronized (sc.blockingLock()) {
                  if (!sc.isBlocking())
                      throw new IllegalBlockingModeException();
                  writeFullyImpl(ch, bb);
              }
          } else {
              writeFullyImpl(ch, bb);
          }
      }
  
      // -- Byte streams from channels --
  
      /**
       * Constructs a stream that reads bytes from the given channel.
       *
       * <p> The <tt>read</tt> methods of the resulting stream will throw an
       * {@link IllegalBlockingModeException} if invoked while the underlying
       * channel is in non-blocking mode.  The stream will not be buffered, and
       * it will not support the {@link InputStream#mark mark} or {@link
       * InputStream#reset reset} methods.  The stream will be safe for access by
       * multiple concurrent threads.  Closing the stream will in turn cause the
       * channel to be closed.  </p>
       *
       * @param  ch
       *         The channel from which bytes will be read
       *
       * @return  A new input stream
       */
      public static InputStream newInputStream(ReadableByteChannel ch) {
          checkNotNull(ch, "ch");
          return new sun.nio.ch.ChannelInputStream(ch);
      }
  
      /**
       * Constructs a stream that writes bytes to the given channel.
       *
       * <p> The <tt>write</tt> methods of the resulting stream will throw an
       * {@link IllegalBlockingModeException} if invoked while the underlying
       * channel is in non-blocking mode.  The stream will not be buffered.  The
       * stream will be safe for access by multiple concurrent threads.  Closing
       * the stream will in turn cause the channel to be closed.  </p>
       *
       * @param  ch
       *         The channel to which bytes will be written
       *
       * @return  A new output stream
       */
      public static OutputStream newOutputStream(final WritableByteChannel ch) {
          checkNotNull(ch, "ch");
  
          return new OutputStream() {
  
                  private ByteBuffer bb = null;
                  private byte[] bs = null;       // Invoker's previous array
                  private byte[] b1 = null;
  
                  public synchronized void write(int b) throws IOException {
                     if (b1 == null)
                          b1 = new byte[1];
                      b1[0] = (byte)b;
                      this.write(b1);
                  }
  
                  public synchronized void write(byte[] bs, int off, int len)
                      throws IOException
                  {
                      if ((off < 0) || (off > bs.length) || (len < 0) ||
                          ((off + len) > bs.length) || ((off + len) < 0)) {
                          throw new IndexOutOfBoundsException();
                      } else if (len == 0) {
                          return;
                      }
                      ByteBuffer bb = ((this.bs == bs)
                                       ? this.bb
                                       : ByteBuffer.wrap(bs));
                      bb.limit(Math.min(off + len, bb.capacity()));
                      bb.position(off);
                      this.bb = bb;
                      this.bs = bs;
                      Channels.writeFully(ch, bb);
                  }
  
                  public void close() throws IOException {
                      ch.close();
                  }
  
              };
      }
  
      /**
       * Constructs a stream that reads bytes from the given channel.
       *
       * <p> The stream will not be buffered, and it will not support the {@link
       * InputStream#mark mark} or {@link InputStream#reset reset} methods.  The
       * stream will be safe for access by multiple concurrent threads.  Closing
       * the stream will in turn cause the channel to be closed.  </p>
       *
       * @param  ch
       *         The channel from which bytes will be read
       *
       * @return  A new input stream
       *
       * @since 1.7
       */
      public static InputStream newInputStream(final AsynchronousByteChannel ch) {
          checkNotNull(ch, "ch");
          return new InputStream() {
  
              private ByteBuffer bb = null;
              private byte[] bs = null;           // Invoker's previous array
              private byte[] b1 = null;
  
              @Override
              public synchronized int read() throws IOException {
                  if (b1 == null)
                      b1 = new byte[1];
                  int n = this.read(b1);
                  if (n == 1)
                      return b1[0] & 0xff;
                  return -1;
              }
  
              @Override
              public synchronized int read(byte[] bs, int off, int len)
                  throws IOException
              {
                  if ((off < 0) || (off > bs.length) || (len < 0) ||
                      ((off + len) > bs.length) || ((off + len) < 0)) {
                      throw new IndexOutOfBoundsException();
                  } else if (len == 0)
                      return 0;
  
                  ByteBuffer bb = ((this.bs == bs)
                                   ? this.bb
                                   : ByteBuffer.wrap(bs));
                  bb.position(off);
                  bb.limit(Math.min(off + len, bb.capacity()));
                  this.bb = bb;
                  this.bs = bs;
  
                  boolean interrupted = false;
                  try {
                      for (;;) {
                          try {
                              return ch.read(bb).get();
                          } catch (ExecutionException ee) {
                              throw new IOException(ee.getCause());
                          } catch (InterruptedException ie) {
                              interrupted = true;
                          }
                      }
                  } finally {
                      if (interrupted)
                          Thread.currentThread().interrupt();
                  }
              }
  
              @Override
              public void close() throws IOException {
                  ch.close();
              }
          };
      }
  
      /**
       * Constructs a stream that writes bytes to the given channel.
       *
       * <p> The stream will not be buffered. The stream will be safe for access
       * by multiple concurrent threads.  Closing the stream will in turn cause
       * the channel to be closed.  </p>
       *
       * @param  ch
       *         The channel to which bytes will be written
       *
       * @return  A new output stream
       *
       * @since 1.7
       */
      public static OutputStream newOutputStream(final AsynchronousByteChannel ch) {
          checkNotNull(ch, "ch");
          return new OutputStream() {
  
              private ByteBuffer bb = null;
              private byte[] bs = null;   // Invoker's previous array
              private byte[] b1 = null;
  
              @Override
              public synchronized void write(int b) throws IOException {
                 if (b1 == null)
                      b1 = new byte[1];
                  b1[0] = (byte)b;
                  this.write(b1);
              }
  
              @Override
              public synchronized void write(byte[] bs, int off, int len)
                  throws IOException
              {
                  if ((off < 0) || (off > bs.length) || (len < 0) ||
                      ((off + len) > bs.length) || ((off + len) < 0)) {
                      throw new IndexOutOfBoundsException();
                  } else if (len == 0) {
                      return;
                  }
                  ByteBuffer bb = ((this.bs == bs)
                                   ? this.bb
                                   : ByteBuffer.wrap(bs));
                  bb.limit(Math.min(off + len, bb.capacity()));
                  bb.position(off);
                  this.bb = bb;
                  this.bs = bs;
  
                  boolean interrupted = false;
                  try {
                      while (bb.remaining() > 0) {
                          try {
                              ch.write(bb).get();
                          } catch (ExecutionException ee) {
                              throw new IOException(ee.getCause());
                          } catch (InterruptedException ie) {
                              interrupted = true;
                          }
                      }
                  } finally {
                      if (interrupted)
                          Thread.currentThread().interrupt();
                  }
              }
  
              @Override
              public void close() throws IOException {
                  ch.close();
              }
          };
      }
  
  
      // -- Channels from streams --
  
      /**
       * Constructs a channel that reads bytes from the given stream.
       *
       * <p> The resulting channel will not be buffered; it will simply redirect
       * its I/O operations to the given stream.  Closing the channel will in
       * turn cause the stream to be closed.  </p>
       *
       * @param  in
       *         The stream from which bytes are to be read
       *
       * @return  A new readable byte channel
       */
      public static ReadableByteChannel newChannel(final InputStream in) {
          checkNotNull(in, "in");
  
          if (in instanceof FileInputStream &&
              FileInputStream.class.equals(in.getClass())) {
              return ((FileInputStream)in).getChannel();
          }
  
          return new ReadableByteChannelImpl(in);
      }
  
      private static class ReadableByteChannelImpl
          extends AbstractInterruptibleChannel    // Not really interruptible
          implements ReadableByteChannel
      {
          InputStream in;
          private static final int TRANSFER_SIZE = 8192;
          private byte buf[] = new byte[0];
          private boolean open = true;
          private Object readLock = new Object();
  
          ReadableByteChannelImpl(InputStream in) {
              this.in = in;
          }
  
          public int read(ByteBuffer dst) throws IOException {
              int len = dst.remaining();
              int totalRead = 0;
              int bytesRead = 0;
              synchronized (readLock) {
                  while (totalRead < len) {
                      int bytesToRead = Math.min((len - totalRead),
                                                 TRANSFER_SIZE);
                      if (buf.length < bytesToRead)
                          buf = new byte[bytesToRead];
                      if ((totalRead > 0) && !(in.available() > 0))
                          break; // block at most once
                      try {
                          begin();
                          bytesRead = in.read(buf, 0, bytesToRead);
                      } finally {
                          end(bytesRead > 0);
                      }
                      if (bytesRead < 0)
                          break;
                      else
                          totalRead += bytesRead;
                      dst.put(buf, 0, bytesRead);
                  }
                  if ((bytesRead < 0) && (totalRead == 0))
                      return -1;
  
                  return totalRead;
              }
          }
  
          protected void implCloseChannel() throws IOException {
              in.close();
              open = false;
          }
      }
  
  
      /**
       * Constructs a channel that writes bytes to the given stream.
       *
       * <p> The resulting channel will not be buffered; it will simply redirect
       * its I/O operations to the given stream.  Closing the channel will in
       * turn cause the stream to be closed.  </p>
       *
       * @param  out
       *         The stream to which bytes are to be written
       *
       * @return  A new writable byte channel
       */
      public static WritableByteChannel newChannel(final OutputStream out) {
          checkNotNull(out, "out");
          return new WritableByteChannelImpl(out);
      }
  
      private static class WritableByteChannelImpl
          extends AbstractInterruptibleChannel    // Not really interruptible
          implements WritableByteChannel
      {
          OutputStream out;
          private static final int TRANSFER_SIZE = 8192;
          private byte buf[] = new byte[0];
          private boolean open = true;
          private Object writeLock = new Object();
  
          WritableByteChannelImpl(OutputStream out) {
              this.out = out;
          }
  
          public int write(ByteBuffer src) throws IOException {
              int len = src.remaining();
              int totalWritten = 0;
              synchronized (writeLock) {
                  while (totalWritten < len) {
                      int bytesToWrite = Math.min((len - totalWritten),
                                                  TRANSFER_SIZE);
                      if (buf.length < bytesToWrite)
                          buf = new byte[bytesToWrite];
                      src.get(buf, 0, bytesToWrite);
                      try {
                          begin();
                          out.write(buf, 0, bytesToWrite);
                      } finally {
                          end(bytesToWrite > 0);
                      }
                      totalWritten += bytesToWrite;
                  }
                  return totalWritten;
              }
          }
  
          protected void implCloseChannel() throws IOException {
              out.close();
              open = false;
          }
      }
  
  
      // -- Character streams from channels --
  
      /**
       * Constructs a reader that decodes bytes from the given channel using the
       * given decoder.
       *
       * <p> The resulting stream will contain an internal input buffer of at
       * least <tt>minBufferCap</tt> bytes.  The stream's <tt>read</tt> methods
       * will, as needed, fill the buffer by reading bytes from the underlying
       * channel; if the channel is in non-blocking mode when bytes are to be
       * read then an {@link IllegalBlockingModeException} will be thrown.  The
       * resulting stream will not otherwise be buffered, and it will not support
       * the {@link Reader#mark mark} or {@link Reader#reset reset} methods.
       * Closing the stream will in turn cause the channel to be closed.  </p>
       *
       * @param  ch
       *         The channel from which bytes will be read
       *
       * @param  dec
       *         The charset decoder to be used
       *
       * @param  minBufferCap
       *         The minimum capacity of the internal byte buffer,
       *         or <tt>-1</tt> if an implementation-dependent
       *         default capacity is to be used
       *
       * @return  A new reader
       */
      public static Reader newReader(ReadableByteChannel ch,
                                     CharsetDecoder dec,
                                     int minBufferCap)
      {
          checkNotNull(ch, "ch");
          return StreamDecoder.forDecoder(ch, dec.reset(), minBufferCap);
      }
  
      /**
       * Constructs a reader that decodes bytes from the given channel according
       * to the named charset.
       *
       * <p> An invocation of this method of the form
       *
       * <blockquote><pre>
       * Channels.newReader(ch, csname)</pre></blockquote>
       *
       * behaves in exactly the same way as the expression
       *
       * <blockquote><pre>
       * Channels.newReader(ch,
       *                    Charset.forName(csName)
       *                        .newDecoder(),
       *                    -1);</pre></blockquote>
       *
       * @param  ch
       *         The channel from which bytes will be read
       *
       * @param  csName
       *         The name of the charset to be used
       *
       * @return  A new reader
       *
       * @throws  UnsupportedCharsetException
       *          If no support for the named charset is available
       *          in this instance of the Java virtual machine
       */
      public static Reader newReader(ReadableByteChannel ch,
                                     String csName)
      {
          checkNotNull(csName, "csName");
          return newReader(ch, Charset.forName(csName).newDecoder(), -1);
      }
  
      /**
       * Constructs a writer that encodes characters using the given encoder and
       * writes the resulting bytes to the given channel.
       *
       * <p> The resulting stream will contain an internal output buffer of at
       * least <tt>minBufferCap</tt> bytes.  The stream's <tt>write</tt> methods
       * will, as needed, flush the buffer by writing bytes to the underlying
       * channel; if the channel is in non-blocking mode when bytes are to be
       * written then an {@link IllegalBlockingModeException} will be thrown.
       * The resulting stream will not otherwise be buffered.  Closing the stream
       * will in turn cause the channel to be closed.  </p>
       *
       * @param  ch
       *         The channel to which bytes will be written
       *
       * @param  enc
       *         The charset encoder to be used
       *
       * @param  minBufferCap
       *         The minimum capacity of the internal byte buffer,
       *         or <tt>-1</tt> if an implementation-dependent
       *         default capacity is to be used
       *
       * @return  A new writer
       */
      public static Writer newWriter(final WritableByteChannel ch,
                                     final CharsetEncoder enc,
                                     final int minBufferCap)
      {
          checkNotNull(ch, "ch");
          return StreamEncoder.forEncoder(ch, enc.reset(), minBufferCap);
      }
  
      /**
       * Constructs a writer that encodes characters according to the named
       * charset and writes the resulting bytes to the given channel.
       *
       * <p> An invocation of this method of the form
       *
       * <blockquote><pre>
       * Channels.newWriter(ch, csname)</pre></blockquote>
       *
       * behaves in exactly the same way as the expression
       *
       * <blockquote><pre>
       * Channels.newWriter(ch,
       *                    Charset.forName(csName)
       *                        .newEncoder(),
       *                    -1);</pre></blockquote>
       *
       * @param  ch
       *         The channel to which bytes will be written
       *
       * @param  csName
       *         The name of the charset to be used
       *
       * @return  A new writer
       *
       * @throws  UnsupportedCharsetException
       *          If no support for the named charset is available
       *          in this instance of the Java virtual machine
       */
      public static Writer newWriter(WritableByteChannel ch,
                                     String csName)
      {
          checkNotNull(csName, "csName");
          return newWriter(ch, Charset.forName(csName).newEncoder(), -1);
      }
  }
  