blob: aed91f6769ed48bf659c37b5e507ad56212f84a7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 SSI Schaefer IT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SSI Schaefer IT Solutions GmbH
*******************************************************************************/
package org.eclipse.tea.library.build.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
/**
* Helper concerning files and directories;
*/
public final class FileUtils {
/**
* Deletes a directory recursively.
*
* @param directory
* directory to delete
*/
public static void deleteDirectory(File directory) {
if (!directory.exists()) {
return;
}
if (!directory.isDirectory()) {
throw new IllegalStateException("not a directory: " + directory);
}
List<String> ignoredFiles = Collections.emptyList();
deleteDirectoryContent(directory, ignoredFiles);
if (!directory.delete()) {
throw new IllegalStateException("cannot delete " + directory);
}
}
/**
* Delete the content of the directory
*
* @param directory
* {@link File} representing the directory, which content should
* be deleted
* @param ignoredFileNames
* a list of filenames, which should not be deleted
*/
public static void deleteDirectoryContent(File directory, List<String> ignoredFileNames) {
for (File child : directory.listFiles()) {
if (child.isDirectory()) {
deleteDirectoryContent(child, ignoredFileNames);
}
if (ignoredFileNames.contains(child.getName())) {
continue;
}
if (!child.delete()) {
throw new IllegalStateException("cannot delete " + child);
}
}
}
/**
* Copies a file to a directory preserving the file date.
* <p>
* This method copies the contents of the specified source file to a file of
* the same name in the specified destination directory. The destination
* directory is created if it does not exist. If the destination file
* exists, then this method will overwrite it. The last modified time of the
* resulting file will be set to that of the source file.
*
* @param srcFile
* an existing file to copy
* @param destDir
* the directory to place the copy in
* @throws IOException
* if an IO error occurs during copying
*/
public static void copyFileToDirectory(File srcFile, File destDir) throws IOException {
if (!destDir.isDirectory()) {
if (!destDir.mkdirs()) {
throw new IOException("cannot create directory " + destDir);
}
}
File destFile = new File(destDir, srcFile.getName());
copyFile(srcFile, destFile);
}
/**
* Convenience method to copy a file from a source to a destination. If the
* file already exists, it will be overwritten. The last modified time of
* the resulting file will be set to that of the source file.
*
* @param srcFile
* the file to copy from. Must not be <code>null</code>.
* @param destFile
* the file to copy to. Must not be <code>null</code>.
* @throws IOException
* if the copying fails.
*/
public static void copyFile(File srcFile, File destFile) throws IOException {
if (!srcFile.isFile()) {
throw new IOException("not a file: " + srcFile);
}
try (FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream out = new FileOutputStream(destFile)) {
StreamHelper.copyStream(fis, out);
}
long time = srcFile.lastModified();
if (!destFile.setLastModified(time)) {
throw new IOException("cannot set last modified time: " + destFile);
}
}
/**
* Tries to create a hardlink at destFile, falls back to copying in case
* something happens.
*
* @param srcFile
* @param destFile
* @throws IOException
*/
public static void hardLinkOrCopy(File srcFile, File destFile) throws IOException {
try {
Files.createLink(destFile.toPath(), srcFile.toPath());
} catch (Exception e) {
copyFile(srcFile, destFile);
}
}
/**
* Opens a stream to a remote file and downloads it to the given target.
*
* @param uri
* the URI of the file
* @param target
* the target to place the file to.
*/
public static void download(String uri, File target) throws Exception {
URL url = new URL(uri);
try (InputStream is = url.openStream(); FileOutputStream out = new FileOutputStream(target)) {
ReadableByteChannel rbc = Channels.newChannel(is);
out.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
}
}
/**
* Copies files from a source to a destination directory. All existing files
* in the target directory will be replaced. The last modified time of the
* resulting file will be set to that of the source file.
*
* @param srcDir
* the directory to copy from. Must not be <code>null</code>.
* @param destDir
* the directory to copy to. Must not be <code>null</code>.
* @throws IOException
* if the copying fails.
*/
public static void copyDirectory(File srcDir, File destDir) throws IOException {
copyDirectory(srcDir, destDir, Collections.emptyList());
}
/**
* Copies files from a source to a destination directory. All existing files
* in the target directory will be replaced. The last modified time of the
* resulting file will be set to that of the source file.
*
* @param srcDir
* the directory to copy from. Must not be <code>null</code>.
* @param destDir
* the directory to copy to. Must not be <code>null</code>.
* @throws IOException
* if the copying fails.
*/
public static void copyDirectory(File srcDir, File destDir, List<String> exclude) throws IOException {
if (!srcDir.isDirectory()) {
throw new IOException("not a directory: " + srcDir);
}
FileUtils.mkdirs(destDir);
for (File file : srcDir.listFiles()) {
if (exclude.contains(file.getName())) {
continue;
}
if (file.isFile()) {
copyFileToDirectory(file, destDir);
} else {
copyDirectory(file, new File(destDir, file.getName()), exclude);
}
}
}
/**
* Deletes a file or directory; throws an exception if it failed.
*/
public static void delete(File file) {
if (file.exists()) {
if (!file.delete()) {
throw new IllegalStateException("cannot delete " + file);
}
}
}
/**
* Creates a directory, including any necessary but nonexistent parent
* directories, in a thread-safe manner. Note that if this operation fails
* it may have succeeded in creating some of the necessary parent
* directories. Throws an exception if the creation failed.
*/
public static void mkdirs(File dir) {
// retry in case some other thread simultaneously creates the same
// directory too
long tried = 0;
while (++tried <= 20 && !dir.exists()) {
if (dir.mkdirs()) {
return;
}
try {
Thread.sleep(tried);
} catch (InterruptedException e) {
// nothing to do
}
}
if (dir.exists()) {
return;
}
throw new IllegalStateException("cannot create " + dir);
}
/**
* Writes a XML file without any special formatting.
*
* @param document
* DOM document
* @param file
* target file (UTF-8)
*/
public static void writeXml(Document document, File file) throws Exception {
DOMSource source = new DOMSource(document);
StreamResult result = new StreamResult(file);
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
xformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
xformer.transform(source, result);
}
/**
* Reads the given file and returns the resulting document.
*
* @param file
* the XML file to read
* @return DOM document or {@code null} if the file is not existing
*/
public static Document readXml(File file) throws Exception {
if (!file.exists() || !file.canRead()) {
return null;
}
FileInputStream is = null;
try {
is = new FileInputStream(file);
return readXml(is);
} finally {
StreamHelper.closeQuietly(is);
}
}
/**
* Reads the given stream and returns the resulting document.
*
* @param stream
* the input stream to read
* @return DOM document
*/
public static Document readXml(InputStream stream) throws Exception {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
return builder.parse(stream);
}
/**
* Reads a given properties file and returns the result.
*
* @param file
* the file to read
* @return the properties
*/
public static Properties readProperties(File file) {
try {
Properties p = new Properties();
try (FileInputStream fis = new FileInputStream(file)) {
p.load(fis);
}
return p;
} catch (IOException e) {
throw new IllegalStateException("cannot read " + file, e);
}
}
/**
* Writes properties to a file.
*
* @param props
* the properties
* @param file
* the file to write
*/
public static void writeProperties(Properties props, File file) throws IOException {
FileOutputStream fos = new FileOutputStream(file);
try {
props.store(fos, null);
} finally {
StreamHelper.closeQuietly(fos);
}
}
/**
* Moves a {@link File} from one location to another. Tries to rename the
* file (which fails in case the rename crosses partitions), and otherwise
* falls back to a copy/delete operation. If the target file already exists
* it will be deleted before moving.
*
* @param source
* the source filename
* @param target
* the target filename
* @throws IOException
* in case an error happens.
*/
public static void moveFile(File source, File target) throws IOException {
delete(target);
if (!source.renameTo(target)) {
copyFile(source, target);
delete(source);
}
}
/**
* Same as {@link #moveFile(File, File)} but for directories.
*/
public static void moveDirectory(File source, File target) throws IOException {
deleteDirectory(target);
if (!source.renameTo(target)) {
copyDirectory(source, target);
deleteDirectory(source);
}
}
public static void touchFile(File f) throws IOException {
if (f.exists()) {
if (!f.setLastModified(System.currentTimeMillis())) {
throw new IOException("setting last-modified time failed");
}
} else {
if (!f.createNewFile()) {
throw new IOException("cannot create " + f);
}
}
}
/**
* Reads a given {@link File} with the given {@link Charset} fully into a
* {@link String}
*
* @param file
* the {@link File} to read from
* @param cs
* the {@link Charset} used for conversion
* @return the {@link String} representing the complete contents of the
* {@link File}
* @throws IOException
* in case of an error.
*/
public static String readStringFromFile(File file, Charset cs) throws IOException {
if (!file.isFile()) {
throw new IOException("not a file: " + file);
}
if (file.length() > Integer.MAX_VALUE) {
throw new IOException("file is too big");
}
int len1 = (int) file.length();
char[] cbuf = new char[len1];
try (InputStreamReader r = new InputStreamReader(new FileInputStream(file), cs)) {
int len2 = r.read(cbuf, 0, len1);
return new String(cbuf, 0, len2);
}
}
/**
* Writes a given {@link String} to a given {@link File} in the given
* {@link Charset}, replacing any existing {@link File} at that location.
*
* @param file
* the {@link File} to write
* @param cs
* the {@link Charset} used for conversion
* @param contents
* the target contents of the {@link File}
* @throws IOException
* in case of an error.
*/
public static void writeFileFromString(File file, Charset cs, String contents) throws IOException {
try (OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(file), cs)) {
w.write(contents);
}
}
/**
* Reads all non-empty lines of a file. Line comments start with a hash
* mark.
*/
public static List<String> readListFile(File file) throws IOException {
if (!file.isFile()) {
throw new IOException("not a file: " + file);
}
Reader reader = new FileReader(file);
try {
return readListFile(reader);
} finally {
reader.close();
}
}
/**
* Reads all non-empty lines from a {@link Reader}. Line comments start with
* a hash mark. Doesn't close the reader.
*/
public static List<String> readListFile(Reader reader) throws IOException {
List<String> result = new ArrayList<>();
LineNumberReader r = new LineNumberReader(reader);
String line = r.readLine();
while (line != null) {
String element = line.trim();
if (!element.isEmpty() && !element.startsWith("#")) {
result.add(element);
}
line = r.readLine();
}
return result;
}
/**
* A {@link File} if f is not <code>null</code> and exists.
*/
public static File safeFile(String f) {
if (f == null) {
return null;
}
File r = new File(f);
if (!r.exists()) {
return null;
}
return r;
}
}