package co.codewizards.cloudstore.core.util;
import static co.codewizards.cloudstore.core.io.StreamUtil.*;
import static co.codewizards.cloudstore.core.oio.OioFileFactory.*;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import java.io.BufferedInputStream;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.Uid;
import co.codewizards.cloudstore.core.oio.File;
import co.codewizards.cloudstore.core.progress.ProgressMonitor;
public final class IOUtil {
/**
* UTF-8 character set name.
*/
public static final String CHARSET_NAME_UTF_8 = "UTF-8";
/**
* 1 GB in bytes.
* This holds the result of the calculation 1 * 1024 * 1024 * 1024
*/
public static final long GIGABYTE = 1 * 1024 * 1024 * 1024;
public static final String COLLISION_FILE_NAME_INFIX = ".collision";
private static File tempDir = null;
private static final Logger logger = LoggerFactory.getLogger(IOUtil.class);
private IOUtil() { }
/**
* This method finds - if possible - a relative path for addressing
* the given <code>file</code> from the given <code>baseDir</code>.
* <p>
* If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
* the absolute path of <code>file</code> will be returned.
* </p>
* <p>
* Examples:
* <ul>
* <li>
* <code>baseDir="/home/marco"</code><br/>
* <code>file="temp/jfire/jboss/bin/run.sh"</code><br/>
* or<br/>
* <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
* <code>result="temp/jfire/jboss/bin/run.sh"</code>
* </li>
* <li>
* <code>baseDir="/home/marco/workspace.jfire/JFireBase"</code><br/>
* <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
* or<br/>
* <code>file="../../temp/jfire/jboss/bin/run.sh"</code><br/>
* <code>result="../../temp/jfire/jboss/bin/run.sh"</code>
* </li>
* <li>
* <code>baseDir="/tmp/workspace.jfire/JFireBase"</code><br/>
* <code>file="/home/marco/temp/jfire/jboss/bin/run.sh"</code><br/>
* <code>result="/home/marco/temp/jfire/jboss/bin/run.sh"</code> (absolute, because relative is not possible)
* </li>
* </ul>
* </p>
*
* @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
* from which to point to <code>file</code>.
* @param file The file to point to.
* @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
* if a relative path cannot be formulated (i.e. they have no directory in common).
* @throws IOException In case of an error
* @deprecated User {@link File#relativize(File)}
*/
@Deprecated
public static String getRelativePath(File baseDir, final String file)
throws IOException
{
// When passing the current working dir by "newFile(".").getAbsoluteFile()" to this method, it
// generates 2 "../" where there should be only one "../", because it counts the single "." at the end as subdirectory.
// Therefore, we now call once simplifyPath before we start.
// Maybe we don't need to call simplifyPath in _getRelativePath anymore. Need to check later. Marco.
// Additionally, this method did not work without baseDir being an absolute path. Therefore, I now check and make it absolute, if it is not yet.
if (baseDir.isAbsolute())
baseDir = createFile(simplifyPath(baseDir));
else
baseDir = createFile(simplifyPath(baseDir.getAbsoluteFile()));
File absFile;
final File tmpF = createFile(file);
if (tmpF.isAbsolute())
absFile = tmpF;
else
absFile = createFile(baseDir, file);
final File dest = absFile;
File b = baseDir;
String up = "";
while (b.getParentFile() != null) {
final String res = _getRelativePath(b, dest.getAbsolutePath());
if (res != null)
return up + res;
// up = "../" + up;
up = ".." + FILE_SEPARATOR + up;
b = b.getParentFile();
}
return absFile.getAbsolutePath();
}
/**
* This method finds - if possible - a relative path for addressing
* the given <code>file</code> from the given <code>baseDir</code>.
* <p>
* If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
* the absolute path of <code>file</code> will be returned.
* </p>
* See {@link getRelativePath} for examples.
*
* @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
* from which to point to <code>file</code>.
* @param file The file to point to.
* @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
* if a relative path cannot be formulated (i.e. they have no directory in common).
* @throws IOException In case of an error
* @deprecated User {@link File#relativize(File)}
*/
@Deprecated
public static String getRelativePath(final File baseDir, final File file)
throws IOException
{
return getRelativePath(baseDir, file.getPath());
}
/**
* This method finds - if possible - a relative path for addressing
* the given <code>file</code> from the given <code>baseDir</code>.
* <p>
* If it is not possible to denote <code>file</code> relative to <code>baseDir</code>,
* the absolute path of <code>file</code> will be returned.
* </p>
* See {@link getRelativePath} for examples.
*
* @param baseDir This directory is used as start for the relative path. It can be seen as the working directory
* from which to point to <code>file</code>.
* @param file The file to point to.
* @return the path to <code>file</code> relative to <code>baseDir</code> or the absolute path,
* if a relative path cannot be formulated (i.e. they have no directory in common).
* @throws IOException In case of an error
* @deprecated User {@link File#relativize(File)}
*/
@Deprecated
public static String getRelativePath(final String baseDir, final String file)
throws IOException
{
return getRelativePath(createFile(baseDir), file);
}
/**
* This method is a helper method used by {@link #getRelativePath(File, String) }. It will
* only return a relative path, if <code>file</code> is a child node of <code>baseDir</code>.
* Otherwise it returns <code>null</code>.
*
* @param baseDir The directory to which the resulting path will be relative.
* @param file The file to which the resulting path will point.
*/
private static String _getRelativePath(final File baseDir, final String file)
throws IOException
{
// we make baseDir now absolute in getRelativePath(...)
// if (!baseDir.isAbsolute())
// throw new IllegalArgumentException("baseDir \""+baseDir.getPath()+"\" is not absolute!");
File absFile;
final File tmpF = createFile(file);
if (tmpF.isAbsolute())
absFile = tmpF;
else
absFile = createFile(baseDir, file);
String absFileStr = null;
String baseDirStr = null;
for (int mode_base = 0; mode_base < 2; ++mode_base) {
switch (mode_base) {
case 0:
baseDirStr = simplifyPath(baseDir);
break;
case 1:
baseDirStr = baseDir.getCanonicalPath();
break;
default:
throw new IllegalStateException("this should never happen!");
}
for (int mode_abs = 0; mode_abs < 2; ++mode_abs) {
baseDirStr = addFinalSlash(baseDirStr);
switch (mode_abs) {
case 0:
absFileStr = simplifyPath(absFile);
break;
case 1:
absFileStr = absFile.getCanonicalPath();
break;
default:
throw new IllegalStateException("this should never happen!");
}
if (!absFileStr.startsWith(baseDirStr)) {
if (mode_base >= 1 && mode_abs >= 1)
return null;
// throw new IllegalArgumentException("file \""+file+"\" is not a child of baseDir \""+baseDir.getCanonicalPath()+"\"!");
}
else
break;
} // for (int mode_abs = 0; mode_abs < 2; ++mode_abs) {
} // for (int mode = 0; mode < 2; ++mode) {
if (baseDirStr == null)
throw new NullPointerException("baseDirStr == null");
if (absFileStr == null)
throw new NullPointerException("absFileStr == null");
return absFileStr.substring(baseDirStr.length(), absFileStr.length());
}
/**
* This method removes double slashes, single-dot-directories and double-dot-directories
* from a path. This means, it does nearly the same as <code>File.getCanonicalPath</code>, but
* it does not resolve symlinks. This is essential for the method <code>getRelativePath</code>,
* because this method first tries it with a simplified path before using the canonical path
* (prevents problems with iteration through directories, where there are symlinks).
* <p>
* Please note that this method makes the given path absolute!
*
* @param path A path to simplify, e.g. "/../opt/java/jboss/../jboss/./bin/././../server/default/lib/."
* @return the simplified string (absolute path), e.g. "/opt/java/jboss/server/default/lib"
*/
public static String simplifyPath(final File path)
{
logger.debug("simplifyPath: path='{}'", path);
final LinkedList<String> dirs = new LinkedList<String>();
final String pathStr = path.getAbsolutePath();
final boolean startWithSeparator = pathStr.startsWith(FILE_SEPARATOR);
final StringTokenizer tk = new StringTokenizer(pathStr, FILE_SEPARATOR, false);
while (tk.hasMoreTokens()) {
final String dir = tk.nextToken();
if (".".equals(dir))
;// nothing
else if ("..".equals(dir)) {
if (!dirs.isEmpty())
dirs.removeLast();
}
else
dirs.addLast(dir);
}
logger.debug("simplifyPath: dirs='{}'", dirs);
final StringBuffer sb = new StringBuffer();
for (final String dir : dirs) {
if (startWithSeparator || sb.length() > 0)
sb.append(FILE_SEPARATOR);
sb.append(dir);
}
return sb.toString();
}
/**
* Transfer data between streams.
* @param in The input stream
* @param out The output stream
* @param inputOffset How many bytes to skip before transferring
* @param inputLen How many bytes to transfer. -1 = all
* @return The number of bytes transferred
* @throws IOException if an error occurs.
*/
public static long transferStreamData(final InputStream in, final OutputStream out, final long inputOffset, final long inputLen)
throws java.io.IOException
{
return transferStreamData(in, out, inputOffset, inputLen, null);
}
public static long transferStreamData(final InputStream in, final OutputStream out, final long inputOffset, final long inputLen, final ProgressMonitor monitor)
throws java.io.IOException
{
final byte[] buf = new byte[4 * 4096];
if (monitor!= null) {
if (inputLen < 0)
monitor.beginTask("Copying data", 100);
else
monitor.beginTask("Copying data", (int)(inputLen / buf.length) + 1);
}
try {
int bytesRead;
int transferred = 0;
//skip offset
if(inputOffset > 0)
if(in.skip(inputOffset) != inputOffset)
throw new IOException("Input skip failed (offset "+inputOffset+")");
while (true) {
if(inputLen >= 0)
bytesRead = in.read(buf, 0, (int)Math.min(buf.length, inputLen-transferred));
else
bytesRead = in.read(buf);
if (bytesRead < 0)
break;
if (bytesRead > 0) {
out.write(buf, 0, bytesRead);
if (monitor != null && inputLen >= 0)
monitor.worked(1);
transferred += bytesRead;
}
if(inputLen >= 0 && transferred >= inputLen)
break;
}
out.flush();
return transferred;
} finally {
if (monitor!= null) {
if (inputLen < 0)
monitor.worked(100);
monitor.done();
}
}
}
/**
* Transfer all available data from an {@link InputStream} to an {@link OutputStream}.
* <p>
* This is a convenience method for <code>transferStreamData(in, out, 0, -1)</code>
* @param in The stream to read from
* @param out The stream to write to
* @return The number of bytes transferred
* @throws IOException In case of an error
*/
public static long transferStreamData(final InputStream in, final OutputStream out)
throws java.io.IOException
{
return transferStreamData(in, out, 0, -1);
}
/**
* This method deletes the given directory recursively. If the given parameter
* specifies a file and no directory, it will be deleted anyway. If one or more
* files or subdirectories cannot be deleted, the method still continues and tries
* to delete as many files/subdirectories as possible.
* <p>
* Before diving into a subdirectory, it first tries to delete <code>dir</code>. If this
* succeeds, it does not try to dive into it. This way, this implementation is symlink-safe.
* Hence, if <code>dir</code> denotes a symlink to another directory, this method does
* normally not delete the real contents of the directory.
* </p>
* <p>
* <b>Warning!</b> It sometimes still deletes the contents! This happens, if the user has no permissions
* onto a symlinked directory (thus, the deletion before dive-in fails), but its contents. In this case,
* the method will dive into the directory (because the deletion failed and it therefore assumes it to be a real
* directory) and delete the contents.
* </p>
* <p>
* We might later extend this method to do further checks (maybe call an OS program like we do in
* {@link #createSymlink(File, File, boolean)}).
* </p>
*
* @param dir The directory or file to delete
* @return <code>true</code> if the file or directory does not exist anymore.
* This means it either was not existing already before or it has been
* successfully deleted. <code>false</code> if the directory could not be
* deleted.
* @deprecated user File.deleteRecursively instead
*/
@Deprecated
public static boolean deleteDirectoryRecursively(final File dir)
{
if (!dir.existsNoFollow())
return true;
// If we're running this on linux (that's what I just tested ;) and dir denotes a symlink,
// we must not dive into it and delete its contents! We can instead directly delete dir.
// There is no way in Java (except for calling system tools) to find out whether it is a symlink,
// but we can simply delete it. If the deletion succeeds, it was a symlink or emtpy directory, otherwise it's a non-empty directory.
// This way, we don't delete the contents in symlinks and thus prevent data loss!
try {
if (dir.delete())
return true;
} catch(final SecurityException e) {
// ignore according to docs.
return false; // or should we really ignore this security exception and delete the contents?!?!?! To return false instead is definitely safer.
}
if (dir.isDirectory()) {
final File[] content = dir.listFiles();
for (final File f : content) {
if (f.isDirectory())
deleteDirectoryRecursively(f);
else
try {
f.delete();
} catch(final SecurityException e) {
// ignore according to docs.
}
}
}
try {
return dir.delete();
} catch(final SecurityException e) {
return false;
}
}
/**
* This method deletes the given directory recursively. If the given parameter
* specifies a file and no directory, it will be deleted anyway. If one or more
* files or subdirectories cannot be deleted, the method still continues and tries
* to delete as many files/subdirectories as possible.
*
* @param dir The directory or file to delete
* @return True, if the file or directory does not exist anymore. This means it either
* was not existing already before or it has been successfully deleted. False, if the
* directory could not be deleted.
* @deprecated Plz use File.deleteRecursively instead!
*/
@Deprecated
public static boolean deleteDirectoryRecursively(final String dir)
{
final File dirF = createFile(dir);
return deleteDirectoryRecursively(dirF);
}
/**
* Tries to find a unique, not existing folder name under the given root folder
* suffixed with a number. When found, the directory will be created.
* It will start with 0 and make Integer.MAX_VALUE number
* of iterations maximum. The first not existing foldername will be returned.
* If no foldername could be found after the maximum iterations a {@link IOException}
* will be thrown.
* <p>
* Note that the creation of the directory is not completely safe. This method is
* synchronized, but other processes could "steal" the unique filename.
*
* @param rootFolder The rootFolder to find a unique subfolder for
* @param prefix A prefix for the folder that has to be found.
* @return A File pointing to an unique (not existing) Folder under the given rootFolder and with the given prefix
* @throws IOException in case of an error
*/
public static synchronized File createUniqueIncrementalFolder(final File rootFolder, final String prefix) throws IOException
{
for(int n=0; n<Integer.MAX_VALUE; n++) {
final File f = createFile(rootFolder, String.format("%s%x", prefix, n));
if(!f.exists()) {
if(!f.mkdirs())
throw new IOException("The directory "+f.getAbsolutePath()+" could not be created");
return f;
}
}
throw new IOException("Iterated to Integer.MAX_VALUE and could not find a unique folder!");
}
/**
* Tries to find a unique, not existing folder name under the given root folder with a random
* number (in hex format) added to the given prefix. When found, the directory will be created.
* <p>
* The method will try to find a name for <code>maxIterations</code> number
* of itarations and use random numbers from 0 to <code>uniqueOutOf</code>.
* <p>
* Note that the creation of the directory is not completely safe. This method is
* synchronized, but other processes could "steal" the unique filename.
*
* @param rootFolder The rootFolder to find a unique subfolder for
* @param prefix A prefix for the folder that has to be found.
* @param maxIterations The maximum number of itarations this method shoud do.
* If after them still no unique folder could be found, a {@link IOException}
* is thrown.
* @param uniqueOutOf The range of random numbers to apply (0 - given value)
*
* @return A File pointing to an unique folder under the given rootFolder and with the given prefix
* @throws IOException in case of an error
*/
public static synchronized File createUniqueRandomFolder(final File rootFolder, final String prefix, final long maxIterations, final long uniqueOutOf) throws IOException
{
long count = 0;
while(++count <= maxIterations) {
final File f = createFile(rootFolder, String.format("%s%x", prefix, (long)(Math.random() * uniqueOutOf)));
if(!f.exists()) {
if(!f.mkdirs())
throw new IOException("The directory "+f.getAbsolutePath()+" could not be created");
return f;
}
}
throw new IOException("Reached end of maxIteration("+maxIterations+"), but could not acquire a unique fileName for folder "+rootFolder);
}
/**
* Tries to find a unique, not existing folder name under the given root folder with a random
* number (in hex format) added to the given prefix. When found, the directory will be created.
* <p>
* This is a convenience method for {@link createUniqueRandomFolder}
* and calls it with 10000 as maxIterations and 10000 as number range.
* <p>
* Note that this method might throw a {@link IOException}
* if it will not find a unique name within 10000 iterations.
* <p>
* Note that the creation of the directory is not completely safe. This method is
* synchronized, but other processes could "steal" the unique filename.
*
* @param rootFolder The rootFolder to find a unique subfolder for
* @param prefix A prefix for the folder that has to be found.
* @return A File pointing to an unique (non-existing) Folder under the given rootFolder and with the given prefix
* @throws IOException in case of an error
*/
public static File createUniqueRandomFolder(final File rootFolder, final String prefix) throws IOException
{
return createUniqueRandomFolder(rootFolder, prefix, 10000, 10000);
}
/**
* Get a file object from a base directory and a list of subdirectories or files.
* @param file The base directory
* @param subDirs The subdirectories or files
* @return The new file instance
*/
public static File getFile(final File file, final String ... subDirs)
{
File f = file;
for (final String subDir : subDirs)
f = createFile(f, subDir);
return f;
}
/**
* Write text to a file.
* @param file The file to write the text to
* @param text The text to write
* @param encoding The caracter set to use as file encoding (e.g. "UTF-8")
* @throws IOException in case of an io error
* @throws FileNotFoundException if the file exists but is a directory
* rather than a regular file, does not exist but cannot
* be created, or cannot be opened for any other reason
* @throws UnsupportedEncodingException If the named encoding is not supported
*/
public static void writeTextFile(final File file, final String text, final String encoding)
throws IOException, FileNotFoundException, UnsupportedEncodingException
{
OutputStream out = null;
OutputStreamWriter w = null;
try {
out = castStream(file.createOutputStream());
w = new OutputStreamWriter(out, encoding);
w.write(text);
} finally {
if (w != null) w.close();
if (out != null) out.close();
}
}
/**
* Read a text file and return the contents as string.
* @param f The file to read, maximum size 1 GB
* @param encoding The file encoding, e.g. "UTF-8"
* @throws FileNotFoundException if the file was not found
* @throws IOException in case of an io error
* @throws UnsupportedEncodingException If the named encoding is not supported
* @return The contents of the text file
*/
public static String readTextFile(final File f, final String encoding)
throws FileNotFoundException, IOException, UnsupportedEncodingException
{
if (f.length() > GIGABYTE)
throw new IllegalArgumentException("File exceeds " + GIGABYTE + " bytes: " + f.getAbsolutePath());
final StringBuffer sb = new StringBuffer();
final InputStream fin = castStream(f.createInputStream());
try {
final InputStreamReader reader = new InputStreamReader(fin, encoding);
try {
final char[] cbuf = new char[1024];
int bytesRead;
while (true) {
bytesRead = reader.read(cbuf);
if (bytesRead <= 0)
break;
else
sb.append(cbuf, 0, bytesRead);
}
} finally {
reader.close();
}
} finally {
fin.close();
}
return sb.toString();
}
/**
* Read a UTF-8 encoded text file and return the contents as string.
* <p>For other encodings, use {@link readTextFile}.
* @param f The file to read, maximum size 1 GB
* @throws FileNotFoundException if the file was not found
* @throws IOException in case of an io error
* @return The contents of the text file
*/
public static String readTextFile(final File f)
throws FileNotFoundException, IOException
{
return readTextFile(f, CHARSET_NAME_UTF_8);
}
/**
* Write text to a file using UTF-8 encoding.
* @param file The file to write the text to
* @param text The text to write
* @throws IOException in case of an io error
* @throws FileNotFoundException if the file exists but is a directory
* rather than a regular file, does not exist but cannot
* be created, or cannot be opened for any other reason
*/
public static void writeTextFile(final File file, final String text)
throws IOException, FileNotFoundException, UnsupportedEncodingException
{
writeTextFile(file, text, CHARSET_NAME_UTF_8);
}
/**
* Read a text file from an {@link InputStream} using
* the given encoding.
* <p>
* This method does NOT close the input stream!
* @param in The stream to read from. It will not be closed by this operation.
* @param encoding The charset used for decoding, e.g. "UTF-8"
* @return The contents of the input stream file
*/
public static String readTextFile(final InputStream in, final String encoding)
throws FileNotFoundException, IOException
{
final StringBuffer sb = new StringBuffer();
final InputStreamReader reader = new InputStreamReader(in, encoding);
final char[] cbuf = new char[1024];
int bytesRead;
while (true) {
bytesRead = reader.read(cbuf);
if (bytesRead <= 0)
break;
else
sb.append(cbuf, 0, bytesRead);
if (sb.length() > GIGABYTE)
throw new IllegalArgumentException("Text exceeds " + GIGABYTE + " bytes!");
}
return sb.toString();
}
/**
* Read a text file from an {@link InputStream} using
* UTF-8 encoding.
* <p>
* This method does NOT close the input stream!
* @param in The stream to read from. It will not be closed by this operation.
* @return The contents of the input stream file
*/
public static String readTextFile(final InputStream in)
throws FileNotFoundException, IOException
{
return readTextFile(in, CHARSET_NAME_UTF_8);
}
/**
* Get the extension of a filename.
* @param fileName A file name (might contain the full path) or <code>null</code>.
* @return <code>null</code>, if the given <code>fileName</code> doesn't contain
* a dot (".") or if the given <code>fileName</code> is <code>null</code>. Otherwise,
* returns all AFTER the last dot.
*/
public static String getFileExtension(final String fileName)
{
if (fileName == null)
return null;
final int lastIndex = fileName.lastIndexOf(".");
if (lastIndex < 0)
return null;
return fileName.substring(lastIndex + 1);
}
/**
* Get a filename without extension.
* @param fileName A file name (might contain the full path) or <code>null</code>.
* @return all before the last dot (".") or the full <code>fileName</code> if no dot exists.
* Returns <code>null</code>, if the given <code>fileName</code> is <code>null</code>.
*/
public static String getFileNameWithoutExtension(final String fileName)
{
if (fileName == null)
return null;
final int lastIndex = fileName.lastIndexOf(".");
if (lastIndex < 0)
return fileName;
return fileName.substring(0, lastIndex);
}
/**
* Get the temporary directory.
* <p>
* Note, that you should better use {@link #getUserTempDir()} in many situations
* since there is solely one global temp directory in GNU/Linux and you might run into permissions trouble
* and other collisions when using the global temp directory with a hardcoded static subdir.
* </p>
*
* @return The temporary directory.
* @see #getUserTempDir()
*/
public static File getTempDir()
{
if(tempDir == null)
tempDir = createFile(System.getProperty("java.io.tmpdir"));
return tempDir;
}
/**
* Get a user-dependent temp directory in every operating system and create it, if it does not exist.
*
* @param prefix <code>null</code> or a prefix to be added before the user-name.
* @param suffix <code>null</code> or a suffix to be added after the user-name.
* @return a user-dependent temp directory, which is created if it does not yet exist.
* @throws IOException if the directory does not exist and cannot be created.
*/
public static File createUserTempDir(final String prefix, final String suffix)
throws IOException
{
final File userTempDir = getUserTempDir(prefix, suffix);
if (userTempDir.isDirectory())
return userTempDir;
if (userTempDir.exists()) {
if (!userTempDir.isDirectory()) // two processes might call this method simultaneously - thus we must check again; in case it was just created.
throw new IOException("The user-dependent temp directory's path exists already, but is no directory: " + userTempDir.getPath());
}
if (!userTempDir.mkdirs()) {
if (!userTempDir.isDirectory())
throw new IOException("The user-dependent temp directory could not be created: " + userTempDir.getPath());
}
return userTempDir;
}
/**
* Get a user-dependent temp directory in every operating system. Given the same prefix and suffix, the resulting
* directory will always be the same on subsequent calls - even across different VMs.
* <p>
* Since many operating systems (for example GNU/Linux and other unixes) use one
* global temp directory for all users, you might run into collisions when using hard-coded sub-directories within the
* temp dir. Permission problems can cause exceptions and multiple users sharing the same directory
* at the same time might even lead to heisenbugs. Therefore, this method encodes the user
* name into a subdirectory under the system's temp dir.
* </p>
* <p>
* In order to prevent illegal directory names to be generated, this method encodes the user name (including the
* pre- and suffixes passed) using a {@link ParameterCoderMinusHex} which encodes all chars except
* '0'...'9', 'A'...'Z', 'a'...'z', '.', '_' and '+'.
* </p>
*
* @param prefix <code>null</code> or a prefix to be added before the user-name.
* @param suffix <code>null</code> or a suffix to be added after the user-name.
* @return a user-dependent temp directory.
* @see #getTempDir()
* @see #createUserTempDir(String, String)
*/
public static File getUserTempDir(final String prefix, final String suffix)
{
// In GNU/Linux, there is exactly one temp-directory for all users; hence we need to put the current OS user's name into the path.
String userNameDir = (prefix == null ? "" : prefix) + String.valueOf(getUserName()) + (suffix == null ? "" : suffix);
// the user name might contain illegal characters (in windows) => we encode basically all characters.
userNameDir = UrlEncoder.encode(userNameDir.replace('*', '_'));
return createFile(IOUtil.getTempDir(), userNameDir);
}
private static String getUserName()
{
return System.getProperty("user.name"); //$NON-NLS-1$
}
public static File getUserHome()
{
final String userHome = System.getProperty("user.home"); //$NON-NLS-1$
if (userHome == null)
throw new IllegalStateException("System property user.home is not set! This should never happen!"); //$NON-NLS-1$
return createFile(userHome);
}
/**
* Compares two InputStreams. The amount of bytes given in the parameter
* <code>length</code> are consumed from the streams, no matter if their
* contents are equal or not.
*
* @param inputStream1 the first InputStream to compare.
* @param inputStream2 the second InputStream to compare.
* @return true if both InputStreams contain the identical data or false if not
* @throws IOException if an I/O error occurs while reading <code>length</code> bytes
* from one of the input streams.
*/
public static boolean compareInputStreams(final InputStream inputStream1, final InputStream inputStream2)
throws IOException
{
assertNotNull(inputStream1, "inputStream1");
assertNotNull(inputStream2, "inputStream2");
// We use a BufferedInputStream, if the given stream does not support mark. This is, because we assume that it is
// already using a buffer, if it does support mark. For example, ByteArrayInputStream and BufferedInputStream do support
// mark. Both should not be decorated. FileInputStream does not support mark => it should definitely be decorated.
final InputStream in1 = inputStream1.markSupported() ? inputStream1 : new BufferedInputStream(inputStream1);
final InputStream in2 = inputStream2.markSupported() ? inputStream2 : new BufferedInputStream(inputStream2);
int b1;
while ((b1 = in1.read()) >= 0) {
final int b2 = in2.read();
if (b1 != b2)
return false;
}
return in2.read() < 0;
}
/**
* Compare the contents of two given files.
* @param f1 the first file
* @param f2 the second file
* @return <code>true</code> if the files have same size and their contents are equal -
* <code>false</code> otherwise.
* @throws FileNotFoundException If one of the files could not be found.
* @throws IOException If reading one of the files failed.
*/
public static boolean compareFiles(final File f1, final File f2) throws FileNotFoundException, IOException
{
if(!f1.exists())
throw new FileNotFoundException(f1.getAbsolutePath());
if(!f2.exists())
throw new FileNotFoundException(f2.getAbsolutePath());
if(f1.equals(f2))
return true;
if(f1.length() != f2.length())
return false;
try (final InputStream in1 = castStream(f1.createInputStream())) {
try (final InputStream in2 = castStream(f2.createInputStream())) {
return compareInputStreams(in1, in2);
}
}
}
/**
* Copy a directory recursively.
* @param sourceDirectory The source directory
* @param destinationDirectory The destination directory
* @throws IOException in case of an error
*/
public static void copyDirectory(final File sourceDirectory, final File destinationDirectory) throws IOException
{
copyDirectory(sourceDirectory, destinationDirectory, null);
}
public static void copyDirectory(final File sourceDirectory, final File destinationDirectory, final FileFilter fileFilter) throws IOException
{
if(!sourceDirectory.exists() || !sourceDirectory.isDirectory())
throw new IOException("No such source directory: "+sourceDirectory.getAbsolutePath());
if(destinationDirectory.exists()) {
if(!destinationDirectory.isDirectory())
throw new IOException("Destination exists but is not a directory: "+sourceDirectory.getAbsolutePath());
} else
destinationDirectory.mkdirs();
final File[] files = sourceDirectory.listFiles(fileFilter);
for (final File file : files) {
final File destinationFile = createFile(destinationDirectory, file.getName());
if(file.isDirectory()) {
if (destinationDirectory.getAbsoluteFile().equals(file.getAbsoluteFile()))
logger.warn("copyDirectory: Skipping directory, because it equals the destination: {}", file.getAbsoluteFile());
else
copyDirectory(file, destinationFile, fileFilter);
}
else
copyFile(file, destinationFile);
}
destinationDirectory.setLastModified(sourceDirectory.lastModified());
}
/**
* Copy a resource loaded by the class loader of a given class to a file.
* <p>
* This is a convenience method for <code>copyResource(sourceResClass, sourceResName, newFile(destinationFilename))</code>.
* @param sourceResClass The class whose class loader to use. If the class
* was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream
* will be used. See {@link Class#getResourceAsStream(String)} for details.
* @param sourceResName The name of the resource
* @param destinationFilename Where to copy the contents of the resource
* @throws IOException in case of an error
*/
public static void copyResource(final Class<?> sourceResClass, final String sourceResName, final String destinationFilename)
throws IOException
{
copyResource(sourceResClass, sourceResName, createFile(destinationFilename));
}
/**
* Copy a resource loaded by the class loader of a given class to a file.
* @param sourceResClass The class whose class loader to use. If the class
* was loaded using the bootstrap class loaderClassloader.getSystemResourceAsStream
* will be used. See {@link Class#getResourceAsStream(String)} for details.
* @param sourceResName The name of the resource
* @param destinationFile Where to copy the contents of the resource
* @throws IOException in case of an error
*/
public static void copyResource(final Class<?> sourceResClass, final String sourceResName, final File destinationFile)
throws IOException
{
InputStream source = null;
OutputStream destination = null;
try{
source = sourceResClass.getResourceAsStream(sourceResName);
if (source == null)
throw new FileNotFoundException("Class " + sourceResClass.getName() + " could not find resource " + sourceResName);
if (destinationFile.exists()) {
if (destinationFile.isFile()) {
if (!destinationFile.canWrite())
throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath());
} else
throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath());
} else {
final File parentdir = destinationFile.getAbsoluteFile().getParentFile();
if (parentdir == null || !parentdir.exists())
throw new IOException("FileCopy: destination's parent directory doesn't exist: " + destinationFile.getCanonicalPath());
if (!parentdir.canWrite())
throw new IOException("FileCopy: destination's parent directory is unwriteable: " + destinationFile.getCanonicalPath());
}
destination = castStream(destinationFile.createOutputStream());
transferStreamData(source,destination);
} finally {
if (source != null)
try { source.close(); } catch (final IOException e) { ; }
if (destination != null)
try { destination.close(); } catch (final IOException e) { ; }
}
}
/**
* Copy a file.
* @param sourceFile The source file to copy
* @param destinationFile To which file to copy the source
* @throws IOException in case of an error
*/
public static void copyFile(final File sourceFile, final File destinationFile)
throws IOException
{
copyFile(sourceFile, destinationFile, null);
}
public static void copyFile(final File sourceFile, final File destinationFile, final ProgressMonitor monitor)
throws IOException
{
InputStream source = null;
OutputStream destination = null;
try {
// First make sure the specified source file
// exists, is a file, and is readable.
if (!sourceFile.exists() || !sourceFile.isFile())
throw new IOException("FileCopy: no such source file: "+sourceFile.getCanonicalPath());
if (!sourceFile.canRead())
throw new IOException("FileCopy: source file is unreadable: "+sourceFile.getCanonicalPath());
// If the destination exists, make sure it is a writeable file. If the destination doesn't
// exist, make sure the directory exists and is writeable.
if (destinationFile.exists()) {
if (destinationFile.isFile()) {
if (!destinationFile.canWrite())
throw new IOException("FileCopy: destination file is unwriteable: " + destinationFile.getCanonicalPath());
} else
throw new IOException("FileCopy: destination is not a file: " + destinationFile.getCanonicalPath());
} else {
final File parentdir = destinationFile.getParentFile();
if (parentdir == null || !parentdir.exists())
throw new IOException("FileCopy: destination directory doesn't exist: " +
destinationFile.getCanonicalPath());
if (!parentdir.canWrite())
throw new IOException("FileCopy: destination directory is unwriteable: " +
destinationFile.getCanonicalPath());
}
// If we've gotten this far, then everything is okay; we can
// copy the file.
source = castStream(sourceFile.createInputStream());
destination = castStream(destinationFile.createOutputStream());
transferStreamData(source, destination, 0, sourceFile.length(), monitor);
// No matter what happens, always close any streams we've opened.
} finally {
if (source != null)
try { source.close(); } catch (final IOException e) { ; }
if (destination != null)
try { destination.close(); } catch (final IOException e) { ; }
}
// copy the timestamp
destinationFile.setLastModified(sourceFile.lastModified());
}
/**
* Add a trailing file separator character to the
* given directory name if it does not already
* end with one.
* @see File#separator
* @param directory A directory name
* @return the directory name anding with a file seperator
*/
public static String addFinalSlash(final String directory)
{
if (directory.endsWith(FILE_SEPARATOR))
return directory;
else
return directory + FILE_SEPARATOR;
}
private static enum ParserExpects {
NORMAL,
BRACKET_OPEN,
VARIABLE,
BRACKET_CLOSE
}
/**
* Generate a file from a template. The template file can contain variables which are formatted <code>"${variable}"</code>.
* All those variables will be replaced, for which a value has been passed in the map <code>variables</code>.
* <p>
* Example:<br/>
* <pre>
* ***
* Dear ${receipient.fullName},
* this is a spam mail trying to sell you ${product.name} for a very cheap price.
* Best regards, ${sender.fullName}
* ***
* </pre>
* <br/>
* In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys:
* <ul>
* <li>receipient.fullName</li>
* <li>product.name</li>
* <li>sender.fullName</li>
* </ul>
* </p>
* <p>
* If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
* logged).
* </p>
*
* @param destinationFile The file (absolute!) that shall be created out of the template.
* @param templateFile The template file to use. Must not be <code>null</code>.
* @param characterSet The charset to use for the input and output file. Use <code>null</code> for the default charset.
* @param variables This map defines what variable has to be replaced by what value. The
* key is the variable name (without '$' and brackets '{', '}'!) and the value is the
* value that will replace the variable in the output. This argument must not be <code>null</code>.
* In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code>
* from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
* value are ignored. Values that are not of type <code>String</code> are converted using the
* {@link Object#toString() toString()} method.
*/
public static void replaceTemplateVariables(final File destinationFile, final File templateFile, final String characterSet, final Map<?, ?> variables)
throws IOException
{
if (!destinationFile.isAbsolute())
throw new IllegalArgumentException("destinationFile is not absolute: " + destinationFile.getPath());
logger.info("Creating destination file \""+destinationFile.getAbsolutePath()+"\" from template \""+templateFile.getAbsolutePath()+"\".");
final File destinationDirectory = destinationFile.getParentFile();
if (!destinationDirectory.exists()) {
logger.info("Directory for destination file does not exist. Creating it: " + destinationDirectory.getAbsolutePath());
if (!destinationDirectory.mkdirs())
logger.error("Creating directory for destination file failed: " + destinationDirectory.getAbsolutePath());
}
// Create and configure StreamTokenizer to read template file.
final InputStream fin = castStream(templateFile.createInputStream());
try {
final Reader fr = characterSet != null ? new InputStreamReader(fin, characterSet) : new InputStreamReader(fin);
try {
// Create FileWriter
final OutputStream fos = castStream(destinationFile.createOutputStream());
try {
final Writer fw = characterSet != null ? new OutputStreamWriter(fos, characterSet) : new OutputStreamWriter(fos);
try {
replaceTemplateVariables(fw, fr, variables);
} finally {
fw.close();
}
} finally {
fos.close();
}
} finally {
fr.close();
}
} finally {
fin.close();
}
}
/**
* Replace variables (formatted <code>"${variable}"</code>) with their values
* passed in the map <code>variables</code>.
* <p>
* Example:<br/>
* <pre>
* ***
* Dear ${receipient.fullName},
* this is a spam mail trying to sell you ${product.name} for a very cheap price.
* Best regards, ${sender.fullName}
* ***
* </pre>
* <br/>
* In order to generate a text from the above template, the map <code>variables</code> needs to contain values for these keys:
* <ul>
* <li>receipient.fullName</li>
* <li>product.name</li>
* <li>sender.fullName</li>
* </ul>
* </p>
* <p>
* If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
* logged).
* </p>
*
* @param template the input text containing variables (or not).
* @param variables This map defines what variable has to be replaced by what value. The
* key is the variable name (without '$' and brackets '{', '}'!) and the value is the
* value that will replace the variable in the output. This argument must not be <code>null</code>.
* In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code>
* from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
* value are ignored. Values that are not of type <code>String</code> are converted using the
* {@link Object#toString() toString()} method.
* @return the processed text (i.e. the same as the template, but all known variables replaced by their values).
*/
public static String replaceTemplateVariables(final String template, final Map<?, ?> variables)
{
try {
final StringReader r = new StringReader(template);
final StringWriter w = new StringWriter();
try {
replaceTemplateVariables(w, r, variables);
w.flush();
return w.toString();
} finally {
r.close();
w.close();
}
} catch (final IOException x) {
throw new RuntimeException(x); // StringReader/Writer should *NEVER* throw any IOException since it's working in-memory only.
}
}
/**
* Copy contents from the given reader to the given writer while
* replacing variables which are formatted <code>"${variable}"</code> with their values
* passed in the map <code>variables</code>.
* <p>
* Example:<br/>
* <pre>
* ***
* Dear ${receipient.fullName},
* this is a spam mail trying to sell you ${product.name} for a very cheap price.
* Best regards, ${sender.fullName}
* ***
* </pre>
* <br/>
* In order to generate a file from the above template, the map <code>variables</code> needs to contain values for these keys:
* <ul>
* <li>receipient.fullName</li>
* <li>product.name</li>
* <li>sender.fullName</li>
* </ul>
* </p>
* <p>
* If a key is missing in the map, the variable will not be replaced but instead written as-is into the destination file (a warning will be
* logged).
* </p>
*
* @param writer The writer to write the output to.
* @param reader The template file to use. Must not be <code>null</code>.
* @param variables This map defines what variable has to be replaced by what value. The
* key is the variable name (without '$' and brackets '{', '}'!) and the value is the
* value that will replace the variable in the output. This argument must not be <code>null</code>.
* In order to allow passing {@link Properties} directly, this has been redefined as <code>Map<?,?></code>
* from version 1.3.0 on. Map entries with a key that is not of type <code>String</code> or with a <code>null</code>
* value are ignored. Values that are not of type <code>String</code> are converted using the
* {@link Object#toString() toString()} method.
*/
public static void replaceTemplateVariables(final Writer writer, final Reader reader, final Map<?, ?> variables)
throws IOException
{
final StreamTokenizer stk = new StreamTokenizer(reader);
stk.resetSyntax();
stk.wordChars(0, Integer.MAX_VALUE);
stk.ordinaryChar('$');
stk.ordinaryChar('{');
stk.ordinaryChar('}');
stk.ordinaryChar('\n');
// Read, parse and replace variables from template and write to FileWriter fw.
String variableName = null;
final StringBuilder tmpBuf = new StringBuilder();
ParserExpects parserExpects = ParserExpects.NORMAL;
while (stk.nextToken() != StreamTokenizer.TT_EOF) {
String stringToWrite = null;
if (stk.ttype == StreamTokenizer.TT_WORD) {
switch (parserExpects) {
case VARIABLE:
parserExpects = ParserExpects.BRACKET_CLOSE;
variableName = stk.sval;
tmpBuf.append(variableName);
break;
case NORMAL:
stringToWrite = stk.sval;
break;
default:
parserExpects = ParserExpects.NORMAL;
stringToWrite = tmpBuf.toString() + stk.sval;
tmpBuf.setLength(0);
}
}
else if (stk.ttype == '\n') {
stringToWrite = new String(Character.toChars(stk.ttype));
// These chars are not valid within a variable, so we reset the variable parsing, if we're currently parsing one.
// This helps keeping the tmpBuf small (to check for rowbreaks is not really necessary).
if (parserExpects != ParserExpects.NORMAL) {
parserExpects = ParserExpects.NORMAL;
tmpBuf.append(stringToWrite);
stringToWrite = tmpBuf.toString();
tmpBuf.setLength(0);
}
}
else if (stk.ttype == '$') {
if (parserExpects != ParserExpects.NORMAL) {
stringToWrite = tmpBuf.toString();
tmpBuf.setLength(0);
}
tmpBuf.appendCodePoint(stk.ttype);
parserExpects = ParserExpects.BRACKET_OPEN;
}
else if (stk.ttype == '{') {
switch (parserExpects) {
case NORMAL:
stringToWrite = new String(Character.toChars(stk.ttype));
break;
case BRACKET_OPEN:
tmpBuf.appendCodePoint(stk.ttype);
parserExpects = ParserExpects.VARIABLE;
break;
default:
parserExpects = ParserExpects.NORMAL;
tmpBuf.appendCodePoint(stk.ttype);
stringToWrite = tmpBuf.toString();
tmpBuf.setLength(0);
}
}
else if (stk.ttype == '}') {
switch (parserExpects) {
case NORMAL:
stringToWrite = new String(Character.toChars(stk.ttype));
break;
case BRACKET_CLOSE:
parserExpects = ParserExpects.NORMAL;
tmpBuf.appendCodePoint(stk.ttype);
if (variableName == null)
throw new IllegalStateException("variableName is null!!!");
final Object variableValue = variables.get(variableName);
stringToWrite = variableValue == null ? null : variableValue.toString();
if (stringToWrite == null) {
logger.warn("Variable " + tmpBuf.toString() + " occuring in template is unknown!");
stringToWrite = tmpBuf.toString();
}
else
stringToWrite = replaceTemplateVariables(stringToWrite, variables); // recursively resolve variables!
tmpBuf.setLength(0);
break;
default:
parserExpects = ParserExpects.NORMAL;
tmpBuf.appendCodePoint(stk.ttype);
stringToWrite = tmpBuf.toString();
tmpBuf.setLength(0);
}
}
if (stringToWrite != null)
writer.write(stringToWrite);
} // while (stk.nextToken() != StreamTokenizer.TT_EOF) {
}
public static byte[] getBytesFromFile(final File file) throws IOException {
// Get the size of the file
final long length = file.length();
final byte[] bytes = new byte[(int)length];
final InputStream is = castStream(file.createInputStream());
try {
// Read in the bytes
int offset = 0;
int numRead = 0;
while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length-offset)) >= 0) {
offset += numRead;
}
// Ensure all the bytes have been read in
if (offset < bytes.length) {
throw new IOException("Could not completely read file "+file.getName());
}
} finally {
// Close the input stream and return bytes
is.close();
}
return bytes;
}
public static File createCollisionFile(final File file) {
final File parentFile = file.getParentFile();
final String fileName = file.getName();
String fileExtension = nullToEmptyString(getFileExtension(fileName));
if (!fileExtension.isEmpty())
fileExtension = '.' + fileExtension;
final File result = createFile(parentFile,
String.format("%s.%s%s%s",
fileName,
Long.toString(System.currentTimeMillis(), 36) + new Uid(),
COLLISION_FILE_NAME_INFIX,
fileExtension));
return result;
}
private static String nullToEmptyString(final String s) {
return s == null ? "" : s;
}
public static void deleteOrFail(final File file) throws IOException {
file.delete();
if (file.isSymbolicLink() || file.exists())
throw new IOException("Could not delete file (it still exists after deletion): " + file);
}
public static int readOrFail(final InputStream in) throws IOException {
final int read = in.read();
if (read < 0)
throw new IOException("Premature end of stream!");
return read;
}
public static void readOrFail(final InputStream in, final byte[] buf, int off, int len) throws IOException {
AssertUtil.assertNotNull(buf, "buf");
if (off < 0)
throw new IllegalArgumentException("off < 0");
if (len == 0)
return;
if (len < 0)
throw new IllegalArgumentException("len < 0");
int bytesRead;
while ((bytesRead = in.read(buf, off, len)) >= 0) {
if (bytesRead > 0) {
off += bytesRead;
len -= bytesRead;
}
if (len < 0)
throw new IllegalStateException("len < 0");
if (len == 0)
return;
}
throw new IOException("Premature end of stream!");
}
public static byte[] longToBytes(final long value) {
final byte[] bytes = new byte[8];
for (int i = 0; i < bytes.length; ++i)
bytes[i] = (byte) (value >>> (8 * (bytes.length - 1 - i)));
return bytes;
}
public static long bytesToLong(final byte[] bytes) {
assertNotNull(bytes, "bytes");
if (bytes.length != 8)
throw new IllegalArgumentException("bytes.length != 8");
long value = 0;
for (int i = 0; i < bytes.length; ++i)
value |= ((long) (bytes[i] & 0xff)) << (8 * (bytes.length - 1 - i));
return value;
}
public static byte[] intToBytes(final int value) {
final byte[] bytes = new byte[4];
for (int i = 0; i < bytes.length; ++i)
bytes[i] = (byte) (value >>> (8 * (bytes.length - 1 - i)));
return bytes;
}
public static int bytesToInt(final byte[] bytes) {
assertNotNull(bytes, "bytes");
if (bytes.length != 4)
throw new IllegalArgumentException("bytes.length != 4");
int value = 0;
for (int i = 0; i < bytes.length; ++i)
value |= ((long) (bytes[i] & 0xff)) << (8 * (bytes.length - 1 - i));
return value;
}
}