blob: 9cf1fecbb90c703bdbca2a885c6ae8f1ead29414 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.freemarker.docgen.core;
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.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
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;
}
public static Writer newFileWriter(File outputFile) throws IOException {
return Files.newBufferedWriter(outputFile.toPath(), StandardCharsets.UTF_8);
}
}