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

aQute.lib.io.IO Maven / Gradle / Ivy

The newest version!
package aQute.lib.io;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.regex.Matcher.quoteReplacement;
import static java.util.stream.Collectors.toList;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.Closeable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.UTFDataFormatException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.text.CollationKey;
import java.text.Collator;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.TreeSet;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import aQute.bnd.exceptions.ConsumerWithException;
import aQute.lib.stringrover.StringRover;
import aQute.libg.glob.Glob;

public class IO {
	final static EnvironmentCalculator					hc						= new EnvironmentCalculator();
	private static final Pattern						WINDOWS_MACROS			= Pattern.compile("%(?[^%]+)%");
	private static final int							BUFFER_SIZE				= IOConstants.PAGE_SIZE * 16;
	private static final int							DIRECT_MAP_THRESHOLD	= BUFFER_SIZE;
	static final public File							work					= new File(
		System.getProperty("user.dir"));
	static final public File							home;
	static final public File							JAVA_HOME;
	private static final EnumSet	writeOptions			= EnumSet.of(StandardOpenOption.WRITE,
		StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
	private static final EnumSet	readOptions				= EnumSet.of(StandardOpenOption.READ);
	final static Path									DOTDOT					= Path.of("..");

	interface OS {
		/**
		 * Resolve the subPath from the absolute file base. The subPath must not
		 * resolve to a path that is not in the subtree of files from base. it
		 * may contain .. as long as this does not end up at the start when
		 * normalized. It (surprisingly) can handle absolute files since `new
		 * File(base,"/foo") resolves not to "/foo" ... I.e. except for the ..,
		 * it can never escape base
		 *
		 * @param base the absolute base directory
		 * @param subPath the sub path
		 * @return the absolute file resolved from base with relativePath
		 * @throws IOException
		 */

		File getBasedFile(File base, String subPath) throws IOException;

		/**
		 * Get an environment variable. Windows is special ...
		 *
		 * @param variableName the variable name
		 * @return the env variable or null
		 */
		String getenv(String variableName);

		/**
		 * Convert to a safe name on the current platform. This is the _name_
		 * part only, not a path!
		 *
		 * @param name the file name
		 * @return a safe file name
		 */
		String toSafeFileName(String name);

		/**
		 * Return a file from a base. The file must use forward slash but may
		 * start on windows with c:/... or /c:/ to indicate a drive.
		 *
		 * @param base the base to resolve the file from
		 * @param file the path
		 * @return a file
		 */
		File getFile(File base, String file);
	}

	final static OS os = File.separatorChar == '\\' ? new Windows() : new Other();

	static {
		home = hc.getDirLocation(os.getenv("HOME"), System.getProperty("user.home"));
		String javaHome = System.getProperty("java.home")
			.replaceAll("(/|\\\\)jre$", "");
		JAVA_HOME = hc.getDirLocation(os.getenv("JAVA_HOME"), javaHome);
	}

	public static String getExtension(String fileName, String deflt) {
		int n = fileName.lastIndexOf('.');
		if (n < 0)
			return deflt;

		return fileName.substring(n + 1);
	}

	public static Collection tree(File current) {
		Set files = new LinkedHashSet<>();
		traverse(files, current, null);
		return files;
	}

	public static Collection tree(File current, String glob) {
		Set files = new LinkedHashSet<>();
		traverse(files, current, glob == null ? null : new Glob(glob));
		return files;
	}

	private static void traverse(Collection files, File current, Glob glob) {
		if (current.isFile() && (glob == null || glob.matcher(current.getName())
			.matches())) {
			files.add(current);
		} else if (current.isDirectory()) {
			for (File sub : listFiles(current)) {
				traverse(files, sub, glob);
			}
		}
	}

	public static Collator fileCollator() {
		Collator collator = Collator.getInstance(Locale.ROOT);
		collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
		collator.setStrength(Collator.IDENTICAL); // case-sensitive
		return collator;
	}

	public static  Comparator fileComparator(Function keyExtractor) {
		return Comparator.comparing(keyExtractor, fileCollator()::compare);
	}

	public static Stream listStream(File dir, BiPredicate filter) {
		if ((dir != null) && dir.isDirectory()) {
			String[] names = dir.list();
			if (names != null) {
				Stream result = StreamSupport
					.stream(Spliterators. spliterator(names,
						Spliterator.ORDERED | Spliterator.IMMUTABLE | Spliterator.DISTINCT), false)
					.filter(name -> filter.test(dir, name))
					.map(fileCollator()::getCollationKey)
					.sorted()
					.map(CollationKey::getSourceString);
				return result;
			}
		}
		return Stream.empty();
	}

	public static Stream listStream(File dir) {
		return listStream(dir, (d, n) -> true);
	}

	public static List list(File dir, BiPredicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.collect(toList());
		}
	}

	public static List list(File dir) {
		return list(dir, (d, n) -> true);
	}

	public static List listFiles(File dir, BiPredicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.map(name -> new File(dir, name))
				.collect(toList());
		}
	}

	public static List listFiles(File dir) {
		return listFiles(dir, (d, n) -> true);
	}

	public static Stream listStream(Path dir, Predicate filter) {
		if ((dir != null) && Files.isDirectory(dir)) {
			try {
				Stream result = Files.list(dir)
					.filter(filter)
					.map(Path::getFileName)
					.map(Path::toString)
					.map(fileCollator()::getCollationKey)
					.sorted()
					.map(CollationKey::getSourceString);
				return result;
			} catch (IOException e) {
				throw new UncheckedIOException(e);
			}
		}
		return Stream.empty();
	}

	public static Stream listStream(Path dir, BiPredicate filter) {
		if ((dir != null) && Files.isDirectory(dir)) {
			try {
				Stream result = Files.list(dir)
					.map(Path::getFileName)
					.map(Path::toString)
					.filter(name -> filter.test(dir, name))
					.map(fileCollator()::getCollationKey)
					.sorted()
					.map(CollationKey::getSourceString);
				return result;
			} catch (IOException e) {
				throw new UncheckedIOException(e);
			}
		}
		return Stream.empty();
	}

	public static Stream listStream(Path dir) {
		return listStream(dir, p -> true);
	}

	public static List list(Path dir, Predicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.collect(toList());
		}
	}

	public static List list(Path dir, BiPredicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.collect(toList());
		}
	}

	public static List list(Path dir) {
		return list(dir, p -> true);
	}

	public static List listPaths(Path dir, Predicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.map(name -> dir.resolve(name))
				.collect(toList());
		}
	}

	public static List listPaths(Path dir, BiPredicate filter) {
		try (Stream stream = listStream(dir, filter)) {
			return stream.map(name -> dir.resolve(name))
				.collect(toList());
		}
	}

	public static List listPaths(Path dir) {
		return listPaths(dir, p -> true);
	}

	public static File copy(byte[] data, File file) throws IOException {
		copy(data, file.toPath());
		return file;
	}

	public static Path copy(byte[] data, Path path) throws IOException {
		try (FileChannel out = writeChannel(path)) {
			ByteBuffer bb = ByteBuffer.wrap(data);
			while (bb.hasRemaining()) {
				out.write(bb);
			}
		}
		return path;
	}

	public static Writer copy(byte[] data, Writer w) throws IOException {
		w.write(new String(data, 0, data.length, UTF_8));
		return w;
	}

	public static OutputStream copy(byte[] data, OutputStream out) throws IOException {
		out.write(data, 0, data.length);
		return out;
	}

	public static Writer copy(Reader r, Writer w) throws IOException {
		try (r) {
			char[] buffer = new char[BUFFER_SIZE];
			for (int size; (size = r.read(buffer, 0, buffer.length)) > 0;) {
				w.write(buffer, 0, size);
			}
			return w;
		}
	}

	public static OutputStream copy(Reader r, OutputStream out) throws IOException {
		return copy(r, out, UTF_8);
	}

	public static OutputStream copy(Reader r, OutputStream out, String charset) throws IOException {
		return copy(r, out, Charset.forName(charset));
	}

	public static OutputStream copy(Reader r, OutputStream out, Charset charset) throws IOException {
		Writer w = writer(out, charset);
		try {
			copy(r, w);
			return out;
		} finally {
			w.flush();
		}
	}

	public static Writer copy(InputStream in, Writer w) throws IOException {
		return copy(in, w, UTF_8);
	}

	public static Writer copy(InputStream in, Writer w, String charset) throws IOException {
		return copy(in, w, Charset.forName(charset));
	}

	public static Writer copy(InputStream in, Writer w, Charset charset) throws IOException {
		return copy(reader(in, charset), w);
	}

	/**
	 * If InputStream throws EOFException, map it to returning -1. Other
	 * IOExceptions are propagated.
	 */
	private static int read(InputStream in, byte[] b, int off, int len) throws IOException {
		try {
			return in.read(b, off, len);
		} catch (EOFException e) {
			return -1;
		}
	}

	/**
	 * If ReadableByteChannel throws EOFException, map it to returning -1. Other
	 * IOExceptions are propagated.
	 */
	private static int read(ReadableByteChannel in, ByteBuffer bb) throws IOException {
		try {
			return in.read(bb);
		} catch (EOFException e) {
			return -1;
		}
	}

	public static OutputStream copy(InputStream in, OutputStream out) throws IOException {
		try (in) {
			byte[] buffer = new byte[BUFFER_SIZE];
			for (int size; (size = read(in, buffer, 0, buffer.length)) > 0;) {
				out.write(buffer, 0, size);
			}
			return out;
		}
	}

	public static OutputStream copy(InputStream in, OutputStream out, int limit) throws IOException {
		return copy(new LimitedInputStream(in, limit), out);
	}

	public static ByteBufferOutputStream copy(InputStream in, ByteBufferOutputStream out) throws IOException {
		try (in) {
			out.write(in);
			return out;
		}
	}

	public static DataOutput copy(InputStream in, DataOutput out) throws IOException {
		try (in) {
			byte[] buffer = new byte[BUFFER_SIZE];
			for (int size; (size = read(in, buffer, 0, buffer.length)) > 0;) {
				out.write(buffer, 0, size);
			}
			return out;
		}
	}

	public static WritableByteChannel copy(ReadableByteChannel in, WritableByteChannel out) throws IOException {
		try (in) {
			ByteBuffer bb = ByteBuffer.allocateDirect(BUFFER_SIZE);
			while (read(in, bb) > 0) {
				bb.flip();
				out.write(bb);
				bb.compact();
			}
			for (bb.flip(); bb.hasRemaining();) {
				out.write(bb);
			}
			return out;
		}
	}

	public static ByteBuffer copy(InputStream in, ByteBuffer bb) throws IOException {
		try (in) {
			if (bb.hasArray()) {
				byte[] buffer = bb.array();
				int offset = bb.arrayOffset();
				for (int size, position; bb.hasRemaining()
					&& (size = read(in, buffer, offset + (position = bb.position()), bb.remaining())) > 0;) {
					bb.position(position + size);
				}
			} else {
				int length = Math.min(bb.remaining(), BUFFER_SIZE);
				byte[] buffer = new byte[length];
				for (int size; length > 0 && (size = read(in, buffer, 0, length)) > 0;) {
					bb.put(buffer, 0, size);
					length = Math.min(bb.remaining(), buffer.length);
				}
			}
			return bb;
		}
	}

	public static byte[] copy(InputStream in, byte[] data) throws IOException {
		return copy(in, data, 0, data.length);
	}

	public static byte[] copy(InputStream in, byte[] data, int off, int len) throws IOException {
		try (in) {
			for (int remaining, size; (remaining = len - off) > 0 && (size = read(in, data, off, remaining)) > 0;) {
				off += size;
			}
			return data;
		}
	}

	public static OutputStream copy(ByteBuffer bb, OutputStream out) throws IOException {
		if (out instanceof ByteBufferOutputStream bbout) {
			bbout.write(bb);
		} else if (bb.hasArray()) {
			out.write(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining());
			bb.position(bb.limit());
		} else {
			int length = Math.min(bb.remaining(), BUFFER_SIZE);
			byte[] buffer = new byte[length];
			while (length > 0) {
				bb.get(buffer, 0, length);
				out.write(buffer, 0, length);
				length = Math.min(bb.remaining(), buffer.length);
			}
		}
		return out;
	}

	public static DataOutput copy(ByteBuffer bb, DataOutput out) throws IOException {
		if (out instanceof ByteBufferDataOutput bbout) {
			bbout.write(bb);
		} else if (bb.hasArray()) {
			out.write(bb.array(), bb.arrayOffset() + bb.position(), bb.remaining());
			bb.position(bb.limit());
		} else {
			int length = Math.min(bb.remaining(), BUFFER_SIZE);
			byte[] buffer = new byte[length];
			while (length > 0) {
				bb.get(buffer, 0, length);
				out.write(buffer, 0, length);
				length = Math.min(bb.remaining(), buffer.length);
			}
		}
		return out;
	}

	public static MessageDigest copy(URL url, MessageDigest md) throws IOException {
		return copy(stream(url), md);
	}

	public static MessageDigest copy(File file, MessageDigest md) throws IOException {
		return copy(file.toPath(), md);
	}

	public static MessageDigest copy(Path path, MessageDigest md) throws IOException {
		return copy(readChannel(path), md);
	}

	public static MessageDigest copy(URLConnection conn, MessageDigest md) throws IOException {
		return copy(conn.getInputStream(), md);
	}

	public static MessageDigest copy(InputStream in, MessageDigest md) throws IOException {
		try (in) {
			byte[] buffer = new byte[BUFFER_SIZE];
			for (int size; (size = read(in, buffer, 0, buffer.length)) > 0;) {
				md.update(buffer, 0, size);
			}
			return md;
		}
	}

	public static MessageDigest copy(ReadableByteChannel in, MessageDigest md) throws IOException {
		try (in) {
			ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
			while (read(in, bb) > 0) {
				bb.flip();
				md.update(bb);
				bb.compact();
			}
			for (bb.flip(); bb.hasRemaining();) {
				md.update(bb);
			}
			return md;
		}
	}

	public static File copy(URL url, File file) throws IOException {
		return copy(stream(url), file);
	}

	public static File copy(URLConnection conn, File file) throws IOException {
		return copy(conn.getInputStream(), file);
	}

	public static URL copy(InputStream in, URL url) throws IOException {
		return copy(in, url, null);
	}

	public static URL copy(InputStream in, URL url, String method) throws IOException {
		URLConnection c = url.openConnection();
		HttpURLConnection http = (c instanceof HttpURLConnection httpCon) ? httpCon : null;
		if (http != null && method != null) {
			http.setRequestMethod(method);
		}
		c.setDoOutput(true);
		try (OutputStream out = c.getOutputStream()) {
			copy(in, out);
			return url;
		} finally {
			if (http != null) {
				http.disconnect();
			}
		}
	}

	public static File copy(File src, File tgt) throws IOException {
		copy(src.toPath(), tgt.toPath());
		return tgt;
	}

	public static Path copy(Path src, Path tgt) throws IOException {
		final Path source = src.toAbsolutePath();
		final Path target = tgt.toAbsolutePath();
		if (Files.isRegularFile(source)) {
			Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
			return tgt;
		}
		if (Files.isDirectory(source)) {
			if (Files.notExists(target)) {
				mkdirs(target);
			}
			if (!Files.isDirectory(target))
				throw new IllegalArgumentException("target directory for a directory must be a directory: " + target);
			if (target.startsWith(source))
				throw new IllegalArgumentException("target directory can not be child of source directory.");

			Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
				new FileVisitor() {
					final FileTime now = FileTime.fromMillis(System.currentTimeMillis());

					@Override
					public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
						Path targetdir = target.resolve(source.relativize(dir));
						try {
							Files.copy(dir, targetdir);
						} catch (FileAlreadyExistsException e) {
							if (!Files.isDirectory(targetdir))
								throw e;
						}
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
						Path targetFile = target.resolve(source.relativize(file));
						Files.copy(file, targetFile, StandardCopyOption.REPLACE_EXISTING);
						Files.setLastModifiedTime(targetFile, now);
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
						if (exc != null) { // directory iteration failed
							throw exc;
						}
						return FileVisitResult.CONTINUE;
					}

					@Override
					public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
						if (exc != null) {
							throw exc;
						}
						return FileVisitResult.CONTINUE;
					}
				});
			return tgt;
		}
		throw new FileNotFoundException("During copy: " + source.toString());
	}

	public static File copy(InputStream in, File file) throws IOException {
		copy(in, file.toPath());
		return file;
	}

	public static Path copy(InputStream in, Path path) throws IOException {
		try (FileChannel out = writeChannel(path)) {
			copy(in, out);
		}
		return path;
	}

	public static OutputStream copy(File file, OutputStream out) throws IOException {
		return copy(file.toPath(), out);
	}

	public static OutputStream copy(Path path, OutputStream out) throws IOException {
		return copy(readChannel(path), out);
	}

	public static WritableByteChannel copy(InputStream in, WritableByteChannel out) throws IOException {
		try (in) {
			ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
			byte[] buffer = bb.array();
			for (int size, position; (size = read(in, buffer, position = bb.position(), bb.remaining())) > 0;) {
				bb.position(position + size);
				bb.flip();
				out.write(bb);
				bb.compact();
			}
			for (bb.flip(); bb.hasRemaining();) {
				out.write(bb);
			}
			return out;
		}
	}

	public static OutputStream copy(ReadableByteChannel in, OutputStream out) throws IOException {
		try (in) {
			ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE);
			byte[] buffer = bb.array();
			for (; read(in, bb) > 0; bb.clear()) {
				out.write(buffer, 0, bb.position());
			}
			return out;
		}
	}

	public static byte[] read(File file) throws IOException {
		try (FileChannel in = readChannel(file.toPath())) {
			ByteBuffer bb = ByteBuffer.allocate((int) in.size());
			while (read(in, bb) > 0) {}
			return bb.array();
		}
	}

	public static ByteBuffer read(Path path) throws IOException {
		try (FileChannel in = readChannel(path)) {
			long size = in.size();

			if (!isWindows() && (size > DIRECT_MAP_THRESHOLD)) {
				return in.map(MapMode.READ_ONLY, 0, size);
			}
			ByteBuffer bb = ByteBuffer.allocate((int) size);
			while (read(in, bb) > 0) {}
			bb.flip();
			return bb;
		}
	}

	public static byte[] read(ByteBuffer bb) {
		byte[] data = new byte[bb.remaining()];
		bb.get(data, 0, data.length);
		return data;
	}

	public static byte[] read(URL url) throws IOException {
		URLConnection conn = url.openConnection();
		conn.connect();
		int length = conn.getContentLength();
		if (length == -1) {
			return read(conn.getInputStream());
		}
		return copy(conn.getInputStream(), new byte[length]);
	}

	public static byte[] read(InputStream in) throws IOException {
		return copy(in, new ByteBufferOutputStream()).toByteArray();
	}

	public static void write(byte[] data, OutputStream out) throws IOException {
		copy(data, out);
	}

	public static void write(byte[] data, File file) throws IOException {
		copy(data, file);
	}

	public static String collect(File file) throws IOException {
		return collect(file.toPath(), UTF_8);
	}

	public static String collect(File file, String encoding) throws IOException {
		return collect(file.toPath(), Charset.forName(encoding));
	}

	public static String collect(File file, Charset encoding) throws IOException {
		return collect(file.toPath(), encoding);
	}

	public static String collect(Path path) throws IOException {
		return collect(path, UTF_8);
	}

	public static String collect(Path path, Charset encoding) throws IOException {
		return collect(reader(path, encoding));
	}

	public static String collect(ByteBuffer bb, Charset encoding) {
		return decode(bb, encoding).toString();
	}

	public static String collect(URL url, String encoding) throws IOException {
		return collect(stream(url), Charset.forName(encoding));
	}

	public static String collect(URL url, Charset encoding) throws IOException {
		return collect(stream(url), encoding);
	}

	public static String collect(URL url) throws IOException {
		return collect(url, UTF_8);
	}

	public static String collect(String path) throws IOException {
		return collect(Paths.get(path), UTF_8);
	}

	public static String collect(InputStream in) throws IOException {
		return collect(in, UTF_8);
	}

	public static String collect(InputStream in, String encoding) throws IOException {
		return collect(in, Charset.forName(encoding));
	}

	public static String collect(InputStream in, Charset encoding) throws IOException {
		return collect(reader(in, encoding));
	}

	public static String collect(Reader r) throws IOException {
		return copy(r, new CharArrayWriter()).toString();
	}

	/**
	 * Create a temporary file.
	 *
	 * @param directory the directory in which to create the file. Can be null,
	 *            in which case the system TMP directory is used
	 * @param pattern the filename prefix pattern. Must be at least 3 characters
	 *            long
	 * @param suffix the filename suffix. Can be null, in which case (system)
	 *            default suffix is used
	 * @return temp file
	 * @throws IllegalArgumentException when pattern is null or too short
	 * @throws IOException when the specified (non-null) directory is not a
	 *             directory
	 */
	public static File createTempFile(File directory, String pattern, String suffix)
		throws IllegalArgumentException, IOException {
		if ((pattern == null) || (pattern.length() < 3)) {
			throw new IllegalArgumentException(
				"Pattern must be at least 3 characters long, got " + ((pattern == null) ? "null" : pattern.length()));
		}

		if ((directory != null) && !directory.isDirectory()) {
			throw new FileNotFoundException("Directory " + directory + " is not a directory");
		}

		return File.createTempFile(pattern, suffix, directory);
	}

	public static String absolutePath(File file) {
		return normalizePath(file.getAbsolutePath());
	}

	public static String absolutePath(Path path) {
		return normalizePath(path.toAbsolutePath());
	}

	public static String normalizePath(Path path) {
		return normalizePath(path.toString());
	}

	public static String normalizePath(File file) {
		return normalizePath(file.getPath());
	}

	public static String normalizePath(String path) {
		return path.replace(File.separatorChar, '/');
	}

	public static File getFile(String file) {
		return getFile(work, file);
	}

	public static File getFile(File base, String file) {
		return os.getFile(base, file);
	}

	/**
	 * Resolve the subPath from the absolute file base. The subPath must not
	 * resolve to a path that is not in the subtree of files from base. it may
	 * contain .. as long as this does not end up at the start when normalized.
	 * It (surprisingly) can handle absolute files since `new File(base,"/foo")
	 * resolves not to "/foo" ... I.e. except for the .., it can never escape
	 * base.
	 * 

* If the subPath is null or empty, this will return the base * * @param base the absolute base directory * @param subPath the sub path or null * @return the absolute file resolved from base with relativePath * @throws IOException */ public static File getBasedFile(File base, String subPath) throws IOException { assert base != null : "base must not be null or the result could be anywhere on the fs"; if (subPath == null || subPath.isEmpty()) return base; return os.getBasedFile(base, subPath); } public static Path getPath(String file) { return getPath(work.toPath(), file); } public static Path getPath(Path base, String file) { StringRover rover = new StringRover(file); if (rover.startsWith("~/")) { rover.increment(2); if (!rover.startsWith("~/")) { return getPath(home.toPath(), rover.substring(0)); } } if (rover.startsWith("~")) { return getPath(home.toPath() .getParent(), rover.substring(1)); } Path f = new File(rover.substring(0)).toPath(); if (f.isAbsolute()) { return f; } if (base == null) { base = work.toPath(); } for (f = base.normalize() .toAbsolutePath(); !rover.isEmpty();) { int n = rover.indexOf('/'); if (n < 0) { n = rover.length(); } String segment = rover.substring(0, n); f = f.resolve(segment) .normalize(); rover.increment(n + 1); } return f.toAbsolutePath(); } public static Path getBasedPath(Path base, String file) throws IOException { base = base.normalize() .toAbsolutePath(); Path child = getPath(base, file); if (child.startsWith(base)) { return child; } throw new IOException("The file " + child + " is outside of the base " + base); } /** * Deletes the specified file. Folders are recursively deleted.
* If file(s) cannot be deleted, no feedback is provided (fail silently). * * @param file file to be deleted */ public static void delete(File file) { delete(file.toPath()); } /** * Deletes the specified content of the given directory. Folders are * recursively deleted. This method will not delete the given dir, this can * be useful in Eclipse or other systems that detect delete-create
* If file(s) cannot be deleted, no feedback is provided (fail silently). * * @param dir of whose contents are to be deleted */ public static void deleteContent(File dir) { if (!dir.isDirectory()) return; Stream.of(dir.listFiles()) .forEach(IO::delete); } /** * Deletes the specified path. Folders are recursively deleted.
* If file(s) cannot be deleted, no feedback is provided (fail silently). * * @param path path to be deleted */ public static void delete(Path path) { try { deleteWithException(path); } catch (IOException e) { // Ignore a failed delete } } /** * Deletes and creates directories */ public static void initialize(File dir) { try { deleteWithException(dir); mkdirs(dir); } catch (IOException e) { throw new RuntimeException(e); } } /** * Deletes the specified file. Folders are recursively deleted.
* Throws exception if any of the files could not be deleted. * * @param file file to be deleted * @throws IOException if the file (or contents of a folder) could not be * deleted */ public static void deleteWithException(File file) throws IOException { deleteWithException(file.toPath()); } /** * Deletes the specified path. Folders are recursively deleted.
* Throws exception if any of the files could not be deleted. * * @param path path to be deleted * @throws IOException if the path (or contents of a folder) could not be * deleted */ public static void deleteWithException(Path path) throws IOException { path = path.toAbsolutePath(); if (Files.notExists(path) && !isSymbolicLink(path)) { return; } if (path.equals(path.getRoot())) throw new IllegalArgumentException("Cannot recursively delete root for safety reasons"); Files.walkFileTree(path, new FileVisitor() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { Files.delete(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { try { Files.delete(file); } catch (IOException e) { throw exc; } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc != null) { // directory iteration failed throw exc; } Files.delete(dir); return FileVisitResult.CONTINUE; } }); } /** * Renames from to to replacing the target file if * necessary. * * @param from source file * @param to destination file * @throws IOException if the rename operation fails */ public static File rename(File from, File to) throws IOException { return rename(from.toPath(), to.toPath()).toFile(); } /** * Renames from to to replacing the target file if * necessary. * * @param from source path * @param to destination path * @throws IOException if the rename operation fails */ public static Path rename(Path from, Path to) throws IOException { try { return Files.move(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); } catch (AtomicMoveNotSupportedException e) { return Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); } } public static File mkdirs(File dir) throws IOException { return mkdirs(dir.toPath()).toFile(); } public static Path mkdirs(Path dir) throws IOException { if (Files.isSymbolicLink(dir)) { Path target = Files.readSymbolicLink(dir); boolean recreateSymlink = isWindows() && !Files.exists(target, LinkOption.NOFOLLOW_LINKS); Path result = mkdirs(target); if (recreateSymlink) { // recreate symlink on windows delete(dir); createSymbolicLink(dir, target); } return result; } return Files.createDirectories(dir); } public static long drain(InputStream in) throws IOException { try (in) { long result = 0L; byte[] buffer = new byte[BUFFER_SIZE]; for (int size; (size = read(in, buffer, 0, buffer.length)) > 0;) { result += size; } return result; } } public static OutputStream copy(Collection c, OutputStream out) throws IOException { PrintWriter pw = writer(out); try { for (Object o : c) { pw.println(o); } return out; } finally { pw.flush(); } } public static Throwable close(AutoCloseable in) { try { if (in != null) in.close(); } catch (Throwable e) { return e; } return null; } // This method is required for binary backwards compatibility. public static Throwable close(Closeable in) { return close((AutoCloseable) in); } /** * Will iterate over the given toBeClose and silently close any object that * implements AutoCloseable. * * @param toBeClosed any iterable * @return true if an exception was thrown during closing */ public static boolean closeAll(Object... toBeClosed) { if (toBeClosed == null) return false; boolean exceptionsWereThrown = false; for (Object o : toBeClosed) { if (o instanceof AutoCloseable closeable) { exceptionsWereThrown |= close(closeable) != null; } else if (o instanceof Iterable iterable) { for (Object oo : iterable) { if (oo instanceof AutoCloseable closeable) { // do not recurse! exceptionsWereThrown |= close(closeable) != null; } } } } return exceptionsWereThrown; } public static URL toURL(String s, File base) throws MalformedURLException { int n = s.indexOf(':'); if (n > 0 && n < 10) { // is url return new URL(s); } return getFile(base, s).toURI() .toURL(); } public static void store(Object o, File file) throws IOException { store(o, file.toPath(), UTF_8); } public static void store(Object o, File file, String encoding) throws IOException { store(o, file.toPath(), Charset.forName(encoding)); } public static void store(Object o, Path path, Charset encoding) throws IOException { try (FileChannel ch = writeChannel(path)) { if (o != null) { try (Writer w = Channels.newWriter(ch, encoding.newEncoder(), -1)) { w.write(o.toString()); } } } } public static void store(Object o, OutputStream out) throws IOException { store(o, out, UTF_8); } public static void store(Object o, OutputStream out, String encoding) throws IOException { store(o, out, Charset.forName(encoding)); } public static void store(Object o, OutputStream out, Charset encoding) throws IOException { Writer w = writer(out, encoding); try { store(o, w); } finally { w.flush(); } } public static void store(Object o, Writer w) throws IOException { if (o != null) { w.write(o.toString()); } } /** * Store output in a file but ensure that the content is updated atomically. * To ensure this, the file is first copied to a temporary file in the same * directory as the target. It is then renamed which will first attempt an * atomic move but will always replace. * * @param store the function provide the output * @param target the file to store it, parent directories will be created if * necessary */ public static void store(ConsumerWithException store, File target) throws Exception { target.getParentFile() .mkdirs(); File tmp = createTempFile(target.getParentFile(), target.getName(), ".tmp"); try { try (OutputStream outputStream = outputStream(tmp)) { store.accept(outputStream); } rename(tmp, target); assert target.isFile(); } finally { if (tmp.exists()) tmp.delete(); } } public static InputStream stream(byte[] data) { return stream(ByteBuffer.wrap(data)); } public static InputStream stream(ByteBuffer bb) { return new ByteBufferInputStream(bb); } public static InputStream stream(String s) { return stream(s, UTF_8); } public static InputStream stream(String s, String encoding) { return stream(s, Charset.forName(encoding)); } public static InputStream stream(String s, Charset encoding) { return stream(s.getBytes(encoding)); } public static InputStream stream(File file) throws IOException { return stream(file.toPath()); } public static InputStream stream(Path path) throws IOException { return Files.newInputStream(path); } public static InputStream stream(URL url) throws IOException { return url.openStream(); } public static FileChannel readChannel(Path path) throws IOException { return FileChannel.open(path, readOptions); } public static OutputStream outputStream(File file) throws IOException { return outputStream(file.toPath()); } public static OutputStream outputStream(Path path) throws IOException { return Files.newOutputStream(path); } public static FileChannel writeChannel(Path path) throws IOException { return FileChannel.open(path, writeOptions); } public static CharBuffer decode(ByteBuffer bb, Charset encoding) { return encoding.decode(bb); } public static ByteBuffer encode(CharBuffer cb, Charset encoding) { return encoding.encode(cb); } public static ByteBuffer encode(String s, Charset encoding) { return encoding.encode(s); } public static BufferedReader reader(String s) { return new BufferedReader(new StringReader(s)); } public static BufferedReader reader(File file) throws IOException { return reader(file.toPath(), UTF_8); } public static BufferedReader reader(File file, String encoding) throws IOException { return reader(file.toPath(), Charset.forName(encoding)); } public static BufferedReader reader(File file, Charset encoding) throws IOException { return reader(file.toPath(), encoding); } public static BufferedReader reader(Path path, Charset encoding) throws IOException { return reader(readChannel(path), encoding); } public static BufferedReader reader(ByteBuffer bb, Charset encoding) { return reader(stream(bb), encoding); } public static BufferedReader reader(CharBuffer cb) { return new BufferedReader(new CharBufferReader(cb)); } public static BufferedReader reader(ReadableByteChannel in, Charset encoding) { return new BufferedReader(Channels.newReader(in, encoding.newDecoder(), -1)); } public static BufferedReader reader(InputStream in) { return reader(in, UTF_8); } public static BufferedReader reader(InputStream in, String encoding) { return reader(in, Charset.forName(encoding)); } public static BufferedReader reader(InputStream in, Charset encoding) { return new BufferedReader(new InputStreamReader(in, encoding)); } public static PrintWriter writer(File file) throws IOException { return writer(file.toPath(), UTF_8); } public static PrintWriter writer(File file, String encoding) throws IOException { return writer(file.toPath(), Charset.forName(encoding)); } public static PrintWriter writer(File file, Charset encoding) throws IOException { return writer(file.toPath(), encoding); } public static PrintWriter writer(Path path) throws IOException { return writer(path, UTF_8); } public static PrintWriter writer(Path path, Charset encoding) throws IOException { return writer(writeChannel(path), encoding); } public static PrintWriter writer(WritableByteChannel out, Charset encoding) { return new PrintWriter(Channels.newWriter(out, encoding.newEncoder(), -1)); } public static PrintWriter writer(OutputStream out) { return writer(out, UTF_8); } public static PrintWriter writer(OutputStream out, String encoding) { return writer(out, Charset.forName(encoding)); } public static PrintWriter writer(OutputStream out, Charset encoding) { return new PrintWriter(new OutputStreamWriter(out, encoding)); } static final class AppendableWriterAdapter extends Writer { private final Appendable appendable; AppendableWriterAdapter(Appendable appendable) { super(appendable); this.appendable = appendable; } @Override public void write(int c) throws IOException { synchronized (lock) { appendable.append((char) c); } } private static void validate(int length, int offset, int count) { if (offset < 0) { throw new IndexOutOfBoundsException("offset less than zero"); } if (count < 0) { throw new IndexOutOfBoundsException("count less than zero"); } if (offset > length - count) { throw new IndexOutOfBoundsException("offset+count greater than input length"); } } @Override public void write(char[] cbuf, int off, int len) throws IOException { validate(cbuf.length, off, len); synchronized (lock) { for (int i = off, end = off + len; i < end; i++) { appendable.append(cbuf[i]); } } } @Override public void write(String str) throws IOException { requireNonNull(str); synchronized (lock) { appendable.append(str); } } @Override public void write(String str, int off, int len) throws IOException { validate(str.length(), off, len); synchronized (lock) { appendable.append(str, off, off + len); } } @Override public Writer append(char c) throws IOException { synchronized (lock) { appendable.append(c); } return this; } @Override public Writer append(CharSequence csq) throws IOException { if (csq == null) { csq = "null"; } synchronized (lock) { appendable.append(csq); } return this; } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { if (csq == null) { csq = "null"; } validate(csq.length(), start, end - start); synchronized (lock) { appendable.append(csq, start, end); } return this; } @Override public void flush() throws IOException { synchronized (lock) { if (appendable instanceof Flushable flushable) { flushable.flush(); } } } @Override public void close() throws IOException { synchronized (lock) { flush(); if (appendable instanceof Closeable closeable) { closeable.close(); } } } } public static Writer appendableToWriter(Appendable appendable) { return new AppendableWriterAdapter(appendable); } public static boolean createSymbolicLink(File link, File target) throws IOException { return createSymbolicLink(link.toPath(), target.toPath()); } public static boolean createSymbolicLink(Path link, Path target) throws IOException { if (isSymbolicLink(link)) { Path linkTarget = Files.readSymbolicLink(link); if (target.equals(linkTarget)) { return true; } else { Files.delete(link); } } try { Files.createSymbolicLink(link, target); return true; } catch (Exception e) { // ignore } return false; } public static boolean isSymbolicLink(File link) { return isSymbolicLink(link.toPath()); } public static boolean isSymbolicLink(Path link) { return Files.isSymbolicLink(link); } /** * Creates a symbolic link from {@code link} to the {@code target}, or * copies {@code target} to {@code link} if running on Windows. *

* Creating symbolic links on Windows requires administrator permissions, so * copying is a safer fallback. Copy only happens if timestamp and and file * length are different than target * * @param link the location of the symbolic link, or destination of the * copy. * @param target the source of the symbolic link, or source of the copy. * @return {@code true} if the operation succeeds, {@code false} otherwise. */ public static boolean createSymbolicLinkOrCopy(File link, File target) { return createSymbolicLinkOrCopy(link.toPath(), target.toPath()); } /** * Creates a symbolic link from {@code link} to the {@code target}, or * copies {@code target} to {@code link} if running on Windows. *

* Creating symbolic links on Windows requires administrator permissions, so * copying is a safer fallback. Copy only happens if timestamp and and file * length are different than target * * @param link the location of the symbolic link, or destination of the * copy. * @param target the source of the symbolic link, or source of the copy. * @return {@code true} if the operation succeeds, {@code false} otherwise. */ public static boolean createSymbolicLinkOrCopy(Path link, Path target) { try { if (isWindows() || !createSymbolicLink(link, target)) { // only copy if target length and timestamp differ BasicFileAttributes targetAttrs = Files.readAttributes(target, BasicFileAttributes.class); try { BasicFileAttributes linkAttrs = Files.readAttributes(link, BasicFileAttributes.class); if (targetAttrs.lastModifiedTime() .equals(linkAttrs.lastModifiedTime()) && targetAttrs.size() == linkAttrs.size()) { return true; } } catch (IOException e) { // link does not exist } copy(target, link); Files.setLastModifiedTime(link, targetAttrs.lastModifiedTime()); } return true; } catch (Exception ignore) { // ignore } return false; } static final public OutputStream nullStream = // new OutputStream() { @Override public void write(int var0) {} @Override public void write(byte[] var0) {} @Override public void write(byte[] var0, int from, int l) {} @Override public void close() {} @Override public void flush() {} }; static final public Writer nullWriter = // new Writer() { @Override public Writer append(char var0) { return this; } @Override public Writer append(CharSequence var0) { return this; } @Override public Writer append(CharSequence var0, int var1, int var2) { return this; } @Override public void write(int var0) {} @Override public void write(String var0) {} @Override public void write(String var0, int var1, int var2) {} @Override public void write(char[] var0) {} @Override public void write(char[] var0, int var1, int var2) {} @Override public void close() {} @Override public void flush() {} }; public static String toSafeFileName(String string) { return os.toSafeFileName(string); } public static boolean isWindows() { return os instanceof Windows; } /* * This class calculates environment variables so that makes it hard to * test. For this reason tests can override the #getenv method. */ static class EnvironmentCalculator { File getDirLocation(String... path) { for (String p : path) { if (p == null) continue; File dir = new File(p); dir.mkdirs(); return dir; } return null; } String getEnv(String key) { return getSystemEnv(key, new TreeSet<>()); } String getSystemEnv(String key, Set visited) { try { if (!visited.add(key)) { return visited.stream() .collect(Collectors.joining(",", "%", "%")); } String value = getenv(key); if (value == null) return null; Matcher matcher = WINDOWS_MACROS.matcher(value); boolean found = matcher.find(); if (!found) return value; StringBuffer sb = new StringBuffer(); do { String macroKey = matcher.group("key"); String macroValue = getSystemEnv(macroKey, visited); if (macroValue == null) macroValue = '%' + macroKey + '%'; matcher.appendReplacement(sb, quoteReplacement(macroValue)); } while (matcher.find()); matcher.appendTail(sb); return sb.toString(); } finally { visited.remove(key); } } protected String getenv(String key) { return System.getenv(key); } } /** * Return the environment variables. On windows, macros in these variables * will be expanded. On other OS'es, macro expansion is done by the os. If * during expansion a key cannot be found, it will be included as %key%` for * diagnostics. * * @param key the environment variable name * @return a string with the (expanded) value of the environment variable or * null if not found */ public static String getenv(String key) { return os.getenv(key); } public static String readUTF(DataInput in) throws IOException { int size = in.readUnsignedShort(); char[] string = new char[size]; int len = 0; for (int i = 0; i < size; i++, len++) { int b = in.readUnsignedByte(); if ((b > 0x00) && (b < 0x80)) { string[len] = (char) b; } else { switch (b >> 4) { // 2 byte encoding case 0b1100, 0b1101 : { i++; if (i >= size) { throw new UTFDataFormatException("partial multi byte charater at end"); } int b2 = in.readUnsignedByte(); if ((b2 & 0b1100_0000) != 0b1000_0000) { throw new UTFDataFormatException("bad encoding at byte: " + (i - 1)); } string[len] = (char) (((b & 0x1F) << 6) | (b2 & 0x3F)); break; } // 3 byte encoding case 0b1110 : { i += 2; if (i >= size) { throw new UTFDataFormatException("partial multi byte charater at end"); } int b2 = in.readUnsignedByte(); int b3 = in.readUnsignedByte(); if (((b2 & 0b1100_0000) != 0b1000_0000) || ((b3 & 0b1100_0000) != 0b1000_0000)) { throw new UTFDataFormatException("bad encoding at byte: " + (i - 2)); } string[len] = (char) (((b & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)); break; } // invalid encoding default : { throw new UTFDataFormatException("bad encoding at byte: " + i); } } } } return new String(string, 0, len); } public static String getJavaExecutablePath(String name) { Path java_home = JAVA_HOME.toPath(); Path command = Paths.get("bin", name); Path executable = java_home.resolve(command); if (Files.exists(executable)) { return absolutePath(executable); } if (java_home.endsWith("jre")) { executable = java_home.getParent() .resolve(command); if (Files.exists(executable)) { return absolutePath(executable); } } return name; } /** * Create a new unique file name in the given folder * * @param folder the folder to create a File in * @param stem the name stem, "untitled-" if null * @return a file in the folder that does not exist */ public static File unique(File folder, String stem) { if (stem == null) stem = "untitled-"; int n = 0; while (true) { File f = new File(folder, stem + n); if (!f.exists()) return f; n++; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy