package org.nutz.lang.util; import java.io.File; import java.io.FileFilter; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.util.LinkedList; import org.nutz.lang.Encoding; import org.nutz.lang.Files; import org.nutz.lang.Lang; import org.nutz.lang.Strings; /** * 磁盘操作的帮助函数集合 * * @author zozoh(zozohtnt@gmail.com) * @author bonyfish(mc02cxj@gmail.com) */ public abstract class Disks { /** * 一个 Vistor 模式的目录深层遍历 * * @param f * 要遍历的目录或者文件,如果是目录,深层遍历,否则,只访问一次文件 * @param fv * 对文件要进行的操作 * @param filter * 遍历目录时,哪些文件应该被忽略 * @return 遍历的文件个数 */ public static int visitFile(File f, FileVisitor fv, FileFilter filter) { int re = 0; if (f.isFile()) { fv.visit(f); re++; } else if (f.isDirectory()) { File[] fs = null == filter ? f.listFiles() : f.listFiles(filter); if (fs != null) for (File theFile : fs) re += visitFile(theFile, fv, filter); } return re; } /** * 将两个文件对象比较,得出相对路径 * * @param base * 基础文件对象 * @param file * 相对文件对象 * @return 相对于基础文件对象的相对路径 */ public static String getRelativePath(File base, File file) { String pathBase = base.getAbsolutePath(); if (base.isDirectory()) pathBase += "/"; String pathFile = file.getAbsolutePath(); if (file.isDirectory()) pathFile += "/"; return getRelativePath(pathBase, pathFile); } /** * 将两个路径比较,得出相对路径 * * @param base * 基础路径,以 '/' 结束,表示目录 * @param path * 相对文件路径,以 '/' 结束,表示目录 * @return 相对于基础路径对象的相对路径 */ public static String getRelativePath(String base, String path) { return getRelativePath(base, path, "./"); } /** * 将两个路径比较,得出相对路径 * * @param base * 基础路径,以 '/' 结束,表示目录 * @param path * 相对文件路径,以 '/' 结束,表示目录 * @param equalPath * 如果两个路径相等,返回什么,通常为 "./"。 你也可以用 "" 或者 "." 或者随便什么字符串来表示 * * @return 相对于基础路径对象的相对路径 */ public static String getRelativePath(String base, String path, String equalPath) { // 如果两个路径相等 if (base.equals(path)) { return equalPath; } // 开始判断 String[] bb = Strings.splitIgnoreBlank(getCanonicalPath(base), "[\\\\/]"); String[] ff = Strings.splitIgnoreBlank(getCanonicalPath(path), "[\\\\/]"); int len = Math.min(bb.length, ff.length); int pos = 0; for (; pos < len; pos++) if (!bb[pos].equals(ff[pos])) break; // 证明路径是相等的 if (len == pos && bb.length == ff.length) { return equalPath; } // 开始查找不同 int dir = 1; if (base.endsWith("/")) dir = 0; StringBuilder sb = new StringBuilder(Strings.dup("../", bb.length - pos - dir)); return sb.append(Lang.concat(pos, ff.length - pos, '/', ff)).toString(); } /** * 获取两个路径从头部开始计算的交集 * * @param ph0 * 路径1 * @param ph1 * 路径2 * @param dft * 如果两个路径完全没有相交,那么返回什么 * @return 两个路径的交集 */ public static String getIntersectPath(String ph0, String ph1, String dft) { // 木可能有交集 if (null == ph0 || null == ph1) return dft; String[] ss0 = Strings.splitIgnoreBlank(ph0, "[\\\\/]"); String[] ss1 = Strings.splitIgnoreBlank(ph1, "[\\\\/]"); int pos = 0; int len = Math.min(ss0.length, ss1.length); for (; pos < len; pos++) { if (!ss0[pos].equals(ss1[pos])) break; } // 木有交集 if (pos == 0) return dft; // 得到 String re = Lang.concat(0, pos, "/", ss0).toString(); // 需要补全后面的 "/" 吗 if (ph0.endsWith("/") && ph1.endsWith("/")) return re + "/"; return re; } /** * 整理路径。 将会合并路径中的 ".." * * @param path * 路径 * @return 整理后的路径 */ public static String getCanonicalPath(String path) { if (Strings.isBlank(path)) return path; String[] pa = Strings.splitIgnoreBlank(path, "[\\\\/]"); LinkedList<String> paths = new LinkedList<String>(); for (String s : pa) { if ("..".equals(s)) { if (paths.size() > 0) paths.removeLast(); continue; } if (".".equals(s)) { // pass } else { paths.add(s); } } if (path.charAt(0) == '/') return Lang.concat("/", paths).insert(0, '/').toString(); return Lang.concat("/", paths).toString(); } /** * @return 当前账户的主目录全路径 */ public static String home() { return System.getProperty("user.home"); } /** * @param path * 相对用户主目录的路径 * @return 相对用户主目录的全路径 */ public static String home(String path) { return home() + path; } /** * 获取一个路径的绝对路径。如果该路径不存在,则返回null * * @param path * 路径 * @return 绝对路径 */ public static String absolute(String path) { return absolute(path, ClassTools.getClassLoader(), Encoding.defaultEncoding()); } /** * 获取一个路径的绝对路径。如果该路径不存在,则返回null * * @param path * 路径 * @param klassLoader * 参考 ClassLoader * @param enc * 路径编码方式 * @return 绝对路径 */ public static String absolute(String path, ClassLoader klassLoader, String enc) { path = normalize(path, enc); if (Strings.isEmpty(path)) return null; File f = new File(path); if (!f.exists()) { URL url = null; try { url = klassLoader.getResource(path); if (null == url) url = Thread.currentThread().getContextClassLoader().getResource(path); if (null == url) url = ClassLoader.getSystemResource(path); } catch (Throwable e) {} if (null != url) return normalize(url.getPath(), Encoding.UTF8);// 通过URL获取String,一律使用UTF-8编码进行解码 return null; } return path; } /** * 让路径变成正常路径,将 ~ 替换成用户主目录 * * @param path * 路径 * @return 正常化后的路径 */ public static String normalize(String path) { return normalize(path, Encoding.defaultEncoding()); } /** * 让路径变成正常路径,将 ~ 替换成用户主目录 * * @param path * 路径 * @param enc * 路径编码方式 * @return 正常化后的路径 */ public static String normalize(String path, String enc) { if (Strings.isEmpty(path)) return null; if (path.charAt(0) == '~') path = Disks.home() + path.substring(1); try { return URLDecoder.decode(path, enc); } catch (UnsupportedEncodingException e) { return null; } } /** * 遍历文件夹下以特定后缀结尾的文件(不包括文件夹,不包括.开头的文件) * * @param path * 根路径 * @param regex * 文件名的正则表达式 * @param deep * 是否深层遍历 * @param fv * 你所提供的访问器,当然就是你自己的逻辑咯 */ public static final void visitFile(String path, final String regex, final boolean deep, final FileVisitor fv) { File d = Files.findFile(path); if (null == d) return; visitFile(d, new FileVisitor() { public void visit(File f) { if (f.isDirectory()) return; fv.visit(f); } }, new FileFilter() { public boolean accept(File f) { if (f.isDirectory()) return deep; if (f.isHidden()) return false; if (Strings.isEmpty(regex)) return true; return f.getName().matches(regex); } }); } /** * 将多个路径拼合成一个路径,他会自动去除重复的 "/" * * <pre> * appendPath("a","b") => "a/b" * appendPath("/a","b/c") => "/a/b/c" * appendPath("/a/","/b/c") => "/a/b/c" * </pre> * * @param phs * 路径数组 * @return 拼合后的路径 */ public static String appendPath(String... phs) { String[] paths = Lang.without(phs, null); if (null != paths && paths.length > 0) { // zozoh: 嗯下面的逻辑木有必要了吧 // if (null == paths[0]) // paths[0] = "/"; String str = Lang.concat("/", paths).toString(); String[] ss = Strings.splitIgnoreBlank(str, "/"); str = Lang.concat("/", ss).toString(); if (paths[0].startsWith("/")) { return "/" + str; } return str; } return null; } }