All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.filesystem.FileSystemUtils Maven / Gradle / Ivy

There is a newer version: 2024.7.2
Show newest version
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed 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.conqat.lib.commons.filesystem;

import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Collections.reverseOrder;
import static java.util.stream.Collectors.toList;
import static org.conqat.lib.commons.date.DateTimeUtils.now;
import static org.conqat.lib.commons.string.StringUtils.bytesToString;
import static org.conqat.lib.commons.string.StringUtils.concat;
import static org.conqat.lib.commons.string.StringUtils.splitLinesAsList;
import static org.conqat.lib.commons.string.StringUtils.stripSuffix;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileFilter;
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.OutputStreamWriter;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.FileVisitOption;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.IllegalFormatException;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.Consumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.collections.CaseInsensitiveStringSet;
import org.conqat.lib.commons.collections.CollectionUtils;
import org.conqat.lib.commons.collections.PairList;
import org.conqat.lib.commons.concurrent.ThreadUtils;
import org.conqat.lib.commons.function.SupplierWithException;
import org.conqat.lib.commons.string.StringUtils;

/** File system utilities. */
public class FileSystemUtils {

	/** Encoding for UTF-8. */
	public static final String UTF8_ENCODING = StandardCharsets.UTF_8.name();

	/**
	 * The {@link Charset} used by the underlying operating system.
	 * 

* Starting with Java 18, this value may differ from {@link Charset#defaultCharset()}, which will * default to {@link StandardCharsets#UTF_8 UTF-8} instead of the OS charset. */ public static final Charset SYSTEM_CHARSET = Charset .forName(System.getProperty("native.encoding", Charset.defaultCharset().name())); /** The path to the directory used by Java to store temporary files */ public static final String TEMP_DIR_PATH = System.getProperty("java.io.tmpdir"); /** Unix file path separator */ public static final char UNIX_SEPARATOR = '/'; /** Windows file path separator */ private static final char WINDOWS_SEPARATOR = '\\'; /** * String containing the unit letters for units in the metric system (K for kilo, M for mega, ...). * Ordered by their power value (the order is important). */ private static final String METRIC_SYSTEM_UNITS = "KMGTPEZY"; /** * Pattern matching the start of the data-size unit in a data-size string (the first non-space char * not belonging to the numeric value). */ private static final Pattern DATA_SIZE_UNIT_START_PATTERN = Pattern.compile("[^\\d\\s.,]"); /** * Names of path segments that are reserved in certain operating systems and hence may not be used. * * @see Why * can't I create a folder with name ... */ private static final Set RESERVED_PATH_SEGMENT_NAMES = new CaseInsensitiveStringSet( Arrays.asList("CON", "PRN", "AUX", "CLOCK$", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9")); /** * Copy an input stream to an output stream. This does not close the streams. * * @param input * input stream * @param output * output stream * @return number of bytes copied * @throws IOException * if an IO exception occurs. */ public static int copy(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[1024]; int size = 0; int len; while ((len = input.read(buffer)) > 0) { output.write(buffer, 0, len); size += len; } return size; } /** * Copy a file. This creates all necessary directories. * * @deprecated Use {@link #copyFile(Path, Path)} instead. */ @Deprecated public static void copyFile(File sourceFile, File targetFile) throws IOException { copyFile(sourceFile.toPath(), targetFile.toPath()); } /** Copy a file. This creates all necessary directories. */ public static void copyFile(Path sourceFile, Path targetFile) throws IOException { if (sourceFile.toAbsolutePath().equals(targetFile.toAbsolutePath())) { throw new IOException("Can not copy file onto itself: " + sourceFile); } ensureParentDirectoryExists(targetFile); Files.copy(sourceFile, targetFile); } /** * Copy all files specified by a file filter from one directory to another. This automatically * creates all necessary directories. * * @param fileFilter * filter to specify file types. If all files should be copied, use * {@link FileOnlyFilter}. * @return number of files copied * * @deprecated Use {@link Files#walk} with {@link Stream#filter} and {@link #copyFile(Path, Path)} * instead. */ @Deprecated public static int copyFiles(File sourceDirectory, File targetDirectory, FileFilter fileFilter) throws IOException { List files = FileSystemUtils.listFilesRecursively(sourceDirectory, fileFilter); int fileCount = 0; for (File sourceFile : files) { if (sourceFile.isFile()) { String path = sourceFile.getAbsolutePath(); int index = sourceDirectory.getAbsolutePath().length(); String newPath = path.substring(index); File targetFile = new File(targetDirectory, newPath); copyFile(sourceFile, targetFile); fileCount++; } } return fileCount; } /** * Recursively delete directories and files. * * @param directory * Directory to delete. * @throws IllegalArgumentException * if {@code directory == null}. * * @deprecated Use {@link #deleteRecursively(Path)} instead. */ @Deprecated public static void deleteRecursively(File directory) throws IOException { checkArgument(directory != null, String.format("Parameter %s is null!", "directory")); deleteRecursively(directory.toPath()); } /** * Recursively delete directories and files. * * @param directory * Directory to delete. Does nothing if {@code directory == null}. * * @throws IOException * when a {@link Files#delete} operation fails. This will immediately abort deletion, * hence the operation might leave the partial contents of a directory behind if this * exception occurred. */ public static void deleteRecursively(@Nullable Path directory) throws IOException { if (directory == null) { return; } if (!Files.exists(directory)) { return; } try (Stream walk = Files.walk(directory)) { for (Iterator it = walk.sorted(reverseOrder()).iterator(); it.hasNext();) { Files.delete(it.next()); } } } /** * Deletes the given file and throws an exception if this fails. Does nothing if the given file * doesn't exist. * * @see Files#delete(Path) * * @deprecated Use {@link Files#delete(Path)} instead. */ @Deprecated public static void deleteFile(File file) throws IOException { if (!file.exists()) { return; } try { Files.delete(file.toPath()); } catch (IOException e) { throw new IOException("Could not delete " + file, e); } } /** * Renames the given file and throws an exception if this fails. * * @deprecated Use {@link Files#move(Path, Path, CopyOption...)} instead. */ @Deprecated public static void renameFileTo(File file, File dest) throws IOException { try { Files.move(file.toPath(), dest.toPath()); } catch (Exception e) { // Catching all exceptions here since we want to convert any exception to an // IOException. Listing all possible exceptions is too cumbersome and // error-prone. throw new IOException("Could not rename " + file + " to " + dest, e); } } /** * Creates a directory and throws an exception if this fails. * * @see File#mkdir() * * @deprecated Use {@link Files#createDirectory(Path, FileAttribute[])} instead. */ @Deprecated public static void mkdir(File dir) throws IOException { if (!dir.mkdir()) { throw new IOException("Could not create directory " + dir); } } /** * Creates a directory and all required parent directories. Throws an exception if this fails. * * @see File#mkdirs() * * @deprecated Use {@link Files#createDirectories(Path, FileAttribute[])} instead. */ @Deprecated public static void mkdirs(File dir) throws IOException { if (dir.exists() && dir.isDirectory()) { // mkdirs will return false if the directory already exists, but in // that case we don't want to throw an exception return; } if (!dir.mkdirs()) { throw new IOException("Could not create directory " + dir); } } /** * Checks if a directory exists and is writable. If not it creates the directory and all necessary * parent directories. * * @throws IOException * if directories couldn't be created. * * @deprecated Use {@link #ensureDirectoryExists(Path)} instead. */ @Deprecated public static void ensureDirectoryExists(File directory) throws IOException { ensureDirectoryExists(directory.toPath()); } /** * Checks if a directory exists and is writable. If not it creates the directory and all necessary * parent directories. * * @throws IOException * if directories couldn't be created. */ public static void ensureDirectoryExists(Path directory) throws IOException { if (!Files.exists(directory)) { Files.createDirectories(directory); } if (Files.exists(directory) && Files.isWritable(directory)) { return; } // Something is wrong. Either the directory does not exist yet, or it is not // writable (yet?). We had a case on a Windows OS where the directory was not // writable in a very small fraction of the calls. We assume this was because // the directory was not "ready" yet although createDirectories returned. Instant start = now(); while ((!Files.exists(directory) || !Files.isWritable(directory)) && start.until(now(), ChronoUnit.MILLIS) < 100) { ThreadUtils.sleep(10); } if (!Files.exists(directory)) { throw new IOException("Directory '" + directory + "' could not be created."); } if (!Files.isWritable(directory)) { throw new IOException("Directory '" + directory + "' exists, but is not writable."); } } /** * Checks if the parent directory of a file exists. If not it creates the directory and all * necessary parent directories. * * @throws IOException * if directories couldn't be created. * * @deprecated Use {@link #ensureParentDirectoryExists(Path)} instead. */ @Deprecated public static void ensureParentDirectoryExists(File file) throws IOException { ensureParentDirectoryExists(file.toPath()); } /** * Checks if the parent directory of a file exists. If not it creates the directory and all * necessary parent directories. * * @throws IOException * if directories couldn't be created. */ public static void ensureParentDirectoryExists(Path file) throws IOException { checkArgument(file != null, String.format("Parameter %s must not be null.", "file")); ensureDirectoryExists(file.toAbsolutePath().getParent()); } /** * Returns a list of all files and directories contained in the given directory and all * subdirectories. The given directory itself is not included in the result. *

* This method knows nothing about (symbolic and hard) links, so care should be taken when * traversing directories containing recursive links. * * @param directory * the directory to start the search from. * @return the list of files found (the order is determined by the file system). * * @deprecated Use {@link Files#walk(Path, FileVisitOption...)} instead. */ @Deprecated public static List listFilesRecursively(File directory) { return listFilesRecursively(directory, null); } /** * Returns a list of all files and directories contained in the given directory and all * subdirectories matching the filter provided. The given directory itself is not included in the * result. *

* The file filter may or may not exclude directories. *

* This method knows nothing about (symbolic and hard) links, so care should be taken when * traversing directories containing recursive links. * * @param directory * the directory to start the search from. If this is null or the directory does not * exist, an empty list is returned. * @param filter * the filter used to determine whether the result should be included. If the filter is * null, all files and directories are included. * @return the list of files found (the order is determined by the file system). Whether the paths * are relative or absolute is determined by the given directory path {@link File#listFiles} * * @deprecated Use {@link Files#walk(Path, FileVisitOption...)} with {@link Stream#filter} instead. */ @Deprecated public static List listFilesRecursively(File directory, FileFilter filter) { if (directory == null || !directory.isDirectory()) { return CollectionUtils.emptyList(); } List result = new ArrayList<>(); listFilesRecursively(directory, result, filter); return result; } /** * Finds all files and directories contained in the given directory and all subdirectories matching * the filter provided and put them into the result collection. The given directory itself is not * included in the result. *

* This method knows nothing about (symbolic and hard) links, so care should be taken when * traversing directories containing recursive links. * * @param directory * the directory to start the search from. * @param result * the collection to add to all files found. * @param filter * the filter used to determine whether the result should be included. If the filter is * null, all files and directories are included. */ private static void listFilesRecursively(File directory, Collection result, FileFilter filter) { File[] files = directory.listFiles(); if (files == null) { // From the docs of `listFiles`: "If this abstract pathname does not denote a // directory, then this method returns null." // It seems to be ok to just return here without throwing an exception. return; } for (File file : files) { if (file.isDirectory()) { listFilesRecursively(file, result, filter); } if (filter == null || filter.accept(file)) { result.add(file); } } } /** @see #listFilesInSameLocationForURL(URL, boolean) */ public static List listFilesInSameLocationForURL(URL baseUrl) throws IOException { return listFilesInSameLocationForURL(baseUrl, false); } /** * Lists the names of all simple files (i.e. no directories) next to an URL. For example for a file, * this would return the names of all files in the same directory (including the file itself). * Currently, this supports both file and jar URLs. The intended use-case is to list a set of files * in a package via the class loader. */ public static List listFilesInSameLocationForURL(URL baseUrl, boolean includeSubfolders) throws IOException { String protocol = baseUrl.getProtocol(); if ("file".equals(protocol)) { return listFilesForFileURL(baseUrl, includeSubfolders); } if ("jar".equals(protocol)) { return listFilesForJarURL(baseUrl, includeSubfolders); } throw new IOException("Unsupported protocol: " + protocol); } /** * Returns the parent path within the jar for a class file url. E.g. for the URL * "jar:file:/path/to/file.jar!/sub/folder/File.class" the method returns "sub/folder/". If the url * does already point to a directory it returns the path of this directory. */ private static String getJarUrlParentDirectoryPrefix(URL baseUrl) { // in JAR URLs we can rely on the separator being a slash String parentPath = StringUtils.getLastPart(baseUrl.getPath(), '!'); parentPath = StringUtils.stripPrefix(parentPath, "/"); if (parentPath.endsWith(".class")) { parentPath = stripSuffix(parentPath, StringUtils.getLastPart(parentPath, UNIX_SEPARATOR)); } else { parentPath = StringUtils.ensureEndsWith(parentPath, String.valueOf(UNIX_SEPARATOR)); } return parentPath; } /** * Lists the names of files for a JAR URL. If the recursive flag is set, all files within the same * jar directory hierarchy will be listed, otherwise only the ones in the same directory. */ private static List listFilesForJarURL(URL baseUrl, boolean recursive) throws IOException { try (JarFile jarFile = new JarFile(FileSystemUtils.extractJarFileFromJarURL(baseUrl))) { String parentPath = getJarUrlParentDirectoryPrefix(baseUrl); return jarFile.stream().filter(entry -> shouldBeContainedInResult(entry, parentPath, recursive)) .map(entry -> StringUtils.stripPrefix(entry.getName(), parentPath)).collect(toList()); } } /** * @return whether the jar entry should be returned when searching for files contained in the given * path. */ private static boolean shouldBeContainedInResult(JarEntry entry, String path, boolean recursive) { if (entry.isDirectory()) { return false; } String simpleName = StringUtils.getLastPart(entry.getName(), UNIX_SEPARATOR); String entryPath = stripSuffix(entry.getName(), simpleName); return !recursive && entryPath.equals(path) || (recursive && entryPath.startsWith(path)); } /** * Lists the names of files (not including directories) within the given file URL. This will only * include subfolder (recursively) if the includeSubfolders flag is set. * * @return a list of relative, separator-normalized file paths without slash prefix, e.g. * ['foo.java', 'subfolder/bar.java'] */ private static List listFilesForFileURL(URL baseUrl, boolean includeSubfolders) throws IOException { try { File directory = new File(baseUrl.toURI()); if (!directory.isDirectory()) { directory = directory.getParentFile(); } if (directory == null || !directory.isDirectory()) { throw new IOException("Parent directory does not exist or is not readable for " + baseUrl); } if (includeSubfolders) { File finalDirectory = directory; return CollectionUtils.filterAndMap(listFilesRecursively(directory), File::isFile, file -> { String relativeFilePath = StringUtils.stripPrefix(file.getAbsolutePath(), finalDirectory.getAbsolutePath()); relativeFilePath = FileSystemUtils.normalizeSeparators(relativeFilePath); return StringUtils.stripPrefix(relativeFilePath, String.valueOf(UNIX_SEPARATOR)); }); } File[] files = directory.listFiles(); if (files == null) { throw new IOException("Failed to list files for directory '" + directory.toPath() + "', even though it was supposed to be a valid directory."); } List names = new ArrayList<>(); for (File file : files) { if (file.isFile()) { names.add(file.getName()); } } return names; } catch (URISyntaxException e) { throw new IOException("Could not convert URL to valid file: " + baseUrl, e); } } /** * Extract all top-level classes in the given JAR and returns a list of their fully qualified class * names. Inner classes are ignored. */ public static List listTopLevelClassesInJarFile(File jarFile) throws IOException { List result = new ArrayList<>(); try (PathBasedContentProviderBase provider = PathBasedContentProviderBase.createProvider(jarFile)) { Collection paths = provider.getPaths(); for (String path : paths) { if (path.endsWith(ClassPathUtils.CLASS_FILE_SUFFIX) && !path.contains("$")) { String fqn = StringUtils.removeLastPart(path, '.'); fqn = fqn.replace(UNIX_SEPARATOR, '.'); result.add(fqn); } } return result; } } /** * Returns the extension of the file. * * @return File extension, i.e. "java" for "FileSystemUtils.java", or {@code null}, if the file has * no extension (i.e. if a filename contains no '.'), returns the empty string if the '.' is * the filename's last character. */ public static @Nullable String getFileExtension(Path file) { return getFileExtension(file.toString()); } /** * Returns the extension of the file. * * @return File extension, i.e. "java" for "FileSystemUtils.java", or {@code null}, if the file has * no extension (i.e. if a filename contains no '.'), returns the empty string if the '.' is * the filename's last character. * * @deprecated Use {@link #getFileExtension(Path)} instead. */ @Deprecated public static @Nullable String getFileExtension(File file) { return getFileExtension(file.getName()); } /** Returns the extension of the file at the given path. */ public static String getFileExtension(String path) { int posLastDot = path.lastIndexOf('.'); if (posLastDot < 0) { return null; } return path.substring(posLastDot + 1); } /** * @return the name of the given file without extension. Example: '/home/joe/data.dat' -> 'data'. * * @deprecated Use {@link #getFilenameWithoutExtension(Path)} instead. */ @Deprecated public static String getFilenameWithoutExtension(File file) { return getFilenameWithoutExtension(file.getName()); } /** * @return the name of the given file without extension. Example: '/home/joe/data.dat' -> 'data'. */ public static String getFilenameWithoutExtension(Path file) { return getFilenameWithoutExtension(file.getFileName().toString()); } /** * @return the name of the given file without extension. Example: 'data.dat' -> 'data'. */ public static String getFilenameWithoutExtension(String filename) { return StringUtils.removeLastPart(filename, '.'); } /** * @return the last path segment (i.e. file name or folder name) of a unix file path (assumes unix * separators). */ public static String getLastPathSegment(String filePath) { String[] split = getPathSegments(filePath); return split[split.length - 1]; } /** @return the segments of a path (assuming unix separators). */ public static String[] getPathSegments(String filePath) { return FileSystemUtils.normalizeSeparators(filePath).split(String.valueOf(UNIX_SEPARATOR)); } /** * @return whether a string is a valid path. It will return false when the path is invalid on the * current platform e.g. because of any non-allowed characters or because the path schema is * for Windows (D:\test) but runs under Linux. */ public static boolean isValidPath(String path) { try { Paths.get(path); } catch (InvalidPathException ex) { return false; } // Split at the default platform separators and check whether there remain any // separator characters in the path segments return Arrays.stream(path.split(Pattern.quote(File.separator))) .noneMatch(pathSegment -> pathSegment.contains(String.valueOf(FileSystemUtils.WINDOWS_SEPARATOR)) || pathSegment.contains(String.valueOf(FileSystemUtils.UNIX_SEPARATOR))); } /** * Checks whether the given folder can be written to, based on {@link Files#isWritable(Path)}. If * the based File object is a directory, the check will be performed on the object itself. If a file * is passed instead, its first parent that is a directory will be used for the * {@link Files#isWritable} check. */ // Inverting this method would make it less intuitive to read. @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isPathWriteable(Path file) { Path folderToCheck = file; if (!Files.isDirectory(file)) { folderToCheck = file.toAbsolutePath().getParent(); } while (folderToCheck != null && !Files.isDirectory(folderToCheck)) { folderToCheck = folderToCheck.getParent(); } return folderToCheck != null && Files.isWritable(folderToCheck); } /** * Read file content into a string using the default encoding for the platform. If the file starts * with a UTF byte order mark (BOM), the encoding is ignored and the correct encoding based on this * BOM is used for reading the file. * * @see EByteOrderMark */ public static String readFile(Path file) throws IOException { return readFile(file, Charset.defaultCharset()); } /** * Read file content into a string using the default encoding for the platform. If the file starts * with a UTF byte order mark (BOM), the encoding is ignored and the correct encoding based on this * BOM is used for reading the file. * * @see EByteOrderMark * * @deprecated Use {@link #readFile(Path)} instead. */ @Deprecated public static String readFile(File file) throws IOException { return readFile(file, Charset.defaultCharset()); } /** * Read file content into a string using UTF-8 encoding. If the file starts with a UTF byte order * mark (BOM), the encoding is ignored and the correct encoding based on this BOM is used for * reading the file. * * @see EByteOrderMark */ public static String readFileUTF8(Path file) throws IOException { return readFile(file, StandardCharsets.UTF_8); } /** * Read file content into a string using UTF-8 encoding. If the file starts with a UTF byte order * mark (BOM), the encoding is ignored and the correct encoding based on this BOM is used for * reading the file. * * @see EByteOrderMark * * @deprecated Use {@link #readFileUTF8(Path)} instead. */ @Deprecated public static String readFileUTF8(File file) throws IOException { return readFile(file, StandardCharsets.UTF_8); } /** * Read file content into a string using the given encoding. If the file starts with a UTF byte * order mark (BOM), the encoding is ignored and the correct encoding based on this BOM is used for * reading the file. * * @see EByteOrderMark * * @deprecated Use {@link #readFile(Path)} instead. */ @Deprecated public static String readFile(File file, Charset encoding) throws IOException { byte[] buffer = readFileBinary(file); return bytesToString(buffer, encoding); } /** * Read file content into a string using the given encoding. If the file starts with a UTF byte * order mark (BOM), the encoding is ignored and the correct encoding based on this BOM is used for * reading the file. * * @see EByteOrderMark */ public static String readFile(Path file, Charset encoding) throws IOException { byte[] buffer = Files.readAllBytes(file); return bytesToString(buffer, encoding); } /** * Read file content into a list of lines (strings) using the given encoding. This uses automatic * BOM handling, just as {@link #readFile(Path)}. */ public static List readLines(Path file, Charset encoding) throws IOException { return splitLinesAsList(readFile(file, encoding)); } /** * Read file content into a list of lines (strings) using the given encoding. This uses automatic * BOM handling, just as {@link #readFile(Path)}. * * @deprecated Use {@link #readLines(Path, Charset)} instead. */ @Deprecated public static List readLines(File file, Charset encoding) throws IOException { return readLines(file.toPath(), encoding); } /** * Read file content into a list of lines (strings) using UTF-8 encoding. This uses automatic BOM * handling, just as {@link #readFile(Path)}. */ public static List readLinesUTF8(Path file) throws IOException { return readLines(file, StandardCharsets.UTF_8); } /** * Read file content into a list of lines (strings) using UTF-8 encoding. This uses automatic BOM * handling, just as {@link #readFile(File)}. * * @deprecated Use {@link #readLinesUTF8(Path)} instead. */ @Deprecated public static List readLinesUTF8(File file) throws IOException { return readLinesUTF8(file.toPath()); } /** Read file content into a byte array. */ public static byte[] readFileBinary(String filePath) throws IOException { return Files.readAllBytes(Paths.get(filePath)); } /** * Read file content into a byte array. * * @deprecated Use {@link Files#readAllBytes(Path)} or {@link #readFileBinary(String)} instead. */ @Deprecated public static byte[] readFileBinary(File file) throws IOException { FileInputStream in = new FileInputStream(file); byte[] buffer = new byte[(int) file.length()]; ByteBuffer byteBuffer = ByteBuffer.wrap(buffer); FileChannel channel = in.getChannel(); try { int readSum = 0; while (readSum < buffer.length) { int read = channel.read(byteBuffer); if (read < 0) { throw new IOException("Reached EOF before entire file could be read!"); } readSum += read; } } finally { close(channel); close(in); } return buffer; } /** Extract a JAR file to a directory. */ public static void unjar(File jarFile, File targetDirectory) throws IOException { unzip(jarFile, targetDirectory); } /** Extract a ZIP file to a directory. */ public static void unzip(File zipFile, File targetDirectory) throws IOException { unzip(zipFile, targetDirectory, null, null); } /** * Extract the entries of ZIP file to a directory. * * @param prefix * Sets a prefix for the entry names (paths) which should be extracted. Only entries * which start with the given prefix are extracted. If prefix is null or * empty all entries are extracted. The prefix will be stripped form the extracted * entries. * @param charset * defines the {@link Charset} of the ZIP file. If null, the standard of * {@link ZipFile} is used (which is UTF-8). * @return list of the extracted paths */ public static List unzip(File zipFile, File targetDirectory, String prefix, Charset charset) throws IOException { ZipFile zip = null; try { if (charset == null) { zip = new ZipFile(zipFile); } else { zip = new ZipFile(zipFile, charset); } return unzip(zip, targetDirectory, prefix); } finally { close(zip); } } /** * Extract entries of a ZipFile to a directory when the ZipFile is created externally. Note that * this does not close the ZipFile, so the caller has to take care of this. */ public static List unzip(ZipFile zip, File targetDirectory, String prefix) throws IOException { Enumeration entries = zip.getEntries(); List extractedPaths = new ArrayList<>(); while (entries.hasMoreElements()) { ZipArchiveEntry entry = entries.nextElement(); if (entry.isDirectory()) { continue; } String filename = entry.getName(); if (!StringUtils.isEmpty(prefix)) { if (!filename.startsWith(prefix)) { continue; } filename = StringUtils.stripPrefix(filename, prefix); } try (InputStream entryStream = zip.getInputStream(entry)) { File file = new File(targetDirectory, filename); ensureParentDirectoryExists(file); try (FileOutputStream outputStream = new FileOutputStream(file)) { copy(entryStream, outputStream); } } extractedPaths.add(filename); } return extractedPaths; } /** * Extract entries of a zip file input stream to a directory. The input stream is automatically * closed by this method. */ public static List unzip(InputStream inputStream, File targetDirectory) throws IOException { List extractedPaths = new ArrayList<>(); try (ZipInputStream zipStream = new ZipInputStream(inputStream)) { while (true) { ZipEntry entry = zipStream.getNextEntry(); if (entry == null) { break; } else if (entry.isDirectory()) { continue; } String filename = entry.getName(); File file = new File(targetDirectory, filename); ensureParentDirectoryExists(file); try (OutputStream targetStream = Files.newOutputStream(file.toPath())) { copy(zipStream, targetStream); } extractedPaths.add(filename); } } return extractedPaths; } /** * Write string to a file with the default encoding. This ensures all directories exist. */ public static void writeFile(Path file, String content) throws IOException { writeFile(file, content, Charset.defaultCharset().name()); } /** * Write string to a file with the default encoding. This ensures all directories exist. * * @deprecated Use {@link #writeFile(Path, String)} instead. */ @Deprecated public static void writeFile(File file, String content) throws IOException { writeFile(file, content, Charset.defaultCharset().name()); } /** * Writes the given collection of String as lines into the specified file. This method uses \n as a * line separator. */ public static void writeLines(Path file, Collection lines) throws IOException { writeFile(file, concat(lines, "\n")); } /** * Writes the given collection of String as lines into the specified file. This method uses \n as a * line separator. * * @deprecated Use {@link #writeLines(Path, Collection)} instead. */ @Deprecated public static void writeLines(File file, Collection lines) throws IOException { writeLines(file.toPath(), lines); } /** * Write string to a file with UTF8 encoding. This ensures all directories exist. * * @deprecated Use {@link #writeFileUTF8(Path, String)} instead. */ @Deprecated public static void writeFileUTF8(File file, String content) throws IOException { writeFile(file, content, UTF8_ENCODING); } /** * Write string to a file with UTF8 encoding. This ensures all directories exist. */ public static void writeFileUTF8(Path file, String content) throws IOException { writeFile(file, content, UTF8_ENCODING); } /** Write string to a file. This ensures all directories exist. */ public static void writeFile(Path file, String content, String encoding) throws IOException { ensureParentDirectoryExists(file); try (OutputStreamWriter writer = new OutputStreamWriter(Files.newOutputStream(file), encoding)) { writer.write(content); } } /** * Write string to a file. This ensures all directories exist. * * @deprecated Use {@link #writeFile(Path, String, String)} instead. */ @Deprecated public static void writeFile(File file, String content, String encoding) throws IOException { writeFile(file.toPath(), content, encoding); } /** * Writes the given bytes to the given file. Directories are created as needed. The file is closed * after writing. */ public static void writeFileBinary(Path file, byte[] bytes) throws IOException { ensureParentDirectoryExists(file); Files.write(file, bytes); } /** * Writes the given bytes to the given file. Directories are created as needed. The file is closed * after writing. * * @deprecated Use {@link #writeFileBinary(Path, byte[])} instead. */ @Deprecated public static void writeFileBinary(File file, byte[] bytes) throws IOException { writeFileBinary(file.toPath(), bytes); } /** * Loads template file with a * Format * string, formats it and writes result to file. * * @param templateFile * the template file with the format string * @param outFile * the target file, parent directories are created automatically. * @param arguments * the formatting arguments. * @throws IOException * if an IO exception occurs or the template file defines an illegal format. */ public static void mergeTemplate(File templateFile, File outFile, Object... arguments) throws IOException { String template = readFile(templateFile); String output; try { output = String.format(template, arguments); } catch (IllegalFormatException e) { throw new IOException("Illegal format: " + e.getMessage(), e); } writeFile(outFile, output); } /** * Loads template file with a * Format * string, formats it and provides result as stream. No streams are closed by this method. * * @param inStream * stream that provides the template format string * @param arguments * the formatting arguments. * @throws IOException * if an IOException occurs or the template file defines an illegal format. */ public static InputStream mergeTemplate(InputStream inStream, Object... arguments) throws IOException { String template = readStream(inStream); String output; try { output = String.format(template, arguments); } catch (IllegalFormatException e) { throw new IOException("Illegal format: " + e.getMessage(), e); } return new ByteArrayInputStream(output.getBytes()); } /** Read input stream into string. */ public static String readStream(InputStream input) throws IOException { return readStream(input, Charset.defaultCharset()); } /** Read input stream into string. */ public static String readStreamUTF8(InputStream input) throws IOException { return readStream(input, StandardCharsets.UTF_8); } /** * Read input stream into string. This method is BOM aware, i.e. deals with the UTF-BOM. */ public static String readStream(InputStream input, Charset encoding) throws IOException { StringBuilder out = new StringBuilder(); Reader r = streamReader(input, encoding); char[] b = new char[4096]; int n; while ((n = r.read(b)) != -1) { out.append(b, 0, n); } return out.toString(); } /** Read input stream into raw byte array. */ public static byte[] readStreamBinary(InputStream input) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); copy(input, out); return out.toByteArray(); } /** * Returns a reader that wraps the given input stream. This method handles the BOM transparently. As * the normal reader constructors can not deal with this, direct construction of readers is * discouraged. */ public static Reader streamReader(InputStream in, Charset encoding) throws IOException { // we need marking to read the BOM mark if (!in.markSupported()) { in = new BufferedInputStream(in); } in.mark(EByteOrderMark.MAX_BOM_LENGTH); byte[] prefix = new byte[EByteOrderMark.MAX_BOM_LENGTH]; EByteOrderMark bom = null; try { safeRead(in, prefix); bom = EByteOrderMark.determineBOM(prefix).orElse(null); } catch (IOException e) { // just use provided encoding; keep BOM as null } in.reset(); if (bom != null) { encoding = bom.getEncoding(); // consume BOM for (int i = 0; i < bom.getBOMLength(); ++i) { int ignored = in.read(); } } return new InputStreamReader(in, encoding); } /** Reads properties from a properties file. */ public static Properties readProperties(File propertiesFile) throws IOException { return readProperties(() -> Files.newInputStream(propertiesFile.toPath())); } /** Reads properties from a properties stream. */ private static Properties readProperties(SupplierWithException streamSupplier) throws IOException { try (InputStream stream = streamSupplier.get()) { Properties props = new Properties(); props.load(stream); return props; } } /** * Determines the root directory from a collection of files. The root directory is the lowest common * ancestor directory of the files in the directory tree. *

* This method does not require the input files to exist. * * @param files * Collection of files for which root directory gets determined. This collection is * required to contain at least 2 files. If it does not, an AssertionError is thrown. * @return Root directory, or null, if the files do not have a common root directory. * @throws AssertionError * If less than two different files are provided whereas fully qualified canonical names * are used for comparison. * @throws IOException * Since canonical paths are used for determination of the common root, and * {@link File#getCanonicalPath()} can throw {@link IOException}s. */ public static File commonRoot(Iterable files) throws IOException { // determine longest common prefix on canonical absolute paths Set absolutePaths = new HashSet<>(); for (File file : files) { absolutePaths.add(file.getCanonicalPath()); } CCSMAssert.isTrue(absolutePaths.size() >= 2, "Expected are at least 2 files"); String longestCommonPrefix = StringUtils.longestCommonPrefix(absolutePaths); // trim to name of root directory (remove possible equal filename // prefixes.) int lastSeparator = longestCommonPrefix.lastIndexOf(File.separator); if (lastSeparator > -1) { longestCommonPrefix = longestCommonPrefix.substring(0, lastSeparator); } if (StringUtils.isEmpty(longestCommonPrefix)) { return null; } return new File(longestCommonPrefix); } /** * Transparently creates a stream for decompression if the provided stream is compressed. Otherwise, * the stream is just handed through. Currently, only GZIP via {@link GZIPInputStream} is supported. */ public static InputStream autoDecompressStream(InputStream in) throws IOException { if (!in.markSupported()) { in = new BufferedInputStream(in); } in.mark(2); // check first two bytes for GZIP header boolean isGZIP = (in.read() & 0xff | (in.read() & 0xff) << 8) == GZIPInputStream.GZIP_MAGIC; in.reset(); if (isGZIP) { return new GZIPInputStream(in); } return in; } /** * Closes the given ZIP file quietly, i.e. ignoring a potential IOException. Additionally, it is * {@code null} safe. */ public static void close(ZipFile zipFile) { if (zipFile == null) { return; } try { zipFile.close(); } catch (IOException e) { // ignore } } /** * This method can be used to simplify the typical {@code finally}-block of code dealing with * streams and readers/writers. It checks if the provided closeable is {@code null}. If not it * closes it. If an exception is thrown during the close operation it will be ignored. */ public static void close(Closeable closeable) { if (closeable == null) { return; } try { closeable.close(); } catch (IOException e) { // ignore } } /** Compares files based on the lexical order of their fully qualified names. */ public static void sort(List files) { files.sort(new FilenameComparator()); } /** * Replace platform dependent separator char (of the operating system running the Teamscale * instance) with forward slashes to create system-independent paths. *

* Be careful when using this method on strings coming from project source code. This will be wrong * if the source code contains (for example) windows paths like {@code "..\foo.h"} and the Teamscale * instance runs on a unix machine. */ public static String normalizeSeparators(String path) { return path.replace(File.separatorChar, UNIX_SEPARATOR); } /** * @return a path normalized by replacing all occurrences of Windows back-slash separators (if * present) with unix forward-slash separators. This is in contrast to * {@link #normalizeSeparators(String)} that replaces all platform-dependent separators. */ public static String normalizeSeparatorsPlatformIndependently(String path) { if (path.contains(String.valueOf(WINDOWS_SEPARATOR)) && !path.contains(String.valueOf(UNIX_SEPARATOR))) { return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); } return path; } /** * Returns the JAR file for a URL with protocol 'jar'. If the protocol is not 'jar' an assertion * error will be caused! An assertion error is also thrown if URL does not point to a file. */ public static File extractJarFileFromJarURL(URL url) { CCSMAssert.isTrue("jar".equals(url.getProtocol()), "May only be used with 'jar' URLs!"); String path = url.getPath(); CCSMAssert.isTrue(path.startsWith("file:"), "May only be used for URLs pointing to files"); // the exclamation mark is the separator between jar file and path // within the file int index = path.indexOf('!'); CCSMAssert.isTrue(index >= 0, "Unknown format for jar URLs"); path = path.substring(0, index); return fromURL(path); } /** * Often file URLs are created the wrong way, i.e. without proper escaping characters invalid in * URLs. Unfortunately, the URL class allows this and the Eclipse framework does it. See * How to * convert java.net.url to java.io.file for details. *

* This method attempts to fix this problem and create a file from it. * * @throws AssertionError * if cleaning up fails. * * @implNote We cannot simply encode the URL this also encodes slashes and other stuff. As a result, * the file constructor throws an exception. As a simple heuristic, we only fix the * spaces. The other route to go would be manually stripping of "file:" and simply * creating a file. However, this does not work if the URL was created properly and * contains URL escapes. */ private static File fromURL(String url) { url = url.replace(StringUtils.SPACE, "%20"); try { return new File(new URI(url)); } catch (URISyntaxException e) { throw new AssertionError("The assumption is that this method is capable of " + "working with non-standard-compliant URLs, too. " + "Apparently it is not. Invalid URL: " + url + ". Ex: " + e.getMessage(), e); } } /** * Returns whether a filename represents an absolute path. *

* This method returns the same result, independent on which operating system it gets executed. In * contrast, the behavior of {@link File#isAbsolute()} is operating system specific. */ public static boolean isAbsolutePath(String filename) { // Unix and macOS: absolute path starts with slash or user home if (filename.startsWith("/") || filename.startsWith("~")) { return true; } // Windows and OS/2: absolute path start with letter and colon if (filename.length() > 2 && Character.isLetter(filename.charAt(0)) && filename.charAt(1) == ':') { return true; } // UNC paths (aka network shares): start with double backslash return filename.startsWith("\\\\"); } /** * Reads bytes of data from the input stream into an array of bytes until the array is full. This * method blocks until input data is available, end of file is detected, or an exception is thrown. *

* The reason for this method is that {@link InputStream#read(byte[])} may read less than the * requested number of bytes, while this method ensures the data is complete. * * @param in * the stream to read from. * @param data * the stream to read from. * @throws IOException * if reading the underlying stream causes an exception. * @throws EOFException * if the end of file was reached before the requested data was read. */ public static void safeRead(InputStream in, byte[] data) throws IOException { int offset = 0; int length = data.length; while (length > 0) { int read = in.read(data, offset, length); if (read < 0) { throw new EOFException("Reached end of file before completing read."); } offset += read; length -= read; } } /** Obtains the system's temporary directory */ public static File getTmpDir() { return new File(TEMP_DIR_PATH); } /** Obtains the current user's home directory */ public static File getUserHomeDir() { return new File(System.getProperty("user.home")); } /** * Obtains the current working directory. This is usually the directory in which the current Java * process was started. In dev-mode (including unit tests), the temp directory is used. */ public static File getJvmWorkingDirOrTempForDevMode() { if (isDevModeOrJunitTest()) { return getTmpDir(); } return new File(System.getProperty("user.dir")); } /** Returns if dev-mode is active or a JUnit test is executed. */ public static boolean isDevModeOrJunitTest() { return Boolean.getBoolean("com.teamscale.dev-mode") || isJUnitTest(); } private static boolean isJUnitTest() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); for (StackTraceElement element : stackTrace) { if (element.getClassName().startsWith("org.junit.")) { return true; } } return false; } /** * @return whether the given {@code files} are non-null, plain files, and readable. */ // Inverting this method would make it less intuitive to read. @SuppressWarnings("BooleanMethodIsAlwaysInverted") public static boolean isReadableFile(File... files) { return Arrays.stream(files) .noneMatch(file -> file == null || !file.exists() || !file.isFile() || !file.canRead()); } /** * Concatenates all path parts into a single path with normalized separators. */ public static String concatenatePaths(String firstParent, String... paths) { return normalizeSeparators(Paths.get(firstParent, paths).toString()); } /** * Removes the given path if it is an empty directory and recursively parent directories if the only * child was deleted. *

* If the given path points to file which does not exist, the parent directory of that file is * deleted. * * @throws IOException * if an I/O error during deletion occurs. */ public static void recursivelyRemoveDirectoryIfEmpty(File path) throws IOException { String[] children = path.list(); if (children == null) { // path either points to a plain file or to a non-existent // path. In the first case, nothing should be done otherwise // deletion should continue with the parent path. if (path.exists()) { return; } } else if (children.length == 0) { deleteFile(path); } else { return; } recursivelyRemoveDirectoryIfEmpty(path.getParentFile()); } /** * Converts the given human-readable data size to the corresponding number of bytes. For example "1 * KB" is converted to 1024. Also supports Si units ("1 KiB" is converted to 1000). *

* Commas are ignored and can be used as thousands separator. A dot is the decimal separator. * ("1.2KiB" is converted to 1200). *

* Method implementation based on this stackoverflow * answer */ public static long parseDataSize(String dataSize) { String dataSizeWithoutComma = dataSize.replaceAll(",", ""); int unitBeginIndex = StringUtils.indexOfMatch(dataSizeWithoutComma, DATA_SIZE_UNIT_START_PATTERN); if (unitBeginIndex == -1) { return Long.parseLong(dataSizeWithoutComma); } double rawDataSize = Double.parseDouble(dataSizeWithoutComma.substring(0, unitBeginIndex)); String unitString = dataSizeWithoutComma.substring(unitBeginIndex); int unitChar = unitString.charAt(0); int power = METRIC_SYSTEM_UNITS.indexOf(unitChar) + 1; boolean isSi = unitString.length() >= 2 && unitString.charAt(1) == 'i'; int factor = 1024; if (isSi) { factor = 1000; if (stripSuffix(unitString, "B").length() != 2) { throw new NumberFormatException("Malformed data size: " + dataSizeWithoutComma); } } else if (power == 0) { if (!stripSuffix(unitString, "B").isEmpty()) { throw new NumberFormatException("Malformed data size: " + dataSizeWithoutComma); } } else { if (stripSuffix(unitString, "B").length() != 1) { throw new NumberFormatException("Malformed data size: " + dataSizeWithoutComma); } } return (long) (rawDataSize * Math.pow(factor, power)); } /** Determines the last modified timestamp in a platform-agnostic way. */ public static long getLastModifiedTimestamp(File file) throws IOException { return Files.getLastModifiedTime(Paths.get(file.toURI())).toMillis(); } /** * Returns a safe filename that can be used for downloads. Replaces everything that is not a letter * or number with "-". *

* Attention: This replaces dots, including the file-end-separator. * {@code toSafeFilename("a.c")=="a-c"} */ public static String toSafeFilename(String name) { name = name.replaceAll("\\W+", "-"); name = name.replaceAll("[-_]+", "-"); return name; } /** * Returns a filename that replaces the Windows-specific illegal characters with "-". Less strict * than {@link #toSafeFilename} as the goal is to ensure that it is possible to create description * files for checks with illegal characters in their names. */ public static String toValidFileName(String name) { return name.replaceAll("[:\\\\/*\"?|<>']", "-"); } /** * @return a new file with all file path segments that are reserved (windows) path names escaped. */ public static File escapeReservedFileNames(File file) { String[] parts = file.getPath().split(Pattern.quote(File.separator)); for (int i = 0; i < parts.length; ++i) { if (RESERVED_PATH_SEGMENT_NAMES.contains(parts[i])) { parts[i] = "_" + parts[i]; } } return new File(concat(parts, File.separator)); } /** * Reads a file using UTF-8 encoding and normalizes line breaks (replacing "\n\r" and "\r" with * "\n"). This generates an OS-independent view on a file. To ensure OS-independent test results, * this method should be used in all tests to read files. */ public static String readFileSystemIndependent(File file) throws IOException { return StringUtils.normalizeLineSeparatorsPlatformIndependent(readFileUTF8(file)); } /** * Replaces the file name of the given path with the given new extension. Returns the newFileName if * the file denoted by the uniform path does not contain a '/'. This method assumes that folders are * separated by '/' (uniform paths). *

* Examples: *

    *
  • {@code replaceFilePathFilenameWith("xx", "yy")} returns {@code "yy"}
  • *
  • {@code replaceFilePathFilenameWith("xx/zz", "yy")} returns {@code "xx/yy"}
  • *
  • {@code replaceFilePathFilenameWith("xx/zz/", "yy")} returns {@code "xx/zz/yy"}
  • *
  • {@code replaceFilePathFilenameWith("", "yy")} returns {@code "yy"}
  • *
*/ public static String replaceFilePathFilenameWith(String uniformPath, String newFileName) { int folderSepIndex = uniformPath.lastIndexOf('/'); if (uniformPath.endsWith("/")) { return uniformPath + newFileName; } else if (folderSepIndex == -1) { return newFileName; } return uniformPath.substring(0, folderSepIndex) + "/" + newFileName; } /** * Calculates the directory size of the provided folder (in bytes). *

* The size is calculated by traversing each file in the directory and summing their size. Because * of this, the result may be incorrect when the directory is modified concurrently (e.g. when files * are deleted or moved). * * @return Computed directory size in bytes. * * @see #calculateDirectorySize(Path, Consumer) */ public static long calculateDirectorySize(@NonNull Path folder) { return calculateDirectorySize(folder, null); } /** * Calculates the directory size of the provided folder (in bytes). *

* The size is calculated by traversing each file in the directory and summing their size. Because * of this, the result may be incorrect when the directory is modified concurrently (e.g. when files * are deleted or moved). *

* Any file, for which the size could not be determined (e.g. because it was deleted mid-traversal) * will be provided to the {@code failedFilesConsumer} together with the corresponding * {@link IOException}. * * @return Computed directory size in bytes. * * @see #calculateDirectorySize(Path) */ public static long calculateDirectorySize(@NonNull Path folder, @Nullable Consumer> failedFilesConsumer) { CCSMAssert.isNotNull(folder, () -> String.format("Expected \"%s\" to be not null", "folder")); if (!Files.isDirectory(folder)) { throw new IllegalArgumentException( String.format("Provided file \"%s\" is not a directory", folder.toAbsolutePath())); } LongAdder directorySize = new LongAdder(); NonThrowingFileVisitor visitor = new NonThrowingFileVisitor<>(file -> { BasicFileAttributes fileAttributes = Files.readAttributes(file, BasicFileAttributes.class); if (fileAttributes.isRegularFile()) { directorySize.add(fileAttributes.size()); } }); try { Files.walkFileTree(folder, visitor); } catch (IOException e) { // Should never happen as we are using the NonThrowingFileVisitor CCSMAssert.fail(String.format("Unexpected IOException occurred while calculating directory size of: %s", folder.toAbsolutePath()), e); } if (failedFilesConsumer != null && !visitor.getFailedFiles().isEmpty()) { failedFilesConsumer.accept(visitor.getFailedFiles()); } return directorySize.longValue(); } /** * Checks whether a file name belongs to a file/folder that may have been creating when * (re-)zipping. */ public static boolean isSystemFileName(String entryName) { return entryName.startsWith("__MACOSX") || entryName.endsWith(".DS_Store") || entryName.endsWith("~"); } /** * Creates a {@link TemporaryDirectory} with the given {@code prefix}. * * @see Files#createTempDirectory(String, FileAttribute[]) * @see Runtime#addShutdownHook(Thread) */ public static TemporaryDirectory getTemporaryDirectory(String prefix) throws IOException { return new TemporaryDirectory(Files.createTempDirectory(prefix), false); } /** * Creates a {@link TemporaryDirectory} which will not be deleted on close, but on shutdown. * * @see Files#createTempDirectory(String, FileAttribute[]) * @see Runtime#addShutdownHook(Thread) */ public static TemporaryDirectory getTemporaryDirectoryDeletedOnShutdown(String prefix) throws IOException { return new TemporaryDirectory(Files.createTempDirectory(prefix), true); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy