package org.freemarker.docgen; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.nio.charset.Charset; import java.util.Collection; import java.util.Collections; import java.util.regex.Pattern; import freemarker.template.utility.StringUtil; final class FileUtil { private static final int READ_BUFFER_SIZE = 4096; // Can't be instantiated private FileUtil() { // nop } private static final int COPY_BUFFER_SIZE = 64 * 1024; /** * Copies a class-loader resource into a plain file of the same name. * The path of the source resource is calculated as * {@link srcDirClass}'s package path + * {@link srcDirFurtherPath} + * {@link srcRelativePath}. * The path of the destination file is calculated as * {@link destDir} + {@link srcRelativePath}. * * @param srcBaseDir Possibly {@code null}. */ static void copyResourceIntoFile( Class<?> srcBaseClass, String srcBaseDir, String srcRelativePath, File destDir) throws IOException { File dstFile = new File( destDir, srcRelativePath.replace('/', File.separatorChar)); File curDestDir = dstFile.getParentFile(); if (!curDestDir.isDirectory() && !curDestDir.mkdirs()) { throw new IOException("Failed to create destination directory: " + curDestDir.getAbsolutePath()); } byte[] buffer = new byte[COPY_BUFFER_SIZE]; String finalResourcePath; if (srcBaseDir == null || srcBaseDir.length() == 0) { finalResourcePath = srcRelativePath; } else { finalResourcePath = srcBaseDir + "/" + srcRelativePath; } InputStream in = srcBaseClass.getResourceAsStream(finalResourcePath); if (in == null) { throw new IOException("Failed to open class-loader resource: " + finalResourcePath + " relatively to " + Transform.class.getPackage().getName()); } try { OutputStream out = new FileOutputStream(dstFile); try { int ln; while ((ln = in.read(buffer)) != -1) { out.write(buffer, 0, ln); } } finally { out.close(); } } finally { in.close(); } } static int copyDir( File srcDir, File destDir) throws IOException { return copyDir(srcDir, destDir, srcDir, Collections.emptySet()); } static int copyDir( File srcDir, File destDir, Collection<Pattern> ignoredFilePathPatterns) throws IOException { return copyDir(srcDir, destDir, srcDir, ignoredFilePathPatterns); } /** * @return the number of files copied. */ private static int copyDir( File srcDir, File destDir, File srcBaseDir, Collection<Pattern> ignoredFilePathPatterns) throws IOException { int fileCounter = 0; destDir = destDir.getAbsoluteFile(); srcDir = srcDir.getAbsoluteFile(); String srcBaseDirPath = ensureEndsWithFileSeparator(srcBaseDir.getAbsolutePath()); if (!destDir.isDirectory()) { if (destDir.exists()) { throw new IOException("Can't create directory, because a " + "file with the same name already exists: " + destDir.getAbsolutePath()); } if (!destDir.mkdir()) { throw new IOException("Failed to create directory: " + destDir.getAbsolutePath()); } } File[] ls = srcDir.listFiles(); if (ls == null) { throw new IOException("Failed to list directory: " + srcDir.getAbsolutePath()); } for (File f : srcDir.listFiles()) { String fName = f.getName(); if (isUsualIgnorableFileOrDirectory(fName) || isDocgenFile(fName)) { continue; } File dest = new File(destDir, fName); if (f.isFile()) { if (!isIgnoredFile(f, srcBaseDirPath, ignoredFilePathPatterns)) { copyFile(f, dest); fileCounter++; } } else if (f.isDirectory()) { fileCounter += copyDir(f, dest, srcBaseDir, ignoredFilePathPatterns); } else { throw new IOException( "Failed decide if it's a file or a directory: " + f.getAbsolutePath()); } } return fileCounter; } private static boolean isIgnoredFile(File f, String srcBaseDirPath, Collection<Pattern> ignoredFilePathPatterns) throws IOException { if (ignoredFilePathPatterns.isEmpty()) { return false; } srcBaseDirPath = ensureEndsWithFileSeparator(srcBaseDirPath); String filePath = f.getAbsolutePath(); if (!filePath.startsWith(srcBaseDirPath)) { throw new IOException("Unexpected: " + StringUtil.jQuote(filePath) + " doesn't start with " + StringUtil.jQuote(srcBaseDirPath)); } String slashRelFilePath = pathToUnixStyle(filePath.substring(srcBaseDirPath.length() - 1)); for (Pattern pattern : ignoredFilePathPatterns) { if (pattern.matcher(slashRelFilePath).matches()) { return true; } } return false; } private static void copyFile(File src, File dst) throws IOException { byte[] buffer = new byte[COPY_BUFFER_SIZE]; InputStream in = new FileInputStream(src); try { long srcLMD = 0L; srcLMD = src.lastModified(); if (srcLMD == 0) { throw new IOException("Failed to get the last modification " + "time of " + src.getAbsolutePath()); } OutputStream out = new FileOutputStream(dst); try { int ln; while ((ln = in.read(buffer)) != -1) { out.write(buffer, 0, ln); } } finally { out.close(); } if (srcLMD != 0L) { if (!dst.setLastModified(srcLMD)) { throw new IOException( "Failed to set last-modification-date for: " + dst.getAbsolutePath()); } } } finally { in.close(); } } static boolean isDocgenFile(String fName) { fName = fName.toLowerCase(); return fName.startsWith("docgen-") || fName.startsWith("docgen.") || fName.equals("docgen"); } static boolean isUsualIgnorableFileOrDirectory(String fName) { fName = fName.toLowerCase(); int i = fName.lastIndexOf("."); String fExt; if (i == -1) { fExt = ""; } else { fExt = fName.substring(i + 1); } // CVS files: if (fName.equals(".cvsignore") || fName.equals("cvs") || (fName.length() > 2 && fName.startsWith(".#"))) { return true; } // SVN files: if (fName.equals(".svn")) { return true; } // Temporary/backup files: if ( ( fExt.equals("bak") || fExt.equals("lock") || fExt.startsWith("~")) || (fName.length() > 2 && ( (fName.startsWith("#") && fName.endsWith("#")) || (fName.startsWith("%") && fName.endsWith("%")) || fName.startsWith("._"))) || (fName.length() > 1 && ( fName.endsWith("~") || fName.startsWith("~"))) ) { return true; } return false; } public static String loadString(File f, Charset charset) throws IOException { FileInputStream in = new FileInputStream(f); try { return loadString(in, charset); } finally { in.close(); } } public static String loadString(InputStream in, Charset charset) throws IOException { Reader r = new InputStreamReader(in, charset); StringBuilder sb = new StringBuilder(256); try { char[] buf = new char[READ_BUFFER_SIZE]; int ln; while ((ln = r.read(buf)) != -1) { sb.append(buf, 0, ln); } } finally { r.close(); } return sb.toString(); } /** * Converts UN*X style path to regular expression. In additional to standard UN*X path meta characters ( * <code>*</code>, <code>?</code>) it understands <code>**</code>, that is the same as in Ant. It assumes that the * matched path always starts with slash (they are absoulte paths to an imaginary base), and uses slash instead of * backslash. */ public static Pattern globToRegexp(String text) { StringBuilder sb = new StringBuilder(); if (!text.startsWith("/")) { text = "/" + text; } if (text.endsWith("/")) { text += "**"; } char[] chars = text.toCharArray(); int ln = chars.length; for (int i = 0; i < ln; i++) { char c = chars[i]; if (c == '\\' || c == '^' || c == '.' || c == '$' || c == '|' || c == '(' || c == ')' || c == '[' || c == ']' || c == '+' || c == '{' || c == '}' || c == '@') { sb.append('\\'); sb.append(c); } else if (i == 0 && ln > 2 && chars[0] == '*' && chars[1] == '*' && chars[2] == '/') { sb.append(".*/"); i += 2; } else if (c == '/' && i + 2 < ln && chars[i + 1] == '*' && chars[i + 2] == '*') { if (i + 3 == ln) { sb.append("/.*"); } else { sb.append("(/.*)?"); } i += 2; } else if (c == '*') { sb.append("[^/]*"); } else if (c == '?') { sb.append("[^/]"); } else { sb.append(c); } } return Pattern.compile(sb.toString()); } public static String pathToUnixStyle(String path) { return path.replace(File.separatorChar, '/'); } public static String ensureEndsWithFileSeparator(String path) { return path.length() > 0 && path.charAt(path.length() - 1) == File.separatorChar ? path : path + File.separatorChar; } }