content: /*
   * Copyright (C) 2010-2101 Alibaba Group Holding Limited.
   *
   * 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 com.alibaba.otter.shared.common.utils;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.nio.channels.Channels;
  import java.nio.channels.FileChannel;
  import java.nio.channels.ReadableByteChannel;
  import java.nio.channels.WritableByteChannel;
  
  import org.apache.commons.io.FileUtils;
  import org.apache.commons.io.IOUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * 基于nio技术，提供一些快速的stream处理
   * 
   * @author jianghang 2011-10-9 下午06:28:44
   * @version 4.0.0
   */
  public class NioUtils {
  
      private static final Logger logger              = LoggerFactory.getLogger(NioUtils.class);
      private static final int    DEFAULT_BUFFER_SIZE = 8 * 1024;
      private static final int    timeWait            = 1000;
  
      /**
       * 基于流的数据copy
       */
      public static long copy(InputStream input, OutputStream output, long offset) throws IOException {
          long count = 0;
          long n = 0;
          if (input instanceof FileInputStream) {
              FileChannel inChannel = ((FileInputStream) input).getChannel();
              WritableByteChannel outChannel = Channels.newChannel(output);
              count = inChannel.transferTo(offset, inChannel.size() - offset, outChannel);
          } else if (output instanceof FileOutputStream) {
              FileChannel outChannel = ((FileOutputStream) output).getChannel();
              ReadableByteChannel inChannel = Channels.newChannel(input);
              do {
                  n = outChannel.transferFrom(inChannel, offset + count, DEFAULT_BUFFER_SIZE);
                  count += n;
              } while (n > 0);
          } else {
              byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  
              input.skip(offset);
              while (-1 != (n = input.read(buffer))) {
                  output.write(buffer, 0, (int) n);
                  count += n;
              }
              // ReadableByteChannel inChannel = Channels.newChannel(input);
              // WritableByteChannel outChannel = Channels.newChannel(output);
              //            
              // //ByteBuffer buffer = new ByteBuffer(DEFAULT_BUFFER_SIZE);
              // ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
              // while (-1 != (n = inChannel.read(buffer))) {
              // outChannel.write(buffer);
              // count += n;
              // }
          }
          return count;
      }
  
      /**
       * 基于流的数据copy
       */
      public static long copy(InputStream input, OutputStream output, long offset, long count) throws IOException {
          long rcount = 0;
          long n = 0;
          if (input instanceof FileInputStream) {
              FileChannel inChannel = ((FileInputStream) input).getChannel();
              WritableByteChannel outChannel = Channels.newChannel(output);
              rcount = inChannel.transferTo(offset, count, outChannel);
          } else if (output instanceof FileOutputStream) {
              FileChannel outChannel = ((FileOutputStream) output).getChannel();
              ReadableByteChannel inChannel = Channels.newChannel(input);
              do {
                  if (count < DEFAULT_BUFFER_SIZE) {
                      n = outChannel.transferFrom(inChannel, offset + rcount, count);
                  } else {
                      n = outChannel.transferFrom(inChannel, offset + rcount, DEFAULT_BUFFER_SIZE);
                  }
                  count -= n;
                  rcount += n;
              } while (n > 0);
          } else {
              byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  
              input.skip(offset);
              while (count > 0) {
                  if (count < DEFAULT_BUFFER_SIZE) {
                      n = input.read(buffer, 0, (int) count);
                  } else {
                      n = input.read(buffer, 0, DEFAULT_BUFFER_SIZE);
                  }
  
                  output.write(buffer, 0, (int) n);
                  count -= n;
                  rcount += n;
              }
              // ReadableByteChannel inChannel = Channels.newChannel(input);
              // WritableByteChannel outChannel = Channels.newChannel(output);
              //            
              // //ByteBuffer buffer = new ByteBuffer(DEFAULT_BUFFER_SIZE);
              // ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
              // while (-1 != (n = inChannel.read(buffer))) {
              // outChannel.write(buffer);
              // count += n;
              // }
          }
          return rcount;
      }
  
      /**
       * 基于流的数据copy
       */
      public static long copy(InputStream input, OutputStream output) throws IOException {
          long count = 0;
          long n = 0;
          if (input instanceof FileInputStream) {
              FileChannel inChannel = ((FileInputStream) input).getChannel();
              WritableByteChannel outChannel = Channels.newChannel(output);
              count = inChannel.transferTo(0, inChannel.size(), outChannel);
          } else if (output instanceof FileOutputStream) {
              FileChannel outChannel = ((FileOutputStream) output).getChannel();
              ReadableByteChannel inChannel = Channels.newChannel(input);
              do {
                  n = outChannel.transferFrom(inChannel, count, DEFAULT_BUFFER_SIZE);
                  count += n;
              } while (n > 0);
          } else {
              byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
  
              while (-1 != (n = input.read(buffer))) {
                  output.write(buffer, 0, (int) n);
                  count += n;
              }
              // ReadableByteChannel inChannel = Channels.newChannel(input);
              // WritableByteChannel outChannel = Channels.newChannel(output);
              //            
              // //ByteBuffer buffer = new ByteBuffer(DEFAULT_BUFFER_SIZE);
              // ByteBuffer buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE);
              // while (-1 != (n = inChannel.read(buffer))) {
              // outChannel.write(buffer);
              // count += n;
              // }
          }
          return count;
      }
  
      /**
       * 将byte[]数据写入到流中
       */
      public static void write(byte[] data, OutputStream output) throws IOException {
          ByteArrayInputStream input = null;
          try {
              input = new ByteArrayInputStream(data);
              copy(input, output);
              output.flush();
          } finally {
              IOUtils.closeQuietly(output);
          }
      }
  
      /**
       * 将byte数组写入文件
       */
      public static void write(final byte[] srcArray, File targetFile) throws IOException {
          write(srcArray, targetFile, true);
      }
  
      /**
       * 将byte数组写入文件
       */
      public static void write(final byte[] srcArray, File targetFile, final boolean overwrite) throws IOException {
          if (srcArray == null) {
              throw new IOException("Source must not be null");
          }
  
          if (targetFile == null) {
              throw new IOException("Target must not be null");
          }
  
          if (true == targetFile.exists()) {
              if (true == targetFile.isDirectory()) {
                  throw new IOException("Target '" + targetFile.getAbsolutePath() + "' is directory!");
              } else if (true == targetFile.isFile()) {
                  if (!overwrite) {
                      throw new IOException("Target file '" + targetFile.getAbsolutePath() + "' already exists!");
                  }
              } else {
                  throw new IOException("Invalid target object '" + targetFile.getAbsolutePath() + "'!");
              }
          } else {
              // create parent dir
              create(targetFile.getParentFile(), false, 3);
          }
  
          // 使用无拷贝的inputStream
          ByteArrayInputStream input = null;
          FileOutputStream output = null;
          try {
              input = new ByteArrayInputStream(srcArray);
              output = new FileOutputStream(targetFile);
              copy(input, output);
              output.flush();
          } finally {
              IOUtils.closeQuietly(input);
              IOUtils.closeQuietly(output);
          }
      }
  
      /**
       * 读取文件到数组
       */
      public static byte[] read(File sourceFile) throws IOException {
          if (sourceFile == null) {
              throw new IOException("Source must not be null");
          }
  
          if (false == sourceFile.exists()) {
              throw new IOException("Source '" + sourceFile + "' does not exist");
          }
  
          if (true == sourceFile.isDirectory()) {
              throw new IOException("Source '" + sourceFile + "' exists but is a directory");
          }
  
          FileInputStream input = null;
          ByteArrayOutputStream output = null;
  
          try {
              input = new FileInputStream(sourceFile);
              output = new ByteArrayOutputStream();
              copy(input, output);
              output.flush();
              return output.toByteArray();
          } finally {
              IOUtils.closeQuietly(input);
              IOUtils.closeQuietly(output);
          }
      }
  
      /**
       * 从流中读数据到byte[]
       */
      public static byte[] read(InputStream input) throws IOException {
          ByteArrayOutputStream output = null;
          try {
              output = new ByteArrayOutputStream();
              copy(input, output);
              output.flush();
              return output.toByteArray();
          } finally {
              IOUtils.closeQuietly(output);
          }
      }
  
      /**
       * Copy file overwrite
       */
      public static void copy(final File src, File dest) throws IOException {
          copy(src, dest, 1);
      }
  
      /**
       * 尝试多次复制文件，排除网络故障
       * 
       * @param src
       * @param dest
       * @param retryTimes
       * @throws IOException
       */
      public static boolean copy(final File src, File dest, final int retryTimes) throws IOException {
          int totalRetry = retryTimes;
  
          if (retryTimes < 1) {
              totalRetry = 1;
          }
  
          int retry = 0;
          while (retry++ < totalRetry) {
              try {
                  copy(src, dest, true);
                  return true;
              } catch (Exception ex) {
                  // 本次等待时间
                  int wait = (int) Math.pow(retry, retry) * timeWait;
                  wait = (wait < timeWait) ? timeWait : wait;
  
                  if (retry == totalRetry) {
                      if (ex instanceof IOException) {
                          throw (IOException) ex;
                      } else {
                          throw new IOException((ex == null) ? "unknow error" : ex.getMessage(), ex);
                      }
                  } else {
                      try {
                          Thread.sleep(wait);
                      } catch (InterruptedException e) {
                          // ignore
                      }
                  }
              }
          }
  
          return false;
      }
  
      /**
       * Copy source file to destination. If destination is a path then source file name is appended. If destination file
       * exists then: overwrite=true - destination file is replaced; overwite=false - exception is thrown
       */
      public static void copy(final File sourceFile, File targetFile, final boolean overwrite) throws IOException {
          if (sourceFile == null) {
              throw new IOException("Source must not be null");
          }
  
          if (targetFile == null) {
              throw new IOException("Target must not be null");
          }
  
          // checks
          if ((false == sourceFile.isFile()) || (false == sourceFile.exists())) {
              throw new IOException("Source file '" + sourceFile.getAbsolutePath() + "' not found!");
          }
  
          if (true == targetFile.exists()) {
              if (true == targetFile.isDirectory()) {
  
                  // Directory? -> use source file name
                  targetFile = new File(targetFile, sourceFile.getName());
              } else if (true == targetFile.isFile()) {
                  if (false == overwrite) {
                      throw new IOException("Target file '" + targetFile.getAbsolutePath() + "' already exists!");
                  }
              } else {
                  throw new IOException("Invalid target object '" + targetFile.getAbsolutePath() + "'!");
              }
          } else {
              // create parent dir
              FileUtils.forceMkdir(targetFile.getParentFile());
          }
  
          FileInputStream input = null;
          FileOutputStream output = null;
  
          try {
              input = new FileInputStream(sourceFile);
              output = new FileOutputStream(targetFile);
              copy(input, output);
              output.flush();
          } finally {
              IOUtils.closeQuietly(input);
              IOUtils.closeQuietly(output);
          }
      }
  
      /**
       * 正常创建
       */
      public static boolean create(File dest) {
          return create(dest, true, 1);
      }
  
      /**
       * 尝试多次创建
       */
      public static boolean create(File dest, final boolean isFile, final int retryTimes) {
          if (dest == null) {
              return false;
          }
  
          int totalRetry = retryTimes;
  
          if (retryTimes < 0) {
              totalRetry = 1;
          }
  
          int retry = 0;
          while (retry++ < totalRetry) {
              try {
                  if (true == isFile) {
                      if ((true == dest.exists()) || (true == dest.createNewFile())) {
                          return true;
                      }
                  } else {
                      FileUtils.forceMkdir(dest);
                      return true;
                  }
              } catch (Exception ex) {
                  // 本次等待时间
                  int wait = (int) Math.pow(retry, retry) * timeWait;
                  wait = (wait < timeWait) ? timeWait : wait;
  
                  // 尝试等待
                  if (retry == totalRetry) {
                      return false;
                  } else {
  
                      // 记录日志
                      logger.warn(String.format("[%s] create() - retry %s failed : wait [%s] ms , caused by %s",
                                                dest.getAbsolutePath(), retry, wait, ex.getMessage()));
                      try {
                          Thread.sleep(wait);
                      } catch (InterruptedException e) {
                          return false;
                      }
                  }
              }
          }
  
          return false;
      }
  
      /**
       * 正常删除，实在不行就jvm退出时删除
       * 
       * @param dest
       * @param retryTimes
       */
      public static boolean delete(File dest) {
          return delete(dest, 1);
      }
  
      /**
       * 尝试多次删除，实在不行就jvm退出时删除
       * 
       * @param dest
       * @param retryTimes
       */
      public static boolean delete(File dest, final int retryTimes) {
          if (dest == null) {
              return false;
          }
  
          if (false == dest.exists()) {
              return true;
          }
  
          int totalRetry = retryTimes;
          if (retryTimes < 1) {
              totalRetry = 1;
          }
  
          int retry = 0;
          while (retry++ < totalRetry) {
              try {
                  FileUtils.forceDelete(dest);
                  return true;
              } catch (FileNotFoundException ex) {
                  return true;
              } catch (Exception ex) {
                  // 本次等待时间
                  int wait = (int) Math.pow(retry, retry) * timeWait;
                  wait = (wait < timeWait) ? timeWait : wait;
                  if (retry == totalRetry) {
                      try {
                          FileUtils.forceDeleteOnExit(dest);
                          return false;
                      } catch (Exception e) {
                          // ignore
                      }
                  } else {
                      // 记录日志
                      logger.warn(String.format("[%s] delete() - retry %s failed : wait [%s] ms , caused by %s",
                                                dest.getAbsolutePath(), retry, wait, ex.getMessage()));
                      try {
                          Thread.sleep(wait);
                      } catch (InterruptedException e) {
                          // ignore
                      }
                  }
              }
          }
  
          return false;
      }
  
      /**
       * Move file without retry
       */
      public static void move(final File src, File dest) throws IOException {
          move(src, dest, 1);
      }
  
      /**
       * Moves the source file to the destination. If the destination cannot be created or is a read-only file, the method
       * returns <code>false</code>. Otherwise, the contents of the source are copied to the destination, the source is
       * deleted, and <code>true</code> is returned.
       * 
       * @param src The source file to move.
       * @param dest The destination where to move the file.
       * @param retryTimes Move and delete retry times
       */
      public static void move(final File src, File dest, final int retryTimes) throws IOException {
          copy(src, dest, retryTimes);
          delete(src, retryTimes);
      }
  
  }
  