FilepathUtils.java

/*
 * @copyright defined in LICENSE.txt
 */

package hera.util;

import static hera.util.StringUtils.EMPTY_STRING;
import static hera.util.StringUtils.isEmpty;

import java.io.File;
import java.util.Stack;

public class FilepathUtils {

  private static final char DIRECTORY_SEPARATOR_CHAR = '/';

  private static final String DIRECTORY_SEPARATOR = "" + DIRECTORY_SEPARATOR_CHAR;

  private static final String CURRENT_DIR = ".";
  private static final String PARENT_DIR = "..";

  /**
   * Return canonical fragments from {@code path}.
   * <p>
   * You should read {@link File#getCanonicalPath()} if you don't know canonical form for path. This
   * method return directory names in array. It will skip if meet current directory string(".") It
   * will remove back if meet parent directory string("..")
   * </p>
   *
   * @param path path
   *
   * @return separated string array
   */
  public static String[] getCanonicalFragments(final String path) {
    final String safe = path.replace(File.separatorChar, DIRECTORY_SEPARATOR_CHAR);

    final Stack<String> stack = new Stack<String>();

    for (final String fragment : safe.split(DIRECTORY_SEPARATOR)) {
      if (isEmpty(fragment)) {
        continue;
      } else if (CURRENT_DIR.equals(fragment)) {
        continue;
      } else if (PARENT_DIR.equals(fragment)) {
        if (stack.isEmpty()) {
          throw new IllegalArgumentException();
        }
        stack.pop();
      } else {
        stack.push(fragment);
      }
    }

    return stack.toArray(new String[stack.size()]);
  }

  /**
   * Return canonical form for {@code path}.
   * <p>
   * Concatenate the fragments from {@link #getCanonicalFragments(String)}
   * </p>
   *
   * @param path path
   *
   * @return canonical path
   *
   * @see #getCanonicalFragments(String)
   */
  public static String getCanonicalForm(final String path) {
    final String[] fragments = getCanonicalFragments(path);
    if (0 == fragments.length) {
      if (path.startsWith(DIRECTORY_SEPARATOR)) {
        return DIRECTORY_SEPARATOR;
      } else {
        return path;
      }
    }
    final StringBuilder builder = new StringBuilder();
    if (path.startsWith(DIRECTORY_SEPARATOR)) {
      builder.append(DIRECTORY_SEPARATOR);
    }
    for (int i = 0; i < fragments.length; ++i) {
      if (0 != i) {
        builder.append(DIRECTORY_SEPARATOR);
      }
      builder.append(fragments[i]);
    }
    return builder.toString();
  }

  /**
   * Split path to directory, file basename and extension.
   *
   * @param path path
   *
   * @return split string array
   */
  public static String[] split(final String path) {
    if (null == path) {
      return null;
    }

    final String parent = getParentPath(path);
    final String filename = getFilename(path);
    final String name = FilenameUtils.stripExtension(filename);
    final String ext = FilenameUtils.getExtension(filename);

    return new String[] {parent, name, ext};
  }

  /**
   * Return parent directory's path of {@code path}.
   * <p>
   * Return {@code null} if {@code path} is null or parent path is unknown path.
   * </p>
   *
   * @param path file path
   * @return parent path
   */
  public static String getParentPath(final String path) {
    if (null == path) {
      return null;
    }

    if (EMPTY_STRING.equals(path) || DIRECTORY_SEPARATOR.equals(path)) {
      return null;
    }

    final int index = path.lastIndexOf(DIRECTORY_SEPARATOR);

    if (0 == index) {
      return DIRECTORY_SEPARATOR;
    } else if (index < 0) {
      return null;
    }

    return path.substring(0, index);
  }

  /**
   * Return filename from {@code path}.
   *
   * @param path file path
   *
   * @return filename
   */
  public static String getFilename(final String path) {
    if (null == path) {
      return null;
    }
    final String canonicalPath = getCanonicalForm(path);
    final int index = canonicalPath.lastIndexOf(DIRECTORY_SEPARATOR);

    return (index < 0) ? canonicalPath : canonicalPath.substring(index + 1);
  }

  /**
   * alias of {@link #append(String...)}.
   *
   * @param fragments path fragments
   *
   * @return concatenated path
   *
   * @see #append(String...)
   */
  public static String concat(final String... fragments) {

    return append(fragments);
  }

  /**
   * Append {@code fragment} and return path.
   *
   * @param fragments path fragments
   *
   * @return concatenated path
   */
  public static String append(final String... fragments) {
    return append(fragments, 0, fragments.length - 1);
  }

  /**
   * Extract sub fragments from {@code start} to {@code to} and append.
   *
   * @param fragments fragments
   * @param start start index
   * @param end end index
   *
   * @return concatenated path
   */
  public static String append(final String[] fragments, final int start, final int end) {

    final StringBuilder buffer = new StringBuilder();

    boolean isFirst = true;
    for (int i = start; i <= end; ++i) {
      final String fragment = fragments[i];
      if (null == fragment) {
        continue;
      }
      final char[] chs = fragment.toCharArray();
      int from = 0;
      int to = chs.length;

      // Remove directory separator in prefix.
      while (from < to && DIRECTORY_SEPARATOR_CHAR == chs[from]) {
        ++from;
      }

      // Remove directory separator suffix.
      while (from < to && DIRECTORY_SEPARATOR_CHAR == chs[to - 1]) {
        --to;
      }

      if (!isFirst || 0 != from) {
        // Add separator
        buffer.append(DIRECTORY_SEPARATOR_CHAR);
      }

      // append file path
      buffer.append(chs, from, to - from);

      isFirst = false;
    }

    return buffer.toString();
  }

}