package aQute.lib.io; import static java.nio.charset.StandardCharsets.UTF_8; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.DataOutput; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; 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.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.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.util.Collection; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.Set; import java.util.regex.Pattern; import aQute.libg.glob.Glob; public class IO { static final int BUFFER_SIZE = IOConstants.PAGE_SIZE * 16; private static final int DIRECT_MAP_THRESHOLD = BUFFER_SIZE; private static final boolean isWindows = File.separatorChar == '\\'; static final public File work = new File(System.getProperty("user.dir")); static final public File home; private static final EnumSet<StandardOpenOption> writeOptions = EnumSet.of(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); private static final EnumSet<StandardOpenOption> readOptions = EnumSet.of(StandardOpenOption.READ); static { File tmp = null; try { tmp = new File(System.getenv("HOME")); } catch (Exception e) {} if (tmp == null) { tmp = new File(System.getProperty("user.home")); } home = tmp; } 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<File> tree(File current) { Set<File> files = new LinkedHashSet<File>(); traverse(files, current, null); return files; } public static Collection<File> tree(File current, String glob) { Set<File> files = new LinkedHashSet<File>(); traverse(files, current, glob == null ? null : new Glob(glob)); return files; } private static void traverse(Collection<File> 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 : current.listFiles()) { traverse(files, sub, glob); } } } 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 { char[] buffer = new char[BUFFER_SIZE]; for (int size; (size = r.read(buffer, 0, buffer.length)) > 0;) { w.write(buffer, 0, size); } return w; } finally { r.close(); } } 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); } public static OutputStream copy(InputStream in, OutputStream out) throws IOException { try { byte[] buffer = new byte[BUFFER_SIZE]; for (int size; (size = in.read(buffer, 0, buffer.length)) > 0;) { out.write(buffer, 0, size); } return out; } finally { in.close(); } } public static DataOutput copy(InputStream in, DataOutput out) throws IOException { try { byte[] buffer = new byte[BUFFER_SIZE]; for (int size; (size = in.read(buffer, 0, buffer.length)) > 0;) { out.write(buffer, 0, size); } return out; } finally { in.close(); } } public static WritableByteChannel copy(ReadableByteChannel in, WritableByteChannel out) throws IOException { try { ByteBuffer bb = ByteBuffer.allocateDirect(BUFFER_SIZE); while (in.read(bb) > 0) { bb.flip(); out.write(bb); bb.compact(); } for (bb.flip(); bb.hasRemaining();) { out.write(bb); } return out; } finally { in.close(); } } public static ByteBuffer copy(InputStream in, ByteBuffer bb) throws IOException { try { if (bb.hasArray()) { byte[] buffer = bb.array(); int offset = bb.arrayOffset(); for (int size; bb.hasRemaining() && (size = in.read(buffer, offset + bb.position(), bb.remaining())) > 0;) { bb.position(bb.position() + size); } } else { int length = Math.min(bb.remaining(), BUFFER_SIZE); byte[] buffer = new byte[length]; for (int size; length > 0 && (size = in.read(buffer, 0, length)) > 0;) { bb.put(buffer, 0, size); length = Math.min(bb.remaining(), buffer.length); } } return bb; } finally { in.close(); } } 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 { for (int remaining, size; (remaining = len - off) > 0 && (size = in.read(data, off, remaining)) > 0;) { off += size; } return data; } finally { in.close(); } } public static OutputStream copy(ByteBuffer bb, OutputStream out) throws IOException { 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 { byte[] buffer = new byte[BUFFER_SIZE]; for (int size; (size = in.read(buffer, 0, buffer.length)) > 0;) { md.update(buffer, 0, size); } return md; } finally { in.close(); } } public static MessageDigest copy(ReadableByteChannel in, MessageDigest md) throws IOException { try { ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); while (in.read(bb) > 0) { bb.flip(); md.update(bb); bb.compact(); } for (bb.flip(); bb.hasRemaining();) { md.update(bb); } return md; } finally { in.close(); } } 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) ? (HttpURLConnection) c : 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<Path>() { 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 { ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); byte[] buffer = bb.array(); for (int size; (size = in.read(buffer, bb.position(), bb.remaining())) > 0;) { bb.position(bb.position() + size); bb.flip(); out.write(bb); bb.compact(); } for (bb.flip(); bb.hasRemaining();) { out.write(bb); } return out; } finally { in.close(); } } public static OutputStream copy(ReadableByteChannel in, OutputStream out) throws IOException { try { ByteBuffer bb = ByteBuffer.allocate(BUFFER_SIZE); byte[] buffer = bb.array(); for (; in.read(bb) > 0; bb.clear()) { out.write(buffer, 0, bb.position()); } return out; } finally { in.close(); } } public static byte[] read(File file) throws IOException { try (FileChannel in = readChannel(file.toPath())) { ByteBuffer bb = ByteBuffer.allocate((int) in.size()); while (in.read(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 (in.read(bb) > 0) {} bb.flip(); return bb; } } public static byte[] read(ByteBuffer bb) throws IOException { 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 { ByteArrayOutputStream out = new ByteArrayOutputStream(IOConstants.PAGE_SIZE); copy(in, out); return out.toByteArray(); } public static void write(byte[] data, OutputStream out) throws Exception { copy(data, out); } public static void write(byte[] data, File file) throws Exception { 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) throws IOException { 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 { StringWriter w = new StringWriter(); copy(r, w); return w.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 File getFile(String filename) { return getFile(work, filename); } public static File getFile(File base, String file) { if (file.startsWith("~/")) { file = file.substring(2); if (!file.startsWith("~/")) { return getFile(home, file); } } if (file.startsWith("~")) { file = file.substring(1); return getFile(home.getParentFile(), file); } File f = new File(file); if (f.isAbsolute()) return f; if (base == null) base = work; f = base.getAbsoluteFile(); for (int n; (n = file.indexOf('/')) > 0;) { String first = file.substring(0, n); file = file.substring(n + 1); if (first.equals("..")) f = f.getParentFile(); else f = new File(f, first); } if (file.equals("..")) return f.getParentFile(); return new File(f, file).getAbsoluteFile(); } /** * Deletes the specified file. Folders are recursively deleted.<br> * 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 path. Folders are recursively deleted.<br> * 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.<br> * 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.<br> * 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<Path>() { @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 <code>from</code> to <code>to</code> replacing the target file if * necessary. * * @param from source file * @param to destination file * @throws IOException if the rename operation fails */ public static void rename(File from, File to) throws IOException { rename(from.toPath(), to.toPath()); } /** * Renames <code>from</code> to <code>to</code> replacing the target file if * necessary. * * @param from source path * @param to destination path * @throws IOException if the rename operation fails */ public static void rename(Path from, Path to) throws IOException { Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); } public static void mkdirs(File dir) throws IOException { mkdirs(dir.toPath()); } public static void mkdirs(Path dir) throws IOException { Files.createDirectories(dir); } public static long drain(InputStream in) throws IOException { try { long result = 0; byte[] buffer = new byte[BUFFER_SIZE]; for (int size; (size = in.read(buffer, 0, buffer.length)) > 0;) { result += size; } return result; } finally { in.close(); } } 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(Closeable in) { try { if (in != null) in.close(); } catch (Throwable e) { return e; } return null; } 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()); } } public static InputStream stream(byte[] data) { return new ByteArrayInputStream(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) throws IOException { 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) throws IOException { return encoding.decode(bb); } public static ByteBuffer encode(CharBuffer cb, Charset encoding) throws IOException { return encoding.encode(cb); } 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) throws IOException { return reader(new ByteBufferInputStream(bb), encoding); } public static BufferedReader reader(CharBuffer cb) throws IOException { return new BufferedReader(new CharBufferReader(cb)); } public static BufferedReader reader(ReadableByteChannel in, Charset encoding) throws IOException { return new BufferedReader(Channels.newReader(in, encoding.newDecoder(), -1)); } public static BufferedReader reader(InputStream in) throws IOException { return reader(in, UTF_8); } public static BufferedReader reader(InputStream in, String encoding) throws IOException { return reader(in, Charset.forName(encoding)); } public static BufferedReader reader(InputStream in, Charset encoding) throws IOException { 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, Charset encoding) throws IOException { return writer(writeChannel(path), encoding); } public static PrintWriter writer(WritableByteChannel out, Charset encoding) throws IOException { return new PrintWriter(Channels.newWriter(out, encoding.newEncoder(), -1)); } public static PrintWriter writer(OutputStream out) throws IOException { return writer(out, UTF_8); } public static PrintWriter writer(OutputStream out, String encoding) throws IOException { return writer(out, Charset.forName(encoding)); } public static PrintWriter writer(OutputStream out, Charset encoding) throws IOException { return new PrintWriter(new OutputStreamWriter(out, encoding)); } public static boolean createSymbolicLink(File link, File target) throws Exception { return createSymbolicLink(link.toPath(), target.toPath()); } public static boolean createSymbolicLink(Path link, Path target) throws Exception { 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. * <p> * 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. * <p> * 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) throws IOException {} @Override public void write(byte[] var0) throws IOException {} @Override public void write(byte[] var0, int from, int l) throws IOException {} }; static final public Writer nullWriter = new Writer() { public java.io.Writer append(char var0) throws java.io.IOException { return null; } public java.io.Writer append(java.lang.CharSequence var0) throws java.io.IOException { return null; } public java.io.Writer append(java.lang.CharSequence var0, int var1, int var2) throws java.io.IOException { return null; } public void write(int var0) throws java.io.IOException {} public void write(java.lang.String var0) throws java.io.IOException {} public void write(java.lang.String var0, int var1, int var2) throws java.io.IOException {} public void write(char[] var0) throws java.io.IOException {} public void write(char[] var0, int var1, int var2) throws java.io.IOException {} public void close() throws IOException {} public void flush() throws IOException {} }; public static String toSafeFileName(String string) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < string.length(); i++) { char c = string.charAt(i); if (c < ' ') continue; if (isWindows) { switch (c) { case '<' : case '>' : case '"' : case '/' : case '\\' : case '|' : case '*' : case ':' : sb.append('%'); break; default : sb.append(c); } } else { switch (c) { case '/' : sb.append('%'); break; default : sb.append(c); } } } if (sb.length() == 0 || (isWindows && RESERVED_WINDOWS_P.matcher(sb).matches())) sb.append("_"); return sb.toString(); } final static Pattern RESERVED_WINDOWS_P = Pattern.compile( "CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]"); public static boolean isWindows() { return isWindows; } }