class PathUtil {

  public static String toCanonicalPath(String path, boolean removeLastSlash) {
    if (path == null || path.isEmpty()) {
      return path;
    }
    if (path.charAt(0) == '.') {
      if (path.length() == 1) {
        return "";
      }

      char c = path.charAt(1);
      if (c == '/') {
        path = path.substring(2);
      }
    }

    int index = -1;
    do {
      index = path.indexOf('/', index + 1);
      char next = index == path.length() - 1 ? 0 : path.charAt(index + 1);
      if (next == '.' || next == '/') {
        break;
      }

    } while (index != -1);

    if (index == -1) {
      if (removeLastSlash) {
        int start = processRoot(path, NullWriter.INSTANCE);
        int slashIndex = path.lastIndexOf('/');
        return slashIndex != -1 && slashIndex > start ? StringUtils.trimEnd(path, '/') : path;
      }
      return path;
    }
    String finalPath = path;
    Supplier<String> canonicalPath =
        () -> {
          try {
            return new File(finalPath).getCanonicalPath();
          } catch (IOException ignore) {
            return toCanonicalPath(finalPath, removeLastSlash);
          }
        };

    StringBuilder result = new StringBuilder(path.length());
    int start = processRoot(path, result);
    int dots = 0;
    boolean separator = true;

    for (int i = start; i < path.length(); ++i) {
      char c = path.charAt(i);
      if (c == '/') {
        if (!separator) {
          if (!processDots(result, dots, start)) {
            return canonicalPath.get();
          }
          dots = 0;
        }
        separator = true;
      } else if (c == '.') {
        if (separator || dots > 0) {
          ++dots;
        } else {
          result.append('.');
        }
        separator = false;
      } else {
        if (dots > 0) {
          StringUtils.repeatSymbol(result, '.', dots);
          dots = 0;
        }
        result.append(c);
        separator = false;
      }
    }

    if (dots > 0) {
      if (!processDots(result, dots, start)) {
        return canonicalPath.get();
      }
    }

    int lastChar = result.length() - 1;
    if (removeLastSlash && lastChar >= 0 && result.charAt(lastChar) == '/' && lastChar > start) {
      result.deleteCharAt(lastChar);
    }
    return result.toString();
  }

}