/** * Copyright 2013-2014 Guoqiang Chen, Shanghai, China. All rights reserved. * * Email: subchen@gmail.com * URL: http://subchen.github.io/ * * 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 jetbrick.io; import java.io.File; public class PathUtils { /** * Returns normalized <code>path</code> (or simply the <code>path</code> if * it is already in normalized form). Normalized path does not contain any * empty or "." segments or ".." segments preceded by other segment than * "..". * * @param path path to normalize * @return normalize path */ public static String normalize(final String path) { if (path == null) { return null; } if (path.indexOf('.') == -1 && path.indexOf('/') == -1) { return path; } boolean wasNormalized = true; // 1. count number of nonempty segments in path // Note that this step is not really necessary because we could simply // estimate the number of segments as path.length() and do the empty // segment check in step two ;-). int numSegments = 0; int lastChar = path.length() - 1; for (int src = lastChar; src >= 0;) { int slash = path.lastIndexOf('/', src); if (slash != -1) { // empty segment? (two adjacent slashes?) if (slash == src) { if (src != lastChar) { // ignore the first slash occurence // (when numSegments == 0) wasNormalized = false; } } else { numSegments++; } } else { numSegments++; } src = slash - 1; } // 2. split path to segments skipping empty segments int[] segments = new int[numSegments]; char[] chars = new char[path.length()]; path.getChars(0, chars.length, chars, 0); numSegments = 0; for (int src = 0; src < chars.length;) { // skip empty segments while (src < chars.length && chars[src] == '/') { src++; } if (src < chars.length) { // note the segment start segments[numSegments++] = src; // seek to the end of the segment while (src < chars.length && chars[src] != '/') { src++; } } } // assert (numSegments == segments.length); // 3. scan segments and remove all "." segments and "foo",".." segment pairs final int DELETED = -1; for (int segment = 0; segment < numSegments; segment++) { int src = segments[segment]; if (chars[src++] == '.') { if (src == chars.length || chars[src] == '/') { // "." or"./" // delete the "." segment segments[segment] = DELETED; wasNormalized = false; } else { // ".something" if (chars[src++] == '.' && (src == chars.length || chars[src] == '/')) { // ".." or "../" // we have the ".." segment scan backwards for segment to delete together with ".." for (int toDelete = segment - 1; toDelete >= 0; toDelete--) { if (segments[toDelete] != DELETED) { if (chars[segments[toDelete]] != '.') { // delete the two segments segments[toDelete] = DELETED; segments[segment] = DELETED; wasNormalized = false; } break; } } } } } } // 4. join the result, if necessary if (wasNormalized) { // already normalized? nothing to do... return path; } else { // join the resulting normalized path, retain the leading and ending slash int dst = (chars[0] == '/') ? 1 : 0; for (int segment = 0; segment < numSegments; segment++) { int segmentStart = segments[segment]; if (segmentStart != DELETED) { // if we remembered segment lengths in step 2, we could use // System.arraycopy method now but we had to allocate one // more array for (int src = segmentStart; src < chars.length; src++) { char ch = chars[src]; chars[dst++] = ch; if (ch == '/') { break; } } } } return new String(chars, 0, dst); } } /** * 组合路径. */ public static String concat(final String parent, final String child) { if (parent == null) { return normalize(child); } if (child == null) { return normalize(parent); } return normalize(parent + '/' + child); } /** * 计算相对路径. */ public static String getRelativePath(final String path, final String relativePath) { if (relativePath.startsWith("/")) { return normalize(relativePath); } int separatorIndex = path.lastIndexOf('/'); if (separatorIndex != -1) { String newPath = path.substring(0, separatorIndex + 1); return normalize(newPath + relativePath); } else { return normalize(relativePath); } } /** * 转为 Unix 样式的路径. */ public static String separatorsToUnix(String path) { if (path == null || path.indexOf('\\') == -1) { return path; } return path.replace('\\', '/'); } /** * 转为 Windows 样式的路径. */ public static String separatorsToWindows(String path) { if (path == null || path.indexOf('/') == -1) { return path; } return path.replace('/', '\\'); } /** * 转为系统默认样式的路径. */ public static String separatorsToSystem(String path) { if (path == null) { return null; } if (File.separatorChar == '\\') { return separatorsToWindows(path); } return separatorsToUnix(path); } }