String (@Nullable String path, char separatorChar, boolean removeLastSlash, @Nullable SymlinkResolver resolver) { if (path == null || path.isEmpty()) { return path; } if (path.charAt(0) == '.') { if (path.length() == 1) { return ""; } char c = path.charAt(1); if (c == '/' || c == separatorChar) { path = path.substring(2); } } if (separatorChar != '/') { path = path.replace(separatorChar, '/'); } // trying to speed up the common case when there are no "//" or "/." 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, NullAppendable.INSTANCE); int slashIndex = path.lastIndexOf('/'); return slashIndex != -1 && slashIndex > start && slashIndex == path.length() - 1 ? path.substring(0, path.length() - 1) : path; } return path; } 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, resolver)) { return resolver.resolveSymlinksAndCanonicalize(path, separatorChar, removeLastSlash); } dots = 0; } separator = true; } else if (c == '.') { if (separator || dots > 0) { ++dots; } else { result.append('.'); } separator = false; } else { while (dots > 0) { result.append('.'); dots--; } result.append(c); separator = false; } } if (dots > 0) { if (!processDots(result, dots, start, resolver)) { return resolver.resolveSymlinksAndCanonicalize(path, separatorChar, removeLastSlash); } } int lastChar = result.length() - 1; if (removeLastSlash && lastChar >= 0 && result.charAt(lastChar) == '/' && lastChar > start) { result.deleteCharAt(lastChar); } return result.toString(); }