content: /*
   * Copyright (C) 2022 AlexMofer
   *
   * Licensed 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.
   */
  package io.github.alexmofer.android.support.utils;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  /**
   * 流工具
   * Created by Alex on 2022/3/25.
   */
  public class StreamUtils {
  
      private static final int DEFAULT_BUFFER_SIZE = 8192;
      /**
       * The maximum size of array to allocate.
       * Some VMs reserve some header words in an array.
       * Attempts to allocate larger arrays may result in
       * OutOfMemoryError: Requested array size exceeds VM limit
       */
      private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
  
      private StreamUtils() {
          //no instance
      }
  
      /**
       * 复制
       *
       * @param input  输入
       * @param output 输出
       * @throws IOException 读写错误
       */
      public static void copy(InputStream input, OutputStream output) throws IOException {
          final byte[] buffer = new byte[1024];
          int count;
          while ((count = input.read(buffer)) != -1) {
              if (count == 0) {
                  count = input.read();
                  if (count < 0)
                      break;
                  output.write(count);
                  continue;
              }
              output.write(buffer, 0, count);
              output.flush();
          }
      }
  
      /**
       * Reads up to a specified number of bytes from the input stream. This
       * method blocks until the requested number of bytes has been read, end
       * of stream is detected, or an exception is thrown. This method does not
       * close the input stream.
       *
       * <p> The length of the returned array equals the number of bytes read
       * from the stream. If {@code len} is zero, then no bytes are read and
       * an empty byte array is returned. Otherwise, up to {@code len} bytes
       * are read from the stream. Fewer than {@code len} bytes may be read if
       * end of stream is encountered.
       *
       * <p> When this stream reaches end of stream, further invocations of this
       * method will return an empty byte array.
       *
       * <p> Note that this method is intended for simple cases where it is
       * convenient to read the specified number of bytes into a byte array. The
       * total amount of memory allocated by this method is proportional to the
       * number of bytes read from the stream which is bounded by {@code len}.
       * Therefore, the method may be safely called with very large values of
       * {@code len} provided sufficient memory is available.
       *
       * <p> The behavior for the case where the input stream is <i>asynchronously
       * closed</i>, or the thread interrupted during the read, is highly input
       * stream specific, and therefore not specified.
       *
       * <p> If an I/O error occurs reading from the input stream, then it may do
       * so after some, but not all, bytes have been read. Consequently the input
       * stream may not be at end of stream and may be in an inconsistent state.
       * It is strongly recommended that the stream be promptly closed if an I/O
       * error occurs.
       *
       * @param len the maximum number of bytes to read
       * @return a byte array containing the bytes read from this input stream
       * @throws IllegalArgumentException if {@code length} is negative
       * @throws IOException              if an I/O error occurs
       * @throws OutOfMemoryError         if an array of the required size cannot be
       *                                  allocated.
       * @implNote The number of bytes allocated to read data from this stream and return
       * the result is bounded by {@code 2*(long)len}, inclusive.
       */
      public static byte[] readNBytes(InputStream input, int len) throws IOException {
          if (len < 0) {
              throw new IllegalArgumentException("len < 0");
          }
  
          List<byte[]> bufs = null;
          byte[] result = null;
          int total = 0;
          int remaining = len;
          int n;
          do {
              byte[] buf = new byte[Math.min(remaining, DEFAULT_BUFFER_SIZE)];
              int nread = 0;
  
              // read to EOF which may read more or less than buffer size
              while ((n = input.read(buf, nread,
                      Math.min(buf.length - nread, remaining))) > 0) {
                  nread += n;
                  remaining -= n;
              }
  
              if (nread > 0) {
                  if (MAX_BUFFER_SIZE - total < nread) {
                      throw new OutOfMemoryError("Required array size too large");
                  }
                  if (nread < buf.length) {
                      buf = Arrays.copyOfRange(buf, 0, nread);
                  }
                  total += nread;
                  if (result == null) {
                      result = buf;
                  } else {
                      if (bufs == null) {
                          bufs = new ArrayList<>();
                          bufs.add(result);
                      }
                      bufs.add(buf);
                  }
              }
              // if the last call to read returned -1 or the number of bytes
              // requested have been read then break
          } while (n >= 0 && remaining > 0);
  
          if (bufs == null) {
              if (result == null) {
                  return new byte[0];
              }
              return result.length == total ?
                      result : Arrays.copyOf(result, total);
          }
  
          result = new byte[total];
          int offset = 0;
          remaining = total;
          for (byte[] b : bufs) {
              int count = Math.min(b.length, remaining);
              System.arraycopy(b, 0, result, offset, count);
              offset += count;
              remaining -= count;
          }
  
          return result;
      }
  
      /**
       * Reads all remaining bytes from the input stream. This method blocks until
       * all remaining bytes have been read and end of stream is detected, or an
       * exception is thrown. This method does not close the input stream.
       *
       * <p> When this stream reaches end of stream, further invocations of this
       * method will return an empty byte array.
       *
       * <p> Note that this method is intended for simple cases where it is
       * convenient to read all bytes into a byte array. It is not intended for
       * reading input streams with large amounts of data.
       *
       * <p> The behavior for the case where the input stream is <i>asynchronously
       * closed</i>, or the thread interrupted during the read, is highly input
       * stream specific, and therefore not specified.
       *
       * <p> If an I/O error occurs reading from the input stream, then it may do
       * so after some, but not all, bytes have been read. Consequently the input
       * stream may not be at end of stream and may be in an inconsistent state.
       * It is strongly recommended that the stream be promptly closed if an I/O
       * error occurs.
       *
       * @return a byte array containing the bytes read from this input stream
       * @throws IOException      if an I/O error occurs
       * @throws OutOfMemoryError if an array of the required size cannot be
       *                          allocated.
       * @implSpec This method invokes {@link #readNBytes(InputStream, int)} with a length of
       * {@link Integer#MAX_VALUE}.
       * @since 9
       */
      public static byte[] readAllBytes(InputStream input) throws IOException {
          return readNBytes(input, Integer.MAX_VALUE);
      }
  }
  