blob: 733e540115279fe2179a0affb95ed02d7ce6977e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.groovy.nio.extensions;
import groovy.io.FileType;
import groovy.io.FileVisitResult;
import groovy.io.GroovyPrintWriter;
import groovy.lang.Closure;
import groovy.lang.MetaClass;
import groovy.lang.Writable;
import groovy.transform.stc.ClosureParams;
import groovy.transform.stc.FromString;
import groovy.transform.stc.PickFirstResolver;
import groovy.transform.stc.SimpleType;
import org.apache.groovy.nio.runtime.WritablePath;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.callsite.BooleanReturningMethodInvoker;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.get;
/**
* This class defines new groovy methods for Readers, Writers, InputStreams and
* OutputStreams which appear on normal JDK classes inside the Groovy environment.
* Static methods are used with the first parameter being the destination class,
* i.e. <code>public static T eachLine(InputStream self, Closure c)</code>
* provides a <code>eachLine(Closure c)</code> method for <code>InputStream</code>.
* <p/>
* NOTE: While this class contains many 'public' static methods, it is
* primarily regarded as an internal class (its internal package name
* suggests this also). We value backwards compatibility of these
* methods when used within Groovy but value less backwards compatibility
* at the Java method call level. I.e. future versions of Groovy may
* remove or move a method call in this file but would normally
* aim to keep the method available from within Groovy.
*/
public class NioExtensions extends DefaultGroovyMethodsSupport {
/**
* Provide the standard Groovy <code>size()</code> method for <code>Path</code>.
*
* @param self a {@code Path} object
* @return the file's size (length)
* @since 2.3.0
*/
public static long size(Path self) throws IOException {
return Files.size(self);
}
/**
* Create an object output stream for this path.
*
* @param self a {@code Path} object
* @return an object output stream
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static ObjectOutputStream newObjectOutputStream(Path self) throws IOException {
return new ObjectOutputStream(Files.newOutputStream(self));
}
/**
* Create a new ObjectOutputStream for this path and then pass it to the
* closure. This method ensures the stream is closed after the closure
* returns.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see IOGroovyMethods#withStream(java.io.OutputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T withObjectOutputStream(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.ObjectOutputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newObjectOutputStream(self), closure);
}
/**
* Create an object input stream for this file.
*
* @param self a {@code Path} object
* @return an object input stream
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static ObjectInputStream newObjectInputStream(Path self) throws IOException {
return new ObjectInputStream(Files.newInputStream(self));
}
/**
* Create an object input stream for this path using the given class loader.
*
* @param self a {@code Path} object
* @param classLoader the class loader to use when loading the class
* @return an object input stream
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static ObjectInputStream newObjectInputStream(Path self, final ClassLoader classLoader) throws IOException {
return IOGroovyMethods.newObjectInputStream(Files.newInputStream(self), classLoader);
}
/**
* Iterates through the given file object by object.
*
* @param self a {@code Path} object
* @param closure a closure
* @throws java.io.IOException if an IOException occurs.
* @throws ClassNotFoundException if the class is not found.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#eachObject(java.io.ObjectInputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachObject(Path self, Closure closure) throws IOException, ClassNotFoundException {
IOGroovyMethods.eachObject(newObjectInputStream(self), closure);
}
/**
* Create a new ObjectInputStream for this file and pass it to the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param path a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.InputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T withObjectInputStream(Path path, @ClosureParams(value = SimpleType.class, options = "java.io.ObjectInputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newObjectInputStream(path), closure);
}
/**
* Create a new ObjectInputStream for this file associated with the given class loader and pass it to the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param self a Path
* @param classLoader the class loader to use when loading the class
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.InputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T withObjectInputStream(Path self, ClassLoader classLoader, @ClosureParams(value = SimpleType.class, options = "java.io.ObjectInputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newObjectInputStream(self, classLoader), closure);
}
/**
* Iterates through this path line by line. Each line is passed to the
* given 1 or 2 arg closure. The file is read using a reader which
* is closed before this method returns.
*
* @param self a Path
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see #eachLine(Path, int, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T eachLine(Path self, @ClosureParams(value = FromString.class, options = {"String", "String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(self, 1, closure);
}
/**
* Iterates through this file line by line. Each line is passed to the
* given 1 or 2 arg closure. The file is read using a reader which
* is closed before this method returns.
*
* @param self a Path
* @param charset opens the file with a specified charset
* @param closure a closure (arg 1 is line, optional arg 2 is line number starting at line 1)
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see #eachLine(Path, String, int, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T eachLine(Path self, String charset, @ClosureParams(value = FromString.class, options = {"String", "String,Integer"}) Closure<T> closure) throws IOException {
return eachLine(self, charset, 1, closure);
}
/**
* Iterates through this file line by line. Each line is passed
* to the given 1 or 2 arg closure. The file is read using a reader
* which is closed before this method returns.
*
* @param self a Path
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#eachLine(java.io.Reader, int, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T eachLine(Path self, int firstLine, @ClosureParams(value = FromString.class, options = {"String", "String,Integer"}) Closure<T> closure) throws IOException {
return IOGroovyMethods.eachLine(newReader(self), firstLine, closure);
}
/**
* Iterates through this file line by line. Each line is passed
* to the given 1 or 2 arg closure. The file is read using a reader
* which is closed before this method returns.
*
* @param self a Path
* @param charset opens the file with a specified charset
* @param firstLine the line number value used for the first line (default is 1, set to 0 to start counting from 0)
* @param closure a closure (arg 1 is line, optional arg 2 is line number)
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#eachLine(java.io.Reader, int, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T eachLine(Path self, String charset, int firstLine, @ClosureParams(value = FromString.class, options = {"String", "String,Integer"}) Closure<T> closure) throws IOException {
return IOGroovyMethods.eachLine(newReader(self, charset), firstLine, closure);
}
/**
* Iterates through this file line by line, splitting each line using
* the given regex separator. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression.
* Finally the resources used for processing the file are closed.
*
* @param self a Path
* @param regex the delimiting regular expression
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see org.codehaus.groovy.runtime.IOGroovyMethods#splitEachLine(java.io.Reader, String, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T splitEachLine(Path self, String regex, @ClosureParams(value = FromString.class, options = {"List<String>", "String[]"}, conflictResolutionStrategy = PickFirstResolver.class) Closure<T> closure) throws IOException {
return IOGroovyMethods.splitEachLine(newReader(self), regex, closure);
}
/**
* Iterates through this file line by line, splitting each line using
* the given separator Pattern. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression Pattern.
* Finally the resources used for processing the file are closed.
*
* @param self a Path
* @param pattern the regular expression Pattern for the delimiter
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#splitEachLine(java.io.Reader, java.util.regex.Pattern, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T splitEachLine(Path self, Pattern pattern, @ClosureParams(value = FromString.class, options = {"List<String>", "String[]"}, conflictResolutionStrategy = PickFirstResolver.class) Closure<T> closure) throws IOException {
return IOGroovyMethods.splitEachLine(newReader(self), pattern, closure);
}
/**
* Iterates through this file line by line, splitting each line using
* the given regex separator. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression.
* Finally the resources used for processing the file are closed.
*
* @param self a Path
* @param regex the delimiting regular expression
* @param charset opens the file with a specified charset
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @throws java.util.regex.PatternSyntaxException if the regular expression's syntax is invalid
* @see org.codehaus.groovy.runtime.IOGroovyMethods#splitEachLine(java.io.Reader, String, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T splitEachLine(Path self, String regex, String charset, @ClosureParams(value = FromString.class, options = {"List<String>", "String[]"}, conflictResolutionStrategy = PickFirstResolver.class) Closure<T> closure) throws IOException {
return IOGroovyMethods.splitEachLine(newReader(self, charset), regex, closure);
}
/**
* Iterates through this file line by line, splitting each line using
* the given regex separator Pattern. For each line, the given closure is called with
* a single parameter being the list of strings computed by splitting the line
* around matches of the given regular expression.
* Finally the resources used for processing the file are closed.
*
* @param self a Path
* @param pattern the regular expression Pattern for the delimiter
* @param charset opens the file with a specified charset
* @param closure a closure
* @return the last value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#splitEachLine(java.io.Reader, java.util.regex.Pattern, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T splitEachLine(Path self, Pattern pattern, String charset, @ClosureParams(value = FromString.class, options = {"List<String>", "String[]"}, conflictResolutionStrategy = PickFirstResolver.class) Closure<T> closure) throws IOException {
return IOGroovyMethods.splitEachLine(newReader(self, charset), pattern, closure);
}
/**
* Reads the file into a list of Strings, with one item for each line.
*
* @param self a Path
* @return a List of lines
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#readLines(java.io.Reader)
* @since 2.3.0
*/
public static List<String> readLines(Path self) throws IOException {
return IOGroovyMethods.readLines(newReader(self));
}
/**
* Reads the file into a list of Strings, with one item for each line.
*
* @param self a Path
* @param charset opens the file with a specified charset
* @return a List of lines
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#readLines(java.io.Reader)
* @since 2.3.0
*/
public static List<String> readLines(Path self, String charset) throws IOException {
return IOGroovyMethods.readLines(newReader(self, charset));
}
/**
* Read the content of the Path using the specified encoding and return it
* as a String.
*
* @param self the file whose content we want to read
* @param charset the charset used to read the content of the file
* @return a String containing the content of the file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static String getText(Path self, String charset) throws IOException {
return IOGroovyMethods.getText(newReader(self, charset));
}
/**
* Read the content of the Path and returns it as a String.
*
* @param self the file whose content we want to read
* @return a String containing the content of the file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static String getText(Path self) throws IOException {
return IOGroovyMethods.getText(newReader(self));
}
/**
* Read the content of the Path and returns it as a byte[].
*
* @param self the file whose content we want to read
* @return a String containing the content of the file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static byte[] getBytes(Path self) throws IOException {
return IOGroovyMethods.getBytes(Files.newInputStream(self));
}
/**
* Write the bytes from the byte array to the Path.
*
* @param self the file to write to
* @param bytes the byte[] to write to the file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void setBytes(Path self, byte[] bytes) throws IOException {
IOGroovyMethods.setBytes(Files.newOutputStream(self), bytes);
}
/**
* Write the text to the Path without writing a BOM .
*
* @param self a Path
* @param text the text to write to the Path
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void write(Path self, String text) throws IOException {
write(self, text, false);
}
/**
* Write the text to the Path. If the default charset is
* "UTF-16BE" or "UTF-16LE" (or an equivalent alias) and
* <code>writeBom</code> is <code>true</code>, the requisite byte order
* mark is written to the file before the text.
*
* @param self a Path
* @param text the text to write to the Path
* @param writeBom whether to write the BOM
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static void write(Path self, String text, boolean writeBom) throws IOException {
write(self, text, Charset.defaultCharset().name(), writeBom);
}
/**
* Synonym for write(text) allowing file.text = 'foo'.
*
* @param self a Path
* @param text the text to write to the Path
* @throws java.io.IOException if an IOException occurs.
* @see #write(Path, String)
* @since 2.3.0
*/
public static void setText(Path self, String text) throws IOException {
write(self, text);
}
/**
* Synonym for write(text, charset) allowing:
* <pre>
* myFile.setText('some text', charset)
* </pre>
* or with some help from <code>ExpandoMetaClass</code>, you could do something like:
* <pre>
* myFile.metaClass.setText = { String s {@code ->} delegate.setText(s, 'UTF-8') }
* myfile.text = 'some text'
* </pre>
*
* @param self A Path
* @param charset The charset used when writing to the file
* @param text The text to write to the Path
* @throws java.io.IOException if an IOException occurs.
* @see #write(Path, String, String)
* @since 2.3.0
*/
public static void setText(Path self, String text, String charset) throws IOException {
write(self, text, charset);
}
/**
* Write the text to the Path.
*
* @param self a Path
* @param text the text to write to the Path
* @return the original file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static Path leftShift(Path self, Object text) throws IOException {
append(self, text);
return self;
}
/**
* Write bytes to a Path.
*
* @param self a Path
* @param bytes the byte array to append to the end of the Path
* @return the original file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static Path leftShift(Path self, byte[] bytes) throws IOException {
append(self, bytes);
return self;
}
/**
* Append binary data to the file. See {@link #append(Path, java.io.InputStream)}
*
* @param path a Path
* @param data an InputStream of data to write to the file
* @return the file
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static Path leftShift(Path path, InputStream data) throws IOException {
append(path, data);
return path;
}
/**
* Write the text to the Path without writing a BOM, using the specified encoding.
*
* @param self a Path
* @param text the text to write to the Path
* @param charset the charset used
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void write(Path self, String text, String charset) throws IOException {
write(self, text, charset, false);
}
/**
* Write the text to the Path, using the specified encoding. If the given
* charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias) and
* <code>writeBom</code> is <code>true</code>, the requisite byte order
* mark is written to the file before the text.
*
* @param self a Path
* @param text the text to write to the Path
* @param charset the charset used
* @param writeBom whether to write a BOM
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static void write(Path self, String text, String charset, boolean writeBom) throws IOException {
Writer writer = null;
try {
OutputStream out = Files.newOutputStream(self);
if (writeBom) {
writeUTF16BomIfRequired(out, charset);
}
writer = new OutputStreamWriter(out, Charset.forName(charset));
writer.write(text);
writer.flush();
Writer temp = writer;
writer = null;
temp.close();
} finally {
closeWithWarning(writer);
}
}
/**
* Append the text at the end of the Path without writing a BOM.
*
* @param self a Path
* @param text the text to append at the end of the Path
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path self, Object text) throws IOException {
append(self, text, Charset.defaultCharset().name(), false);
}
/**
* Append the text supplied by the Writer at the end of the File without
* writing a BOM.
*
* @param file a Path
* @param reader the Reader supplying the text to append at the end of the File
* @throws IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path file, Reader reader) throws IOException {
append(file, reader, Charset.defaultCharset().name());
}
/**
* Append the text supplied by the Writer at the end of the File without writing a BOM.
*
* @param file a File
* @param writer the Writer supplying the text to append at the end of the File
* @throws IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path file, Writer writer) throws IOException {
append(file, writer, Charset.defaultCharset().name());
}
/**
* Append bytes to the end of a Path. It <strong>will not</strong> be
* interpreted as text.
*
* @param self a Path
* @param bytes the byte array to append to the end of the Path
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path self, byte[] bytes) throws IOException {
OutputStream stream = null;
try {
stream = Files.newOutputStream(self, CREATE, APPEND);
stream.write(bytes, 0, bytes.length);
stream.flush();
OutputStream temp = stream;
stream = null;
temp.close();
} finally {
closeWithWarning(stream);
}
}
/**
* Append binary data to the file. It <strong>will not</strong> be
* interpreted as text.
*
* @param self a Path
* @param stream stream to read data from.
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path self, InputStream stream) throws IOException {
OutputStream out = Files.newOutputStream(self, CREATE, APPEND);
try {
IOGroovyMethods.leftShift(out, stream);
} finally {
closeWithWarning(out);
}
}
/**
* Append the text at the end of the Path. If the default charset is
* "UTF-16BE" or "UTF-16LE" (or an equivalent alias) and
* <code>writeBom</code> is <code>true</code>, the requisite byte order
* mark is written to the file before the text.
*
* @param self a Path
* @param text the text to append at the end of the Path
* @param writeBom whether to write the BOM
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path self, Object text, boolean writeBom) throws IOException {
append(self, text, Charset.defaultCharset().name(), writeBom);
}
/**
* Append the text at the end of the Path without writing a BOM, using a specified
* encoding.
*
* @param self a Path
* @param text the text to append at the end of the Path
* @param charset the charset used
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path self, Object text, String charset) throws IOException {
append(self, text, charset, false);
}
/**
* Append the text at the end of the Path, using a specified encoding. If
* the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already
* exist, the requisite byte order mark is written to the file before the
* text is appended.
*
* @param self a Path
* @param text the text to append at the end of the Path
* @param charset the charset used
* @param writeBom whether to write the BOM
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path self, Object text, String charset, boolean writeBom) throws IOException {
Writer writer = null;
try {
Charset resolvedCharset = Charset.forName(charset);
boolean shouldWriteBom = writeBom && !self.toFile().exists();
OutputStream out = Files.newOutputStream(self, CREATE, APPEND);
if (shouldWriteBom) {
writeUTF16BomIfRequired(out, resolvedCharset);
}
writer = new OutputStreamWriter(out, resolvedCharset);
InvokerHelper.write(writer, text);
writer.flush();
Writer temp = writer;
writer = null;
temp.close();
} finally {
closeWithWarning(writer);
}
}
/**
* Append the text supplied by the Writer at the end of the File, using a specified encoding.
* If the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already
* exist, the requisite byte order mark is written to the file before the
* text is appended.
*
* @param file a File
* @param writer the Writer supplying the text to append at the end of the File
* @param writeBom whether to write the BOM
* @throws IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path file, Writer writer, boolean writeBom) throws IOException {
append(file, writer, Charset.defaultCharset().name(), writeBom);
}
/**
* Append the text supplied by the Writer at the end of the File without writing a BOM,
* using a specified encoding.
*
* @param file a File
* @param writer the Writer supplying the text to append at the end of the File
* @param charset the charset used
* @throws IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path file, Writer writer, String charset) throws IOException {
appendBuffered(file, writer, charset, false);
}
/**
* Append the text supplied by the Writer at the end of the File, using a specified encoding.
* If the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already
* exist, the requisite byte order mark is written to the file before the
* text is appended.
*
* @param file a File
* @param writer the Writer supplying the text to append at the end of the File
* @param charset the charset used
* @param writeBom whether to write the BOM
* @throws IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path file, Writer writer, String charset, boolean writeBom) throws IOException {
appendBuffered(file, writer, charset, writeBom);
}
/**
* Append the text supplied by the Reader at the end of the File, using a specified encoding.
* If the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already
* exist, the requisite byte order mark is written to the file before the
* text is appended.
*
* @param file a File
* @param reader the Reader supplying the text to append at the end of the File
* @param writeBom whether to write the BOM
* @throws IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path file, Reader reader, boolean writeBom) throws IOException {
appendBuffered(file, reader, Charset.defaultCharset().name(), writeBom);
}
/**
* Append the text supplied by the Reader at the end of the File without writing
* a BOM, using a specified encoding.
*
* @param file a File
* @param reader the Reader supplying the text to append at the end of the File
* @param charset the charset used
* @throws IOException if an IOException occurs.
* @since 2.3.0
*/
public static void append(Path file, Reader reader, String charset) throws IOException {
append(file, reader, charset, false);
}
/**
* Append the text supplied by the Reader at the end of the File, using a specified encoding.
* If the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already
* exist, the requisite byte order mark is written to the file before the
* text is appended.
*
* @param file a File
* @param reader the Reader supplying the text to append at the end of the File
* @param charset the charset used
* @param writeBom whether to write the BOM
* @throws IOException if an IOException occurs.
* @since 2.5.0
*/
public static void append(Path file, Reader reader, String charset, boolean writeBom) throws IOException {
appendBuffered(file, reader, charset, writeBom);
}
private static void appendBuffered(Path file, Object text, String charset, boolean writeBom) throws IOException {
BufferedWriter writer = null;
try {
boolean shouldWriteBom = writeBom && !file.toFile().exists();
writer = newWriter(file, charset, true);
if (shouldWriteBom) {
writeUTF16BomIfRequired(writer, charset);
}
InvokerHelper.write(writer, text);
writer.flush();
Writer temp = writer;
writer = null;
temp.close();
} finally {
closeWithWarning(writer);
}
}
/**
* This method is used to throw useful exceptions when the eachFile* and eachDir closure methods
* are used incorrectly.
*
* @param self The directory to check
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @since 2.3.0
*/
private static void checkDir(Path self) throws FileNotFoundException, IllegalArgumentException {
if (!Files.exists(self))
throw new FileNotFoundException(self.toAbsolutePath().toString());
if (!Files.isDirectory(self))
throw new IllegalArgumentException("The provided Path object is not a directory: " + self.toAbsolutePath());
}
/**
* Invokes the closure for each 'child' file in this 'parent' folder/directory.
* Both regular files and subfolders/subdirectories can be processed depending
* on the fileType enum value.
*
* @param self a Path (that happens to be a folder/directory)
* @param fileType if normal files or directories or both should be processed
* @param closure the closure to invoke
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @since 2.3.0
*/
public static void eachFile(final Path self, final FileType fileType, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException {
//throws FileNotFoundException, IllegalArgumentException {
checkDir(self);
// TODO GroovyDoc doesn't parse this file as our java.g doesn't handle this JDK7 syntax
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
for (Path path : stream) {
if (fileType == FileType.ANY ||
(fileType != FileType.FILES && Files.isDirectory(path)) ||
(fileType != FileType.DIRECTORIES && Files.isRegularFile(path))) {
closure.call(path);
}
}
}
}
/**
* Invokes the closure for each 'child' file in this 'parent' folder/directory.
* Both regular files and subfolders/subdirectories are processed.
*
* @param self a Path (that happens to be a folder/directory)
* @param closure a closure (the parameter is the Path for the 'child' file)
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFile(Path, groovy.io.FileType, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachFile(final Path self, final Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
eachFile(self, FileType.ANY, closure);
}
/**
* Invokes the closure for each subdirectory in this directory,
* ignoring regular files.
*
* @param self a Path (that happens to be a folder/directory)
* @param closure a closure (the parameter is the Path for the subdirectory file)
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFile(Path, groovy.io.FileType, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachDir(Path self, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
eachFile(self, FileType.DIRECTORIES, closure);
}
/**
* Processes each descendant file in this directory and any sub-directories.
* Processing consists of potentially calling <code>closure</code> passing it the current
* file (which may be a normal file or subdirectory) and then if a subdirectory was encountered,
* recursively processing the subdirectory. Whether the closure is called is determined by whether
* the file was a normal file or subdirectory and the value of fileType.
*
* @param self a Path (that happens to be a folder/directory)
* @param fileType if normal files or directories or both should be processed
* @param closure the closure to invoke on each file
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @since 2.3.0
*/
public static void eachFileRecurse(final Path self, final FileType fileType, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
// throws FileNotFoundException, IllegalArgumentException {
checkDir(self);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
for (Path path : stream) {
if (Files.isDirectory(path)) {
if (fileType != FileType.FILES) closure.call(path);
eachFileRecurse(path, fileType, closure);
} else if (fileType != FileType.DIRECTORIES) {
closure.call(path);
}
}
}
}
/**
* Processes each descendant file in this directory and any sub-directories.
* Processing consists of potentially calling <code>closure</code> passing it the current
* file (which may be a normal file or subdirectory) and then if a subdirectory was encountered,
* recursively processing the subdirectory.
* <p>
* The traversal can be adapted by providing various options in the <code>options</code> Map according
* to the following keys:<dl>
* <dt>type</dt><dd>A {@link groovy.io.FileType} enum to determine if normal files or directories or both are processed</dd>
* <dt>preDir</dt><dd>A {@link groovy.lang.Closure} run before each directory is processed and optionally returning a {@link groovy.io.FileVisitResult} value
* which can be used to control subsequent processing.</dd>
* <dt>preRoot</dt><dd>A boolean indicating that the 'preDir' closure should be applied at the root level</dd>
* <dt>postDir</dt><dd>A {@link groovy.lang.Closure} run after each directory is processed and optionally returning a {@link groovy.io.FileVisitResult} value
* which can be used to control subsequent processing.</dd>
* <dt>postRoot</dt><dd>A boolean indicating that the 'postDir' closure should be applied at the root level</dd>
* <dt>visitRoot</dt><dd>A boolean indicating that the given closure should be applied for the root dir
* (not applicable if the 'type' is set to {@link groovy.io.FileType#FILES})</dd>
* <dt>maxDepth</dt><dd>The maximum number of directory levels when recursing
* (default is -1 which means infinite, set to 0 for no recursion)</dd>
* <dt>filter</dt><dd>A filter to perform on traversed files/directories (using the {@link DefaultGroovyMethods#isCase(Object, Object)} method). If set,
* only files/dirs which match are candidates for visiting.</dd>
* <dt>nameFilter</dt><dd>A filter to perform on the name of traversed files/directories (using the {@link DefaultGroovyMethods#isCase(Object, Object)} method). If set,
* only files/dirs which match are candidates for visiting. (Must not be set if 'filter' is set)</dd>
* <dt>excludeFilter</dt><dd>A filter to perform on traversed files/directories (using the {@link DefaultGroovyMethods#isCase(Object, Object)} method).
* If set, any candidates which match won't be visited.</dd>
* <dt>excludeNameFilter</dt><dd>A filter to perform on the names of traversed files/directories (using the {@link DefaultGroovyMethods#isCase(Object, Object)} method).
* If set, any candidates which match won't be visited. (Must not be set if 'excludeFilter' is set)</dd>
* <dt>sort</dt><dd>A {@link groovy.lang.Closure} which if set causes the files and subdirectories for each directory to be processed in sorted order.
* Note that even when processing only files, the order of visited subdirectories will be affected by this parameter.</dd>
* </dl>
* This example prints out file counts and size aggregates for groovy source files within a directory tree:
* <pre>
* def totalSize = 0
* def count = 0
* def sortByTypeThenName = { a, b {@code ->}
* a.isFile() != b.isFile() ? a.isFile() {@code <=>} b.isFile() : a.name {@code <=>} b.name
* }
* rootDir.traverse(
* type : FILES,
* nameFilter : ~/.*\.groovy/,
* preDir : { if (it.name == '.svn') return SKIP_SUBTREE },
* postDir : { println "Found $count files in $it.name totalling $totalSize bytes"
* totalSize = 0; count = 0 },
* postRoot : true
* sort : sortByTypeThenName
* ) {it {@code ->} totalSize += it.size(); count++ }
* </pre>
*
* @param self a Path (that happens to be a folder/directory)
* @param options a Map of options to alter the traversal behavior
* @param closure the Closure to invoke on each file/directory and optionally returning a {@link groovy.io.FileVisitResult} value
* which can be used to control subsequent processing
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory or illegal filter combinations are supplied
* @see DefaultGroovyMethods#sort(java.lang.Iterable, groovy.lang.Closure)
* @see groovy.io.FileVisitResult
* @see groovy.io.FileType
* @since 2.3.0
*/
public static void traverse(final Path self, final Map<String, Object> options, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure)
throws IOException {
// throws FileNotFoundException, IllegalArgumentException {
Number maxDepthNumber = DefaultGroovyMethods.asType(options.remove("maxDepth"), Number.class);
int maxDepth = maxDepthNumber == null ? -1 : maxDepthNumber.intValue();
Boolean visitRoot = DefaultGroovyMethods.asType(get(options, "visitRoot", false), Boolean.class);
Boolean preRoot = DefaultGroovyMethods.asType(get(options, "preRoot", false), Boolean.class);
Boolean postRoot = DefaultGroovyMethods.asType(get(options, "postRoot", false), Boolean.class);
final Closure pre = (Closure) options.get("preDir");
final Closure post = (Closure) options.get("postDir");
final FileType type = (FileType) options.get("type");
final Object filter = options.get("filter");
final Object nameFilter = options.get("nameFilter");
final Object excludeFilter = options.get("excludeFilter");
final Object excludeNameFilter = options.get("excludeNameFilter");
Object preResult = null;
if (preRoot && pre != null) {
preResult = pre.call(self);
}
if (preResult == FileVisitResult.TERMINATE ||
preResult == FileVisitResult.SKIP_SUBTREE) return;
FileVisitResult terminated = traverse(self, options, closure, maxDepth);
if (type != FileType.FILES && visitRoot) {
if (closure != null && notFiltered(self, filter, nameFilter, excludeFilter, excludeNameFilter)) {
Object closureResult = closure.call(self);
if (closureResult == FileVisitResult.TERMINATE) return;
}
}
if (postRoot && post != null && terminated != FileVisitResult.TERMINATE) post.call(self);
}
private static boolean notFiltered(Path path, Object filter, Object nameFilter, Object excludeFilter, Object excludeNameFilter) {
if (filter == null && nameFilter == null && excludeFilter == null && excludeNameFilter == null) return true;
if (filter != null && nameFilter != null)
throw new IllegalArgumentException("Can't set both 'filter' and 'nameFilter'");
if (excludeFilter != null && excludeNameFilter != null)
throw new IllegalArgumentException("Can't set both 'excludeFilter' and 'excludeNameFilter'");
Object filterToUse = null;
Object filterParam = null;
if (filter != null) {
filterToUse = filter;
filterParam = path;
} else if (nameFilter != null) {
filterToUse = nameFilter;
filterParam = path.getFileName().toString();
}
Object excludeFilterToUse = null;
Object excludeParam = null;
if (excludeFilter != null) {
excludeFilterToUse = excludeFilter;
excludeParam = path;
} else if (excludeNameFilter != null) {
excludeFilterToUse = excludeNameFilter;
excludeParam = path.getFileName().toString();
}
final MetaClass filterMC = filterToUse == null ? null : InvokerHelper.getMetaClass(filterToUse);
final MetaClass excludeMC = excludeFilterToUse == null ? null : InvokerHelper.getMetaClass(excludeFilterToUse);
boolean included = filterToUse == null || DefaultTypeTransformation.castToBoolean(filterMC.invokeMethod(filterToUse, "isCase", filterParam));
boolean excluded = excludeFilterToUse != null && DefaultTypeTransformation.castToBoolean(excludeMC.invokeMethod(excludeFilterToUse, "isCase", excludeParam));
return included && !excluded;
}
/**
* Processes each descendant file in this directory and any sub-directories.
* Convenience method for {@link #traverse(Path, java.util.Map, groovy.lang.Closure)} when
* no options to alter the traversal behavior are required.
*
* @param self a Path (that happens to be a folder/directory)
* @param closure the Closure to invoke on each file/directory and optionally returning a {@link groovy.io.FileVisitResult} value
* which can be used to control subsequent processing
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #traverse(Path, java.util.Map, groovy.lang.Closure)
* @since 2.3.0
*/
public static void traverse(final Path self, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure)
throws IOException {
traverse(self, new HashMap<>(), closure);
}
/**
* Invokes the closure specified with key 'visit' in the options Map
* for each descendant file in this directory tree. Convenience method
* for {@link #traverse(Path, java.util.Map, groovy.lang.Closure)} allowing the 'visit' closure
* to be included in the options Map rather than as a parameter.
*
* @param self a Path (that happens to be a folder/directory)
* @param options a Map of options to alter the traversal behavior
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory or illegal filter combinations are supplied
* @see #traverse(Path, java.util.Map, groovy.lang.Closure)
* @since 2.3.0
*/
public static void traverse(final Path self, final Map<String, Object> options)
throws IOException {
final Closure visit = (Closure) options.remove("visit");
traverse(self, options, visit);
}
private static FileVisitResult traverse(final Path self, final Map<String, Object> options, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure, final int maxDepth)
throws IOException {
checkDir(self);
final Closure pre = (Closure) options.get("preDir");
final Closure post = (Closure) options.get("postDir");
final FileType type = (FileType) options.get("type");
final Object filter = options.get("filter");
final Object nameFilter = options.get("nameFilter");
final Object excludeFilter = options.get("excludeFilter");
final Object excludeNameFilter = options.get("excludeNameFilter");
final Closure sort = (Closure) options.get("sort");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
final Iterator<Path> itr = stream.iterator();
List<Path> files = new LinkedList<>();
while (itr.hasNext()) {
files.add(itr.next());
}
if (sort != null) files = DefaultGroovyMethods.sort((Iterable<Path>) files, sort);
for (Path path : files) {
if (Files.isDirectory(path)) {
if (type != FileType.FILES) {
if (closure != null && notFiltered(path, filter, nameFilter, excludeFilter, excludeNameFilter)) {
Object closureResult = closure.call(path);
if (closureResult == FileVisitResult.SKIP_SIBLINGS) break;
if (closureResult == FileVisitResult.TERMINATE) return FileVisitResult.TERMINATE;
}
}
if (maxDepth != 0) {
Object preResult = null;
if (pre != null) {
preResult = pre.call(path);
}
if (preResult == FileVisitResult.SKIP_SIBLINGS) break;
if (preResult == FileVisitResult.TERMINATE) return FileVisitResult.TERMINATE;
if (preResult != FileVisitResult.SKIP_SUBTREE) {
FileVisitResult terminated = traverse(path, options, closure, maxDepth - 1);
if (terminated == FileVisitResult.TERMINATE) return terminated;
}
Object postResult = null;
if (post != null) {
postResult = post.call(path);
}
if (postResult == FileVisitResult.SKIP_SIBLINGS) break;
if (postResult == FileVisitResult.TERMINATE) return FileVisitResult.TERMINATE;
}
} else if (type != FileType.DIRECTORIES) {
if (closure != null && notFiltered(path, filter, nameFilter, excludeFilter, excludeNameFilter)) {
Object closureResult = closure.call(path);
if (closureResult == FileVisitResult.SKIP_SIBLINGS) break;
if (closureResult == FileVisitResult.TERMINATE) return FileVisitResult.TERMINATE;
}
}
}
return FileVisitResult.CONTINUE;
}
}
/**
* Processes each descendant file in this directory and any sub-directories.
* Processing consists of calling <code>closure</code> passing it the current
* file (which may be a normal file or subdirectory) and then if a subdirectory was encountered,
* recursively processing the subdirectory.
*
* @param self a Path (that happens to be a folder/directory)
* @param closure a Closure
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFileRecurse(Path, groovy.io.FileType, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachFileRecurse(Path self, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
eachFileRecurse(self, FileType.ANY, closure);
}
/**
* Recursively processes each descendant subdirectory in this directory.
* Processing consists of calling <code>closure</code> passing it the current
* subdirectory and then recursively processing that subdirectory.
* Regular files are ignored during traversal.
*
* @param self a Path (that happens to be a folder/directory)
* @param closure a closure
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFileRecurse(Path, groovy.io.FileType, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachDirRecurse(final Path self, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException { //throws FileNotFoundException, IllegalArgumentException {
eachFileRecurse(self, FileType.DIRECTORIES, closure);
}
/**
* Invokes the closure for each file whose name (file.name) matches the given nameFilter in the given directory
* - calling the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#isCase(Object, Object)} method to determine if a match occurs. This method can be used
* with different kinds of filters like regular expressions, classes, ranges etc.
* Both regular files and subdirectories may be candidates for matching depending
* on the value of fileType.
* <pre>
* // collect names of files in baseDir matching supplied regex pattern
* import static groovy.io.FileType.*
* def names = []
* baseDir.eachFileMatch FILES, ~/foo\d\.txt/, { names {@code <<} it.name }
* assert names == ['foo1.txt', 'foo2.txt']
*
* // remove all *.bak files in baseDir
* baseDir.eachFileMatch FILES, ~/.*\.bak/, { Path bak {@code ->} bak.delete() }
*
* // print out files &gt; 4K in size from baseDir
* baseDir.eachFileMatch FILES, { new Path(baseDir, it).size() {@code >} 4096 }, { println "$it.name ${it.size()}" }
* </pre>
*
* @param self a Path (that happens to be a folder/directory)
* @param fileType whether normal files or directories or both should be processed
* @param nameFilter the filter to perform on the name of the file/directory (using the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#isCase(Object, Object)} method)
* @param closure the closure to invoke
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @since 2.3.0
*/
public static void eachFileMatch(final Path self, final FileType fileType, final Object nameFilter, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException {
// throws FileNotFoundException, IllegalArgumentException {
checkDir(self);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
Iterator<Path> itr = stream.iterator();
BooleanReturningMethodInvoker bmi = new BooleanReturningMethodInvoker("isCase");
while (itr.hasNext()) {
Path currentPath = itr.next();
if ((fileType != FileType.FILES && Files.isDirectory(currentPath)) ||
(fileType != FileType.DIRECTORIES && Files.isRegularFile(currentPath))) {
if (bmi.invoke(nameFilter, currentPath.getFileName().toString()))
closure.call(currentPath);
}
}
}
}
/**
* Invokes the closure for each file whose name (file.name) matches the given nameFilter in the given directory
* - calling the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#isCase(Object, Object)} method to determine if a match occurs. This method can be used
* with different kinds of filters like regular expressions, classes, ranges etc.
* Both regular files and subdirectories are matched.
*
* @param self a Path (that happens to be a folder/directory)
* @param nameFilter the nameFilter to perform on the name of the file (using the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#isCase(Object, Object)} method)
* @param closure the closure to invoke
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFileMatch(Path, groovy.io.FileType, Object, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachFileMatch(final Path self, final Object nameFilter, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException {
// throws FileNotFoundException, IllegalArgumentException {
eachFileMatch(self, FileType.ANY, nameFilter, closure);
}
/**
* Invokes the closure for each subdirectory whose name (dir.name) matches the given nameFilter in the given directory
* - calling the {@link DefaultGroovyMethods#isCase(java.lang.Object, java.lang.Object)} method to determine if a match occurs. This method can be used
* with different kinds of filters like regular expressions, classes, ranges etc.
* Only subdirectories are matched; regular files are ignored.
*
* @param self a Path (that happens to be a folder/directory)
* @param nameFilter the nameFilter to perform on the name of the directory (using the {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#isCase(Object, Object)} method)
* @param closure the closure to invoke
* @throws java.io.FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
* @see #eachFileMatch(Path, groovy.io.FileType, Object, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachDirMatch(final Path self, final Object nameFilter, @ClosureParams(value = SimpleType.class, options = "java.nio.file.Path") final Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
eachFileMatch(self, FileType.DIRECTORIES, nameFilter, closure);
}
/**
* Deletes a directory with all contained files and subdirectories.
* <p>The method returns
* <ul>
* <li>true, when deletion was successful</li>
* <li>true, when it is called for a non existing directory</li>
* <li>false, when it is called for a file which isn't a directory</li>
* <li>false, when directory couldn't be deleted</li>
* </ul>
* </p>
*
* @param self a Path
* @return true if the file doesn't exist or deletion was successful
* @since 2.3.0
*/
public static boolean deleteDir(final Path self) {
if (!Files.exists(self))
return true;
if (!Files.isDirectory(self))
return false;
// delete contained files
try (DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
for (Path path : stream) {
if (Files.isDirectory(path)) {
if (!deleteDir(path)) {
return false;
}
} else {
Files.delete(path);
}
}
// now delete directory itself
Files.delete(self);
return true;
} catch (IOException e) {
return false;
}
}
/**
* Renames a file.
*
* @param self a Path
* @param newPathName The new pathname for the named file
* @return <code>true</code> if and only if the renaming succeeded;
* <code>false</code> otherwise
* @since 2.3.0
*/
public static boolean renameTo(final Path self, String newPathName) {
try {
Files.move(self, Paths.get(newPathName));
return true;
} catch (IOException e) {
return false;
}
}
/**
* Renames a file.
*
* @param self a Path
* @param newPathName The new target path specified as a URI object
* @return <code>true</code> if and only if the renaming succeeded;
* <code>false</code> otherwise
* @since 2.3.0
*/
public static boolean renameTo(final Path self, URI newPathName) {
try {
Files.move(self, Paths.get(newPathName));
return true;
} catch (IOException e) {
return false;
}
}
/**
* Converts this Path to a {@link groovy.lang.Writable}.
*
* @param self a Path
* @return a Path which wraps the input file and which implements Writable
* @since 2.3.0
*/
public static Path asWritable(Path self) {
return new WritablePath(self);
}
/**
* Converts this Path to a {@link groovy.lang.Writable} or delegates to default
* {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#asType(Object, Class)}.
*
* @param path a Path
* @param c the desired class
* @return the converted object
* @since 2.3.0
*/
@SuppressWarnings("unchecked")
public static <T> T asType(Path path, Class<T> c) {
if (c == Writable.class) {
return (T) asWritable(path);
}
return DefaultGroovyMethods.asType((Object) path, c);
}
/**
* Allows a file to return a Writable implementation that can output itself
* to a Writer stream.
*
* @param self a Path
* @param encoding the encoding to be used when reading the file's contents
* @return Path which wraps the input file and which implements Writable
* @since 2.3.0
*/
public static Path asWritable(Path self, String encoding) {
return new WritablePath(self, encoding);
}
/**
* Create a buffered reader for this file.
*
* @param self a Path
* @return a BufferedReader
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedReader newReader(Path self) throws IOException {
return Files.newBufferedReader(self, Charset.defaultCharset());
}
/**
* Create a buffered reader for this file, using the specified
* charset as the encoding.
*
* @param self a Path
* @param charset the charset for this Path
* @return a BufferedReader
* @throws java.io.FileNotFoundException if the Path was not found
* @throws java.io.UnsupportedEncodingException if the encoding specified is not supported
* @since 2.3.0
*/
public static BufferedReader newReader(Path self, String charset) throws IOException {
return Files.newBufferedReader(self, Charset.forName(charset));
}
/**
* Create a new BufferedReader for this file and then
* passes it into the closure, ensuring the reader is closed after the
* closure returns.
*
* @param self a file object
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withReader(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.Reader") Closure<T> closure) throws IOException {
return IOGroovyMethods.withReader(newReader(self), closure);
}
/**
* Create a new BufferedReader for this file using the specified charset and then
* passes it into the closure, ensuring the reader is closed after the
* closure returns. The writer will use the given charset encoding,
* but will not write a BOM.
*
* @param self a file object
* @param charset the charset for this input stream
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withReader(Path self, String charset, @ClosureParams(value = SimpleType.class, options = "java.io.Reader") Closure<T> closure) throws IOException {
return IOGroovyMethods.withReader(newReader(self, charset), closure);
}
/**
* Create a buffered output stream for this file.
*
* @param self a file object
* @return the created OutputStream
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedOutputStream newOutputStream(Path self) throws IOException {
return new BufferedOutputStream(Files.newOutputStream(self));
}
/**
* Creates a new data output stream for this file.
*
* @param self a file object
* @return the created DataOutputStream
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static DataOutputStream newDataOutputStream(Path self) throws IOException {
return new DataOutputStream(Files.newOutputStream(self));
}
/**
* Creates a new OutputStream for this file and passes it into the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.OutputStream, groovy.lang.Closure)
* @since 2.3.0
*/
@SuppressWarnings("unchecked")
public static <T> T withOutputStream(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.OutputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newOutputStream(self), closure);
}
/**
* Create a new InputStream for this file and passes it into the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.InputStream, groovy.lang.Closure)
* @since 2.3.0
*/
@SuppressWarnings("unchecked")
public static <T> T withInputStream(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.InputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newInputStream(self), closure);
}
/**
* Create a new DataOutputStream for this file and passes it into the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.OutputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T withDataOutputStream(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.DataOutputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newDataOutputStream(self), closure);
}
/**
* Create a new DataInputStream for this file and passes it into the closure.
* This method ensures the stream is closed after the closure returns.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withStream(java.io.InputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static <T> T withDataInputStream(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.DataInputStream") Closure<T> closure) throws IOException {
return IOGroovyMethods.withStream(newDataInputStream(self), closure);
}
/**
* Create a buffered writer for this file.
*
* @param self a Path
* @return a BufferedWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedWriter newWriter(Path self) throws IOException {
return Files.newBufferedWriter(self, Charset.defaultCharset());
}
/**
* Creates a buffered writer for this file, optionally appending to the
* existing file content.
*
* @param self a Path
* @param append true if data should be appended to the file
* @return a BufferedWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedWriter newWriter(Path self, boolean append) throws IOException {
if (append) {
return Files.newBufferedWriter(self, Charset.defaultCharset(), CREATE, APPEND);
}
return Files.newBufferedWriter(self, Charset.defaultCharset());
}
/**
* Helper method to create a buffered writer for a file without writing a BOM.
*
* @param self a Path
* @param charset the name of the encoding used to write in this file
* @param append true if in append mode
* @return a BufferedWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedWriter newWriter(Path self, String charset, boolean append) throws IOException {
return newWriter(self, charset, append, false);
}
/**
* Helper method to create a buffered writer for a file. If the given
* charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias), the
* requisite byte order mark is written to the stream before the writer
* is returned.
*
* @param self a Path
* @param charset the name of the encoding used to write in this file
* @param append true if in append mode
* @param writeBom whether to write a BOM
* @return a BufferedWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static BufferedWriter newWriter(Path self, String charset, boolean append, boolean writeBom) throws IOException {
boolean shouldWriteBom = writeBom && !self.toFile().exists();
if (append) {
BufferedWriter writer = Files.newBufferedWriter(self, Charset.forName(charset), CREATE, APPEND);
if (shouldWriteBom) {
writeUTF16BomIfRequired(writer, charset);
}
return writer;
} else {
OutputStream out = Files.newOutputStream(self);
if (shouldWriteBom) {
writeUTF16BomIfRequired(out, charset);
}
return new BufferedWriter(new OutputStreamWriter(out, Charset.forName(charset)));
}
}
/**
* Creates a buffered writer for this file without writing a BOM, writing data using the given
* encoding.
*
* @param self a Path
* @param charset the name of the encoding used to write in this file
* @return a BufferedWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static BufferedWriter newWriter(Path self, String charset) throws IOException {
return newWriter(self, charset, false);
}
/**
* Creates a new BufferedWriter for this file, passes it to the closure, and
* ensures the stream is flushed and closed after the closure returns.
* The writer will not write a BOM.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withWriter(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return withWriter(self, Charset.defaultCharset().name(), closure);
}
/**
* Creates a new BufferedWriter for this file, passes it to the closure, and
* ensures the stream is flushed and closed after the closure returns.
* The writer will use the given charset encoding, but will not write a BOM.
*
* @param self a Path
* @param charset the charset used
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withWriter(Path self, String charset, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return withWriter(self, charset, false, closure);
}
/**
* Creates a new BufferedWriter for this file, passes it to the closure, and
* ensures the stream is flushed and closed after the closure returns.
* The writer will use the given charset encoding. If the given charset is
* "UTF-16BE" or "UTF-16LE" (or an equivalent alias), <code>writeBom</code>
* is <code>true</code>, and the file doesn't already exist, the requisite
* byte order mark is written to the stream when the writer is created.
*
* @param self a Path
* @param charset the charset used
* @param writeBom whether to write the BOM
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static <T> T withWriter(Path self, String charset, boolean writeBom, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return IOGroovyMethods.withWriter(newWriter(self, charset, false, writeBom), closure);
}
/**
* Create a new BufferedWriter which will append to this
* file. The writer is passed to the closure and will be closed before
* this method returns. The writer will use the given charset encoding,
* but will not write a BOM.
*
* @param self a Path
* @param charset the charset used
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withWriterAppend(Path self, String charset, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return withWriterAppend(self, charset, false, closure);
}
/**
* Create a new BufferedWriter which will append to this
* file. The writer is passed to the closure and will be closed before
* this method returns. The writer will use the given charset encoding.
* If the given charset is "UTF-16BE" or "UTF-16LE" (or an equivalent alias),
* <code>writeBom</code> is <code>true</code>, and the file doesn't already exist,
* the requisite byte order mark is written to the stream when the writer is created.
*
* @param self a Path
* @param charset the charset used
* @param writeBom whether to write the BOM
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.5.0
*/
public static <T> T withWriterAppend(Path self, String charset, boolean writeBom, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return IOGroovyMethods.withWriter(newWriter(self, charset, true, writeBom), closure);
}
/**
* Create a new BufferedWriter for this file in append mode. The writer
* is passed to the closure and is closed after the closure returns.
* The writer will not write a BOM.
*
* @param self a Path
* @param closure a closure
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withWriterAppend(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.Writer") Closure<T> closure) throws IOException {
return withWriterAppend(self, Charset.defaultCharset().name(), closure);
}
/**
* Create a new PrintWriter for this file.
*
* @param self a Path
* @return the created PrintWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static PrintWriter newPrintWriter(Path self) throws IOException {
return new GroovyPrintWriter(newWriter(self));
}
/**
* Create a new PrintWriter for this file, using specified
* charset.
*
* @param self a Path
* @param charset the charset
* @return a PrintWriter
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static PrintWriter newPrintWriter(Path self, String charset) throws IOException {
return new GroovyPrintWriter(newWriter(self, charset));
}
/**
* Create a new PrintWriter for this file which is then
* passed it into the given closure. This method ensures its the writer
* is closed after the closure returns.
*
* @param self a Path
* @param closure the closure to invoke with the PrintWriter
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withPrintWriter(Path self, @ClosureParams(value = SimpleType.class, options = "java.io.PrintWriter") Closure<T> closure) throws IOException {
return IOGroovyMethods.withWriter(newPrintWriter(self), closure);
}
/**
* Create a new PrintWriter with a specified charset for
* this file. The writer is passed to the closure, and will be closed
* before this method returns.
*
* @param self a Path
* @param charset the charset
* @param closure the closure to invoke with the PrintWriter
* @return the value returned by the closure
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static <T> T withPrintWriter(Path self, String charset, @ClosureParams(value = SimpleType.class, options = "java.io.PrintWriter") Closure<T> closure) throws IOException {
return IOGroovyMethods.withWriter(newPrintWriter(self, charset), closure);
}
/**
* Creates a buffered input stream for this file.
*
* @param self a Path
* @return a BufferedInputStream of the file
* @throws java.io.FileNotFoundException if the file is not found.
* @since 2.3.0
*/
public static BufferedInputStream newInputStream(Path self) throws IOException { // throws FileNotFoundException {
return new BufferedInputStream(Files.newInputStream(self));
}
/**
* Create a data input stream for this file
*
* @param self a Path
* @return a DataInputStream of the file
* @throws java.io.FileNotFoundException if the file is not found.
* @since 2.3.0
*/
public static DataInputStream newDataInputStream(Path self) throws IOException { // throws FileNotFoundException {
return new DataInputStream(Files.newInputStream(self));
}
/**
* Traverse through each byte of this Path
*
* @param self a Path
* @param closure a closure
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#eachByte(java.io.InputStream, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachByte(Path self, @ClosureParams(value = SimpleType.class, options = "byte") Closure closure) throws IOException {
BufferedInputStream is = newInputStream(self);
IOGroovyMethods.eachByte(is, closure);
}
/**
* Traverse through the bytes of this Path, bufferLen bytes at a time.
*
* @param self a Path
* @param bufferLen the length of the buffer to use.
* @param closure a 2 parameter closure which is passed the byte[] and a number of bytes successfully read.
* @throws java.io.IOException if an IOException occurs.
* @see org.codehaus.groovy.runtime.IOGroovyMethods#eachByte(java.io.InputStream, int, groovy.lang.Closure)
* @since 2.3.0
*/
public static void eachByte(Path self, int bufferLen, @ClosureParams(value = FromString.class, options = "byte[],Integer") Closure closure) throws IOException {
BufferedInputStream is = newInputStream(self);
IOGroovyMethods.eachByte(is, bufferLen, closure);
}
/**
* Filters the lines of a Path and creates a Writable in return to
* stream the filtered lines.
*
* @param self a Path
* @param closure a closure which returns a boolean indicating to filter
* the line or not
* @return a Writable closure
* @throws java.io.IOException if <code>self</code> is not readable
* @see org.codehaus.groovy.runtime.IOGroovyMethods#filterLine(java.io.Reader, groovy.lang.Closure)
* @since 2.3.0
*/
public static Writable filterLine(Path self, @ClosureParams(value = SimpleType.class, options = "java.lang.String") Closure closure) throws IOException {
return IOGroovyMethods.filterLine(newReader(self), closure);
}
/**
* Filters the lines of a Path and creates a Writable in return to
* stream the filtered lines.
*
* @param self a Path
* @param charset opens the file with a specified charset
* @param closure a closure which returns a boolean indicating to filter
* the line or not
* @return a Writable closure
* @throws java.io.IOException if an IOException occurs
* @see org.codehaus.groovy.runtime.IOGroovyMethods#filterLine(java.io.Reader, groovy.lang.Closure)
* @since 2.3.0
*/
public static Writable filterLine(Path self, String charset, @ClosureParams(value = SimpleType.class, options = "java.lang.String") Closure closure) throws IOException {
return IOGroovyMethods.filterLine(newReader(self, charset), closure);
}
/**
* Filter the lines from this Path, and write them to the given writer based
* on the given closure predicate.
*
* @param self a Path
* @param writer a writer destination to write filtered lines to
* @param closure a closure which takes each line as a parameter and returns
* <code>true</code> if the line should be written to this writer.
* @throws java.io.IOException if <code>self</code> is not readable
* @see org.codehaus.groovy.runtime.IOGroovyMethods#filterLine(java.io.Reader, java.io.Writer, groovy.lang.Closure)
* @since 2.3.0
*/
public static void filterLine(Path self, Writer writer, @ClosureParams(value = SimpleType.class, options = "java.lang.String") Closure closure) throws IOException {
IOGroovyMethods.filterLine(newReader(self), writer, closure);
}
/**
* Filter the lines from this Path, and write them to the given writer based
* on the given closure predicate.
*
* @param self a Path
* @param writer a writer destination to write filtered lines to
* @param charset opens the file with a specified charset
* @param closure a closure which takes each line as a parameter and returns
* <code>true</code> if the line should be written to this writer.
* @throws java.io.IOException if an IO error occurs
* @see org.codehaus.groovy.runtime.IOGroovyMethods#filterLine(java.io.Reader, java.io.Writer, groovy.lang.Closure)
* @since 2.3.0
*/
public static void filterLine(Path self, Writer writer, String charset, @ClosureParams(value = SimpleType.class, options = "java.lang.String") Closure closure) throws IOException {
IOGroovyMethods.filterLine(newReader(self, charset), writer, closure);
}
/**
* Reads the content of the file into a byte array.
*
* @param self a Path
* @return a byte array with the contents of the file.
* @throws java.io.IOException if an IOException occurs.
* @since 2.3.0
*/
public static byte[] readBytes(Path self) throws IOException {
return Files.readAllBytes(self);
}
/**
* #deprecated use the variant in IOGroovyMethods
*
* @see org.codehaus.groovy.runtime.IOGroovyMethods#withCloseable(java.io.Closeable, groovy.lang.Closure)
* @since 2.3.0
*/
@Deprecated
public static <T> T withCloseable(Closeable self, @ClosureParams(value = SimpleType.class, options = "java.io.Closeable") Closure<T> action) throws IOException {
return IOGroovyMethods.withCloseable(self, action);
}
}