content: package com.alipay.rdf.file.util;
  
  import java.io.File;
  
  /**
   * Copyright (C) 2013-2018 Ant Financial Services Group
   *
   * @author hongwei.quhw
   * @version $Id: FileNameUtils.java, v 0.1 2018年3月12日 下午4:29:22 hongwei.quhw Exp $
   */
  public class FileNameUtils {
      public static final String EMPTY             = "";
      /**
       * The Unix separator character.
       */
      private static final char  UNIX_SEPARATOR    = '/';
  
      /**
       * The Windows separator character.
       */
      private static final char  WINDOWS_SEPARATOR = '\\';
  
      /**
       * The system separator character.
       */
      private static final char  SYSTEM_SEPARATOR  = File.separatorChar;
  
      /**
       * The separator character that is the opposite of the system separator.
       */
      private static final char  OTHER_SEPARATOR;
  
      static {
          if (isSystemWindows()) {
              OTHER_SEPARATOR = UNIX_SEPARATOR;
          } else {
              OTHER_SEPARATOR = WINDOWS_SEPARATOR;
          }
      }
  
      /**
       * Determines if Windows file system is in use.
       * 
       * @return true if the system is Windows
       */
      static boolean isSystemWindows() {
          return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;
      }
  
      /**
       * Normalizes a path, removing double and single dot path steps.
       * <p>
       * This method normalizes a path to a standard format.
       * The input may contain separators in either Unix or Windows format.
       * The output will contain separators in the format of the system.
       * <p>
       * A trailing slash will be retained.
       * A double slash will be merged to a single slash (but UNC names are handled).
       * A single dot path segment will be removed.
       * A double dot will cause that path segment and the one before to be removed.
       * If the double dot has no parent path segment to work with, {@code null}
       * is returned.
       * <p>
       * The output will be the same on both Unix and Windows except
       * for the separator character.
       * <pre>
       * /foo//               -->   /foo/
       * /foo/./              -->   /foo/
       * /foo/../bar          -->   /bar
       * /foo/../bar/         -->   /bar/
       * /foo/../bar/../baz   -->   /baz
       * //foo//./bar         -->   /foo/bar
       * /../                 -->   null
       * ../foo               -->   null
       * foo/bar/..           -->   foo/
       * foo/../../bar        -->   null
       * foo/../bar           -->   bar
       * //server/foo/../bar  -->   //server/bar
       * //server/../bar      -->   null
       * C:\foo\..\bar        -->   C:\bar
       * C:\..\bar            -->   null
       * ~/foo/../bar/        -->   ~/bar/
       * ~/../bar             -->   null
       * </pre>
       * (Note the file separator returned will be correct for Windows/Unix)
       *
       * @param filename  the filename to normalize, null returns null
       * @return the normalized filename, or null if invalid
       */
      public static String normalize(String filename) {
          return doNormalize(filename, SYSTEM_SEPARATOR, true);
      }
  
      /**
       * Internal method to perform the normalization.
       *
       * @param filename  the filename
       * @param separator The separator character to use
       * @param keepSeparator  true to keep the final separator
       * @return the normalized filename
       */
      private static String doNormalize(String filename, char separator, boolean keepSeparator) {
          if (filename == null) {
              return null;
          }
          int size = filename.length();
          if (size == 0) {
              return filename;
          }
          int prefix = getPrefixLength(filename);
          if (prefix < 0) {
              return null;
          }
  
          char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy
          filename.getChars(0, filename.length(), array, 0);
  
          // fix separators throughout
          char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR;
          for (int i = 0; i < array.length; i++) {
              if (array[i] == otherSeparator) {
                  array[i] = separator;
              }
          }
  
          // add extra separator on the end to simplify code below
          boolean lastIsDirectory = true;
          if (array[size - 1] != separator) {
              array[size++] = separator;
              lastIsDirectory = false;
          }
  
          // adjoining slashes
          for (int i = prefix + 1; i < size; i++) {
              if (array[i] == separator && array[i - 1] == separator) {
                  System.arraycopy(array, i, array, i - 1, size - i);
                  size--;
                  i--;
              }
          }
  
          // dot slash
          for (int i = prefix + 1; i < size; i++) {
              if (array[i] == separator && array[i - 1] == '.'
                  && (i == prefix + 1 || array[i - 2] == separator)) {
                  if (i == size - 1) {
                      lastIsDirectory = true;
                  }
                  System.arraycopy(array, i + 1, array, i - 1, size - i);
                  size -= 2;
                  i--;
              }
          }
  
          // double dot slash
          outer: for (int i = prefix + 2; i < size; i++) {
              if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.'
                  && (i == prefix + 2 || array[i - 3] == separator)) {
                  if (i == prefix + 2) {
                      return null;
                  }
                  if (i == size - 1) {
                      lastIsDirectory = true;
                  }
                  int j;
                  for (j = i - 4; j >= prefix; j--) {
                      if (array[j] == separator) {
                          // remove b/../ from a/b/../c
                          System.arraycopy(array, i + 1, array, j + 1, size - i);
                          size -= i - j;
                          i = j + 1;
                          continue outer;
                      }
                  }
                  // remove a/../ from a/../c
                  System.arraycopy(array, i + 1, array, prefix, size - i);
                  size -= i + 1 - prefix;
                  i = prefix + 1;
              }
          }
  
          if (size <= 0) { // should never be less than 0
              return "";
          }
          if (size <= prefix) { // should never be less than prefix
              return new String(array, 0, size);
          }
          if (lastIsDirectory && keepSeparator) {
              return new String(array, 0, size); // keep trailing separator
          }
          return new String(array, 0, size - 1); // lose trailing separator
      }
  
      /**
       * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>.
       * <p>
       * This method will handle a file in either Unix or Windows format.
       * <p>
       * The prefix length includes the first slash in the full filename
       * if applicable. Thus, it is possible that the length returned is greater
       * than the length of the input string.
       * <pre>
       * Windows:
       * a\b\c.txt           --> ""          --> relative
       * \a\b\c.txt          --> "\"         --> current drive absolute
       * C:a\b\c.txt         --> "C:"        --> drive relative
       * C:\a\b\c.txt        --> "C:\"       --> absolute
       * \\server\a\b\c.txt  --> "\\server\" --> UNC
       *
       * Unix:
       * a/b/c.txt           --> ""          --> relative
       * /a/b/c.txt          --> "/"         --> absolute
       * ~/a/b/c.txt         --> "~/"        --> current user
       * ~                   --> "~/"        --> current user (slash added)
       * ~user/a/b/c.txt     --> "~user/"    --> named user
       * ~user               --> "~user/"    --> named user (slash added)
       * </pre>
       * <p>
       * The output will be the same irrespective of the machine that the code is running on.
       * ie. both Unix and Windows prefixes are matched regardless.
       *
       * @param filename  the filename to find the prefix in, null returns -1
       * @return the length of the prefix, -1 if invalid or null
       */
      public static int getPrefixLength(String filename) {
          if (filename == null) {
              return -1;
          }
          int len = filename.length();
          if (len == 0) {
              return 0;
          }
          char ch0 = filename.charAt(0);
          if (ch0 == ':') {
              return -1;
          }
          if (len == 1) {
              if (ch0 == '~') {
                  return 2; // return a length greater than the input
              }
              return isSeparator(ch0) ? 1 : 0;
          } else {
              if (ch0 == '~') {
                  int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
                  int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
                  if (posUnix == -1 && posWin == -1) {
                      return len + 1; // return a length greater than the input
                  }
                  posUnix = posUnix == -1 ? posWin : posUnix;
                  posWin = posWin == -1 ? posUnix : posWin;
                  return Math.min(posUnix, posWin) + 1;
              }
              char ch1 = filename.charAt(1);
              if (ch1 == ':') {
                  ch0 = Character.toUpperCase(ch0);
                  if (ch0 >= 'A' && ch0 <= 'Z') {
                      if (len == 2 || isSeparator(filename.charAt(2)) == false) {
                          return 2;
                      }
                      return 3;
                  }
                  return -1;
  
              } else if (isSeparator(ch0) && isSeparator(ch1)) {
                  int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
                  int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
                  if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) {
                      return -1;
                  }
                  posUnix = posUnix == -1 ? posWin : posUnix;
                  posWin = posWin == -1 ? posUnix : posWin;
                  return Math.min(posUnix, posWin) + 1;
              } else {
                  return isSeparator(ch0) ? 1 : 0;
              }
          }
      }
  
      /**
       * Checks if the character is a separator.
       * 
       * @param ch  the character to check
       * @return true if it is a separator character
       */
      private static boolean isSeparator(char ch) {
          return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR;
      }
  
      public static String join(Object[] array, String separator) {
          if (array == null) {
              return null;
          }
          return join(array, separator, 0, array.length);
      }
  
      public static String join(Object[] array, String separator, int startIndex, int endIndex) {
          if (array == null) {
              return null;
          }
          if (separator == null) {
              separator = EMPTY;
          }
  
          // endIndex - startIndex > 0:   Len = NofStrings *(len(firstString) + len(separator))
          //           (Assuming that all Strings are roughly equally long)
          int bufSize = (endIndex - startIndex);
          if (bufSize <= 0) {
              return EMPTY;
          }
  
          bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length())
                      + separator.length());
  
          StringBuilder buf = new StringBuilder(bufSize);
  
          for (int i = startIndex; i < endIndex; i++) {
              if (i > startIndex) {
                  buf.append(separator);
              }
              if (array[i] != null) {
                  buf.append(array[i]);
              }
          }
          return buf.toString();
      }
  }
  