Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Groovy NIO module first commit [GROOVY-6377] #260

Merged
merged 6 commits into from

4 participants

@pditommaso

This pull request implements the extension methods for the class java.nio.Path as the ones already defined by the extension ResourceGroovyMethods for the "legacy" java.io.File

@melix
Owner

Very good work!

This looks good at the first sight, but I have an issue with using Spock: the way it's defined, Spock will use its own version of Groovy instead of using the version which is currently built. This means that the extension module is tested against a different version of Groovy than the one it's currently built with, so you wouldn't be able to find bugs related to Groovy core.

I didn't test, but maybe excluding the Groovy dependency from the Spock declaration would do...

Hello,

Do you mean in this way:

testCompile ('org.spockframework:spock-core:0.7-groovy-2.0') {
    exclude module: 'groovy-all'
}

I tried it and it compiles and tests pass.

Owner

Yes, that's what I meant. To be sure it works, I suggest you introduce a bug in Groovy core then run the build again. For example, comment out the following line:

https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/runtime/m12n/ExtensionModuleScanner.java#L54

Then do: ./gradlew clean groovy-nio:test

Since we're disabling module loading, your tests should now fail if Spock is really using the current version of Groovy.

Also in the module descriptor, we should probably align the version number with the Groovy build number. Last but not least, the module should probably be excluded from "groovy-all" in assembly.gradle.

Note that we're not going to merge this in 2.2.0, this is probably going to happen in 2.3 or 3.0. Thanks for your work!

Actually it worked even commenting out the line you suggested. Anyway in the dependencies tree I don't see any other Groovy version than the 2.3.0-snapshot.

testRuntime - Runtime classpath for source set 'test'.
+--- org.codehaus.groovy:groovy:2.3.0-SNAPSHOT
|    +--- commons-cli:commons-cli:1.2
|    +--- com.thoughtworks.xstream:xstream:1.4.4
|    |    \--- xmlpull:xmlpull:1.1.3.1
|    +--- jline:jline:2.10
|    +--- org.fusesource.jansi:jansi:1.10
|    +--- org.apache.ivy:ivy:2.3.0
|    +--- antlr:antlr:2.7.7
|    +--- org.ow2.asm:asm:4.1
|    +--- org.ow2.asm:asm-analysis:4.1
|    |    \--- org.ow2.asm:asm-tree:4.1
|    |         \--- org.ow2.asm:asm:4.1
|    +--- org.ow2.asm:asm-commons:4.1
|    |    \--- org.ow2.asm:asm-tree:4.1 (*)
|    +--- org.ow2.asm:asm-tree:4.1 (*)
|    +--- org.ow2.asm:asm-util:4.1
|    |    \--- org.ow2.asm:asm-tree:4.1 (*)
|    \--- org.codehaus.gpars:gpars:1.1.0
|         +--- org.multiverse:multiverse-core:0.7.0
|         \--- org.codehaus.jsr166-mirror:jsr166y:1.7.0
+--- org.codehaus.groovy:groovy-test:2.3.0-SNAPSHOT
|    +--- junit:junit:4.11
|    |    \--- org.hamcrest:hamcrest-core:1.3
|    \--- org.codehaus.groovy:groovy:2.3.0-SNAPSHOT (*)
+--- org.spockframework:spock-core:0.7-groovy-2.0
|    +--- junit:junit-dep:4.10
|    |    \--- org.hamcrest:hamcrest-core:1.1 -> 1.3
|    \--- org.hamcrest:hamcrest-core:1.3
\--- net.sourceforge.cobertura:cobertura:1.9.4.1
     +--- oro:oro:2.0.8
     +--- asm:asm:3.0
     +--- asm:asm-tree:3.0
     |    \--- asm:asm:3.0
     +--- log4j:log4j:1.2.9
     \--- org.apache.ant:ant:1.7.0
          \--- org.apache.ant:ant-launcher:1.7.0

About the version number in the module descriptor how to do that?

Owner

Instead of writing the descriptor file, you can have it generated for you:

See for example: https://github.com/groovy/groovy-core/blob/master/subprojects/groovy-jsr223/build.gradle#L21

I'm sorry, but I'm not getting the point here. I'm sure that is much more easier for some of you Groovy gurus to manage this, when you will merge the pull request.

Owner

Sure. It's just that if you use the task, you don't have to put an explicit descriptor file in subprojects/groovy-nio/src/resources/META-INF/services like you did. Instead, the task will generate it for you, based on the module name and the version number of Groovy.

I got it. You want the module descriptor generated on-fly by Gradle. Ok, I remove the "explicit" description and modified the build to have it generated.

Moreover I've understood why the tests were not failing commenting out the line the line you said. Because in the tests I was invoking the extensions methods by static reference, I mean:

 NioGroovyMethods.size(path) 

instead of

path.size()

I've changed them and now they fail commenting out your line.

So far so good. I hope to see this in 2.3 release.

Cheers,
Paolo

@PascalSchumacher
Collaborator

Hello Paolo,

very good work. :)

Just a small nitpick:

The tests "testNewObjectOutputStream" and "testEachObject" fail under Windows 7, because Streams are not closed (see my other comments for details).

Thanks for submitting this high quality pull request. :)

@PascalSchumacher PascalSchumacher commented on the diff
...roovy-nio/src/test/groovy/NioGroovyMethodsTest.groovy
((22 lines not shown))
+ then:
+ path.size() == str.size()
+ cleanup:
+ Files.deleteIfExists(path)
+ }
+
+
+ def testNewObjectOutputStream() {
+
+ setup:
+ def str = 'Hello world!'
+ Path path = Paths.get('new_obj_out_stream')
+ when:
+ def out = path.newObjectOutputStream()
+ out.writeObject(str)
+ out.flush()
@PascalSchumacher Collaborator

The stream should be closed (or the tests fails under Windows 7).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@PascalSchumacher PascalSchumacher commented on the diff
...roovy-nio/src/test/groovy/NioGroovyMethodsTest.groovy
((47 lines not shown))
+
+ def testNewObjectInputStream() {
+
+ setup:
+ def str = 'Hello world!'
+ def path = Paths.get('new_obj_in_stream')
+ def stream = new ObjectOutputStream(new FileOutputStream(path.toFile()))
+ stream.writeObject(str)
+ stream.close()
+
+ when:
+ def obj = path.newObjectInputStream()
+ then:
+ obj.readObject() == str
+ cleanup:
+ Files.deleteIfExists(path)
@PascalSchumacher Collaborator

Under Windows 7 it is seems somehow necessary to close the stream first, before deleting the file. Else the following testEachObject() test fails with an access exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@PascalSchumacher PascalSchumacher commented on the diff
...roovy-nio/src/test/groovy/NioGroovyMethodsTest.groovy
((29 lines not shown))
+ def testNewObjectOutputStream() {
+
+ setup:
+ def str = 'Hello world!'
+ Path path = Paths.get('new_obj_out_stream')
+ when:
+ def out = path.newObjectOutputStream()
+ out.writeObject(str)
+ out.flush()
+ def stream = new ObjectInputStream(new FileInputStream(path.toFile()))
+
+ then:
+ stream.readObject() == str
+
+ cleanup:
+ Files.deleteIfExists(path)
@PascalSchumacher Collaborator

Under Windows 7 it is seems somehow necessary to close the first, before deleting the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@pditommaso

Hi Pascal, thanks.

Unfortunately I do not have a Windows box to test it. Anyway this means that you managed to fix the tests on Windows. Is it right?

Cheers,
Paolo

@PascalSchumacher
Collaborator

Hi Paolo,

yes I managed to fix the tests on Windows. If my git skill were better I would send you a pull request with the fix. I will just wait till your pull request is merged and the commit the fix.

@pditommaso

Hello, quick question about this pull request.

Is it ok using Java 1.7 source compatibility for this module or it is mandatory to stick to with 1.5 like other modules ?

@melix
Owner
@pditommaso

Good. I've just submitted an update for it.

One more thing: in the File based implementation there were some methods having in the signature a throws declaration for FileNotFoundException and IllegalArgumentException. For example:

public static void eachFile(final File self, final FileType fileType, final Closure closure) 
        throws FileNotFoundException, IllegalArgumentException 
{ .. }

Using the new Path based implementation that exceptions aren't thrown any more, so I've changed the method signature to:

public static void eachFile(final Path self, final FileType fileType, final Closure closure) 
        throws IOException 
{ .. }

Is it OK? or it would be better to declare these methods to throw the same exceptions to keep the API "aligned" between File and Path implementation ?

@glaforge
Owner

I think it's okay to dump the throw exception which are not being thrown anymore.

@pditommaso

Good! If so it remains only to pull it :)

@melix melix merged commit 758b0fb into groovy:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
View
3  settings.gradle
@@ -13,7 +13,8 @@ include 'groovy-ant',
'groovy-templates',
'groovy-test',
'groovy-testng',
- 'groovy-xml'
+ 'groovy-xml',
+ 'groovy-nio'
rootProject.children.each { prj ->
prj.projectDir = new File("$rootDir/subprojects/$prj.name")
View
16 subprojects/groovy-nio/build.gradle
@@ -0,0 +1,16 @@
+sourceCompatibility = 1.7
+targetCompatibility = 1.7
+
+dependencies {
+ compile project(':')
+ groovy project(':')
+ testCompile project(':groovy-test')
+ testCompile ('org.spockframework:spock-core:0.7-groovy-2.0') {
+ exclude module: 'groovy-all'
+ }
+}
+
+task moduleDescriptor(type: org.codehaus.groovy.gradle.WriteExtensionDescriptorTask) {
+ extensionClasses = 'org.codehaus.groovy.runtime.NioGroovyMethods'
+}
+compileJava.dependsOn moduleDescriptor
View
1,674 subprojects/groovy-nio/src/main/java/org/codehaus/groovy/runtime/NioGroovyMethods.java
@@ -0,0 +1,1674 @@
+/*
+ * Copyright 2003-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.codehaus.groovy.runtime;
+
+import static java.nio.file.StandardOpenOption.APPEND;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static org.codehaus.groovy.runtime.DefaultGroovyMethods.get;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+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.PrintWriter;
+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 groovy.io.FileType;
+import groovy.io.FileVisitResult;
+import groovy.io.GroovyPrintWriter;
+import groovy.lang.Closure;
+import groovy.lang.MetaClass;
+import groovy.lang.Writable;
+import org.codehaus.groovy.runtime.callsite.BooleanReturningMethodInvoker;
+import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+
+/**
+ * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
+ */
+
+/**
+ * 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.
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ * @author Jeremy Rayner
+ * @author Sam Pullara
+ * @author Rod Cope
+ * @author Guillaume Laforge
+ * @author John Wilson
+ * @author Hein Meling
+ * @author Dierk Koenig
+ * @author Pilho Kim
+ * @author Marc Guillemot
+ * @author Russel Winder
+ * @author bing ran
+ * @author Jochen Theodorou
+ * @author Paul King
+ * @author Michael Baehr
+ * @author Joachim Baumann
+ * @author Alex Tkachman
+ * @author Ted Naleid
+ * @author Brad Long
+ * @author Jim Jagielski
+ * @author Rodolfo Velasco
+ * @author jeremi Joslin
+ * @author Hamlet D'Arcy
+ * @author Cedric Champeau
+ * @author Tim Yates
+ * @author Dinko Srkoc
+ */
+
+public class NioGroovyMethods 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)
+ */
+ 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.
+ */
+ 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)
+ */
+ public static <T> T withObjectOutputStream(Path self, 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.
+ */
+ 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.
+ */
+ 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)
+ */
+ 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)
+ */
+ public static <T> T withObjectInputStream(Path path, 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)
+ */
+ public static <T> T withObjectInputStream(Path self, ClassLoader classLoader, 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)
+ */
+ public static <T> T eachLine(Path self, 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)
+ */
+ public static <T> T eachLine(Path self, String charset, 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)
+ */
+ public static <T> T eachLine(Path self, int firstLine, 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)
+ */
+ public static <T> T eachLine(Path self, String charset, int firstLine, 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)
+ */
+ public static <T> T splitEachLine(Path self, String regex, 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)
+ */
+ public static <T> T splitEachLine(Path self, Pattern pattern, 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)
+ */
+ public static <T> T splitEachLine(Path self, String regex, String charset, 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)
+ */
+ public static <T> T splitEachLine(Path self, Pattern pattern, String charset, 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)
+ */
+ 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 1.6.8
+ */
+ 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 1.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 1.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 1.7.1
+ */
+ 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 1.7.1
+ */
+ public static void setBytes(Path self, byte[] bytes) throws IOException {
+ IOGroovyMethods.setBytes( Files.newOutputStream(self), bytes);
+ }
+
+ /**
+ * Write the text to the Path.
+ *
+ * @param self a Path
+ * @param text the text to write to the Path
+ * @throws java.io.IOException if an IOException occurs.
+ * @since 1.0
+ */
+ public static void write(Path self, String text) throws IOException {
+ BufferedWriter writer = null;
+ try {
+ writer = newWriter(self);
+ writer.write(text);
+ writer.flush();
+
+ Writer temp = writer;
+ writer = null;
+ temp.close();
+ } finally {
+ closeWithWarning(writer);
+ }
+ }
+
+ /**
+ * 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 1.5.1
+ */
+ 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 -> 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 1.7.3
+ */
+ 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 1.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 1.5.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 1.5.0
+ */
+ public static Path leftShift(Path path, InputStream data) throws IOException {
+ append(path, data);
+ return path;
+ }
+
+ /**
+ * Write the text to the Path, 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 1.0
+ */
+ public static void write(Path self, String text, String charset) throws IOException {
+ BufferedWriter writer = null;
+ try {
+ writer = newWriter(self, 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.
+ *
+ * @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 1.0
+ */
+ public static void append(Path self, Object text) throws IOException {
+ BufferedWriter writer = null;
+ try {
+ writer = newWriter(self, true);
+ InvokerHelper.write(writer, text);
+ writer.flush();
+
+ Writer temp = writer;
+ writer = null;
+ temp.close();
+ } finally {
+ closeWithWarning(writer);
+ }
+ }
+
+ /**
+ * Append bytes to the end of a Path.
+ *
+ * @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 1.5.1
+ */
+ public static void append(Path self, byte[] bytes) throws IOException {
+ BufferedOutputStream stream = null;
+ try {
+ stream = new BufferedOutputStream( 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 1.5.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, 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 1.0
+ */
+ public static void append(Path self, Object text, String charset) throws IOException {
+ BufferedWriter writer = null;
+ try {
+ writer = newWriter(self, charset, true);
+ 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 1.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 file object
+ * @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 1.7.1
+ */
+ public static void eachFile(final Path self, final FileType fileType, final Closure closure) throws IOException {
+ //throws FileNotFoundException, IllegalArgumentException {
+ checkDir(self);
+
+ try ( DirectoryStream<Path> stream = Files.newDirectoryStream(self) ) {
+ Iterator<Path> itr = stream.iterator();
+ while( itr.hasNext() ) {
+ Path path = itr.next();
+ 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 (first parameter is 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 1.5.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 (first parameter is 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 1.0
+ */
+ public static void eachDir(Path self, Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
+ eachFile(self, FileType.DIRECTORIES, closure);
+ }
+
+ /**
+ * Invokes the closure for each descendant file in this directory.
+ * Sub-directories are recursively searched in a depth-first fashion.
+ * Both regular files and subdirectories may be passed to the closure
+ * depending on the value of fileType.
+ *
+ * @param self a file object
+ * @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 1.7.1
+ */
+ public static void eachFileRecurse(final Path self, final FileType fileType, final Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
+ // throws FileNotFoundException, IllegalArgumentException {
+ checkDir(self);
+ try ( DirectoryStream<Path> stream = Files.newDirectoryStream(self)) {
+ Iterator<Path> itr = stream.iterator();
+ while ( itr.hasNext() ) {
+ Path path = itr.next();
+ if (Files.isDirectory(path)) {
+ if (fileType != FileType.FILES) closure.call(path);
+ eachFileRecurse(path, fileType, closure);
+ } else if (fileType != FileType.DIRECTORIES) {
+ closure.call(path);
+ }
+ }
+
+ }
+
+ }
+
+ /**
+ * Invokes <code>closure</code> for each descendant file in this directory tree.
+ * Sub-directories are recursively traversed as found.
+ * 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 ->
+ * a.isFile() != b.isFile() ? a.isFile() <=> b.isFile() : a.name <=> 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 -> totalSize += it.size(); count++ }
+ * </pre>
+ *
+ * @param self a Path
+ * @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.util.Collection, groovy.lang.Closure)
+ * @see groovy.io.FileVisitResult
+ * @see groovy.io.FileType
+ * @since 1.7.1
+ */
+ public static void traverse(final Path self, final Map<String, Object> options, 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;
+ }
+
+ /**
+ * Invokes the closure for each descendant file in this directory tree.
+ * Sub-directories are recursively traversed in a depth-first fashion.
+ * 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
+ * @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 1.7.1
+ */
+ public static void traverse(final Path self, final Closure closure)
+ throws IOException {
+ // throws FileNotFoundException, IllegalArgumentException {
+ traverse(self, new HashMap<String, Object>(), 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
+ * @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 1.7.1
+ */
+ public static void traverse(final Path self, final Map<String, Object> options)
+ throws IOException {
+ // throws FileNotFoundException, IllegalArgumentException {
+ final Closure visit = (Closure) options.remove("visit");
+ traverse(self, options, visit);
+ }
+
+ private static FileVisitResult traverse(final Path self, final Map<String, Object> options, final Closure closure, final int maxDepth)
+ throws IOException {
+ //throws FileNotFoundException, IllegalArgumentException {
+ 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<Path>();
+ while(itr.hasNext()) { files.add(itr.next()); }
+
+ if (sort != null) files = DefaultGroovyMethods.sort(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;
+
+ }
+
+ }
+
+ /**
+ * Invokes the closure for each descendant file in this directory.
+ * Sub-directories are recursively searched in a depth-first fashion.
+ * Both regular files and subdirectories are passed to the closure.
+ *
+ * @param self a Path
+ * @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 1.0
+ */
+ public static void eachFileRecurse(Path self, Closure closure) throws IOException { // throws FileNotFoundException, IllegalArgumentException {
+ eachFileRecurse(self, FileType.ANY, closure);
+ }
+
+ /**
+ * Invokes the closure for each descendant directory of this directory.
+ * Sub-directories are recursively searched in a depth-first fashion.
+ * Only subdirectories are passed to the closure; regular files are ignored.
+ *
+ * @param self a 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 1.5.0
+ */
+ public static void eachDirRecurse(final Path self, 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 << it.name }
+ * assert names == ['foo1.txt', 'foo2.txt']
+ *
+ * // remove all *.bak files in baseDir
+ * baseDir.eachFileMatch FILES, ~/.*\.bak/, { Path bak -> bak.delete() }
+ *
+ * // print out files > 4K in size from baseDir
+ * baseDir.eachFileMatch FILES, { new Path(baseDir, it).size() > 4096 }, { println "$it.name ${it.size()}" }
+ * </pre>
+ *
+ * @param self a file
+ * @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 1.7.1
+ */
+ public static void eachFileMatch(final Path self, final FileType fileType, final Object nameFilter, 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 file
+ * @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 1.5.0
+ */
+ public static void eachFileMatch(final Path self, final Object nameFilter, 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 file
+ * @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 1.5.0
+ */
+ public static void eachDirMatch(final Path self, final Object nameFilter, 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 1.6.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) ) {
+
+ Iterator<Path> itr = stream.iterator();
+
+ while (itr.hasNext()) {
+ Path path = itr.next();
+ 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 the 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 1.7.4
+ */
+ public static boolean renameTo(final Path self, String newPathName) {
+ try {
+ Files.move(self, Paths.get(newPathName));
+ return true;
+ } catch(IOException e) {
+ return false;
+ }
+ }
+
+ 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 1.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 1.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 1.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.
+ */
+ 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
+ */
+ 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 1.5.2
+ */
+ public static <T> T withReader(Path self, 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.
+ *
+ * @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 1.6.0
+ */
+ public static <T> T withReader(Path self, String charset, 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 1.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 1.5.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 1.5.2
+ */
+ public static Object withOutputStream(Path self, Closure 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 1.5.2
+ */
+ public static Object withInputStream(Path self, Closure 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 1.5.2
+ */
+ public static <T> T withDataOutputStream(Path self, 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 1.5.2
+ */
+ public static <T> T withDataInputStream(Path self, 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 1.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 1.0
+ */
+ public static BufferedWriter newWriter(Path self, boolean append) throws IOException {
+ if( append ) {
+ return Files.newBufferedWriter(self, Charset.defaultCharset(), CREATE, APPEND);
+ }
+ else {
+ return Files.newBufferedWriter(self, Charset.defaultCharset());
+ }
+ }
+
+ /**
+ * Helper method to create a buffered writer for a file. If the given
+ * charset is "UTF-16BE" or "UTF-16LE", 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
+ * @return a BufferedWriter
+ * @throws java.io.IOException if an IOException occurs.
+ * @since 1.0
+ */
+ public static BufferedWriter newWriter(Path self, String charset, boolean append) throws IOException {
+ if (append) {
+ return Files.newBufferedWriter(self, Charset.forName(charset), CREATE, APPEND);
+ }
+ else {
+ return Files.newBufferedWriter(self, Charset.forName(charset) );
+ }
+ }
+
+ /**
+ * Creates a buffered writer for this file, 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 1.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.
+ *
+ * @param self a Path
+ * @param closure a closure
+ * @return the value returned by the closure
+ * @throws java.io.IOException if an IOException occurs.
+ * @since 1.5.2
+ */
+ public static <T> T withWriter(Path self, Closure<T> closure) throws IOException {
+ return IOGroovyMethods.withWriter(newWriter(self), 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.
+ *
+ * @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 1.5.2
+ */
+ public static <T> T withWriter(Path self, String charset, Closure<T> closure) throws IOException {
+ return IOGroovyMethods.withWriter(newWriter(self, charset), 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.
+ *
+ * @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 1.5.2
+ */
+ public static <T> T withWriterAppend(Path self, String charset, Closure<T> closure) throws IOException {
+ return IOGroovyMethods.withWriter(newWriter(self, charset, true), 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.
+ *
+ * @param self a Path
+ * @param closure a closure
+ * @return the value returned by the closure
+ * @throws java.io.IOException if an IOException occurs.
+ * @since 1.5.2
+ */
+ public static <T> T withWriterAppend(Path self, Closure<T> closure) throws IOException {
+ return IOGroovyMethods.withWriter(newWriter(self, true), 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 1.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 1.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 1.5.2
+ */
+ public static <T> T withPrintWriter(Path self, 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 1.5.2
+ */
+ public static <T> T withPrintWriter(Path self, String charset, 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 1.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 1.5.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 1.0
+ */
+ public static void eachByte(Path self, 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 1.7.4
+ */
+ public static void eachByte(Path self, int bufferLen, 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 1.0
+ */
+ public static Writable filterLine(Path self, 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 1.6.8
+ */
+ public static Writable filterLine(Path self, String charset, 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 1.0
+ */
+ public static void filterLine(Path self, Writer writer, 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 1.6.8
+ */
+ public static void filterLine(Path self, Writer writer, String charset, 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 1.0
+ */
+ public static byte[] readBytes(Path self) throws IOException {
+ return Files.readAllBytes(self);
+ }
+
+
+
+}
View
223 subprojects/groovy-nio/src/main/java/org/codehaus/groovy/runtime/WritablePath.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright 2003-2012 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.codehaus.groovy.runtime;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Iterator;
+
+import groovy.lang.Writable;
+
+/**
+ * A Writable Path.
+ *
+ * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
+ * @author John Wilson
+ */
+
+public class WritablePath implements Path, Writable {
+
+ private final String encoding;
+ private final Path delegate;
+
+ public WritablePath(final Path delegate) {
+ this(delegate, null);
+ }
+
+ public WritablePath(final Path delegate, final String encoding) {
+ this.encoding = encoding;
+ this.delegate = delegate;
+ }
+
+ public Writer writeTo(final Writer out) throws IOException {
+ final Reader reader =
+ (this.encoding == null)
+ ? new InputStreamReader(Files.newInputStream(this))
+ : new InputStreamReader(Files.newInputStream(this), Charset.forName(this.encoding));
+
+ try {
+ int c = reader.read();
+
+ while (c != -1) {
+ out.write(c);
+ c = reader.read();
+ }
+ }
+ finally {
+ reader.close();
+ }
+ return out;
+ }
+
+ @Override
+ public FileSystem getFileSystem() {
+ return delegate.getFileSystem();
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return delegate.isAbsolute();
+ }
+
+ @Override
+ public Path getRoot() {
+ return delegate.getRoot();
+ }
+
+ @Override
+ public Path getFileName() {
+ return delegate.getFileName();
+ }
+
+ @Override
+ public Path getParent() {
+ return delegate.getParent();
+ }
+
+ @Override
+ public int getNameCount() {
+ return delegate.getNameCount();
+ }
+
+ @Override
+ public Path getName(int index) {
+ return delegate.getName(index);
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ return delegate.subpath(beginIndex, endIndex);
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ return delegate.startsWith(other);
+ }
+
+ @Override
+ public boolean startsWith(String other) {
+ return delegate.startsWith(other);
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ return delegate.endsWith(other);
+ }
+
+ @Override
+ public boolean endsWith(String other) {
+ return delegate.endsWith(other);
+ }
+
+ @Override
+ public Path normalize() {
+ return delegate.normalize();
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ return delegate.resolve(other);
+ }
+
+ @Override
+ public Path resolve(String other) {
+ return delegate.resolve(other);
+ }
+
+ @Override
+ public Path resolveSibling(Path other) {
+ return delegate.resolveSibling(other);
+ }
+
+ @Override
+ public Path resolveSibling(String other) {
+ return delegate.resolveSibling(other);
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ return delegate.relativize(other);
+ }
+
+ @Override
+ public URI toUri() {
+ return delegate.toUri();
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ return delegate.toAbsolutePath();
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ return delegate.toRealPath(options);
+ }
+
+ @Override
+ public File toFile() {
+ return delegate.toFile();
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers) throws IOException {
+ return delegate.register(watcher,events,modifiers);
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events) throws IOException {
+ return delegate.register(watcher,events);
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return delegate.iterator();
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ return delegate.compareTo(other);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return delegate.equals(other);
+ }
+
+ @Override
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return delegate.toString();
+ }
+
+}
View
306 subprojects/groovy-nio/src/test/groovy/NioGroovyMethodsTest.groovy
@@ -0,0 +1,306 @@
+/**
+ *
+ * @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
+ */
+package org.codehaus.groovy.runtime
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+
+import groovy.io.FileType
+import spock.lang.Specification
+
+class NioGroovyMethodsTest extends Specification {
+
+ def testPathSize() {
+
+ when:
+ def str = 'Hello world!'
+ Path path = Files.createTempFile('test-size',null)
+ Files.copy( new ByteArrayInputStream(str.getBytes()), path, StandardCopyOption.REPLACE_EXISTING )
+ then:
+ path.size() == str.size()
+ cleanup:
+ Files.deleteIfExists(path)
+ }
+
+
+ def testNewObjectOutputStream() {
+
+ setup:
+ def str = 'Hello world!'
+ Path path = Paths.get('new_obj_out_stream')
+ when:
+ def out = path.newObjectOutputStream()
+ out.writeObject(str)
+ out.flush()
@PascalSchumacher Collaborator

The stream should be closed (or the tests fails under Windows 7).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ def stream = new ObjectInputStream(new FileInputStream(path.toFile()))
+
+ then:
+ stream.readObject() == str
+
+ cleanup:
+ Files.deleteIfExists(path)
@PascalSchumacher Collaborator

Under Windows 7 it is seems somehow necessary to close the first, before deleting the file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ }
+
+ def testNewObjectInputStream() {
+
+ setup:
+ def str = 'Hello world!'
+ def path = Paths.get('new_obj_in_stream')
+ def stream = new ObjectOutputStream(new FileOutputStream(path.toFile()))
+ stream.writeObject(str)
+ stream.close()
+
+ when:
+ def obj = path.newObjectInputStream()
+ then:
+ obj.readObject() == str
+ cleanup:
+ Files.deleteIfExists(path)
@PascalSchumacher Collaborator

Under Windows 7 it is seems somehow necessary to close the stream first, before deleting the file. Else the following testEachObject() test fails with an access exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ }
+
+ def testEachObject() {
+
+ setup:
+ def str1 = 'alpha'
+ def str2 = 'beta'
+ def str3 = 'delta'
+ def path = Paths.get('new_obj_in_stream')
+ def stream = new ObjectOutputStream(new FileOutputStream(path.toFile()))
+ stream.writeObject(str1)
+ stream.writeObject(str2)
+ stream.writeObject(str3)
+ stream.close()
+
+ when:
+ def list = []
+ path.eachObject() { list << it }
+
+ then:
+ list.size()==3
+ list[0]==str1
+ list[1]==str2
+ list[2]==str3
+
+ cleanup:
+ Files.deleteIfExists(path)
+ }
+
+ def testEachLine() {
+
+ setup:
+ def file = new File('test_each_file')
+ file.text = 'alpha\nbeta\ndelta';
+ when:
+ def lines = file.toPath().readLines()
+ then:
+ lines.size()==3
+ lines[0]=='alpha'
+ lines[1]=='beta'
+ lines[2]=='delta'
+
+ cleanup:
+ file?.delete()
+ }
+
+ def testNewReader() {
+
+ setup:
+ final str = 'Hello world!'
+ def file = new File('test_new_reader')
+ file.text = str
+ when:
+ def reader = file.toPath().newReader()
+ def line = reader.readLine()
+ then:
+ line == str
+ reader.readLine() == null
+
+ cleanup:
+ file?.delete()
+
+ }
+
+ def testGetBytes() {
+
+ when:
+ def file = new File('test_getBytes')
+ file.text = 'Hello world!'
+ def path = file.toPath()
+ then:
+ path.getBytes() == 'Hello world!'.getBytes()
+
+ cleanup:
+ file?.delete()
+
+ }
+
+ def testSetBytes() {
+
+
+ when:
+ def file = new File('test_setBytes')
+ file.toPath().setBytes( 'Ciao mundo!'.getBytes() )
+ then:
+ file.text == 'Ciao mundo!'
+ cleanup:
+ file?.delete()
+
+ }
+
+ def testWrite( ) {
+ when:
+ String str = 'Hello there!'
+ def file = new File('test_write');
+ file.toPath().write(str)
+ then:
+ file.text == 'Hello there!'
+ cleanup:
+ file?.delete()
+ }
+
+
+ def testAppendObject() {
+
+ setup:
+ def file = new File('test_appendObject')
+ file.text = 'alpha'
+ when:
+ file.toPath().append( '-gamma' )
+ then:
+ file.text == 'alpha-gamma'
+ cleanup:
+ file.delete()
+ }
+
+ def testAppendBytes() {
+ setup:
+ def file = new File('test_appendBytes')
+ file.text = 'alpha'
+ when:
+ file.toPath().append( '-beta'.getBytes() )
+ then:
+ file.text == 'alpha-beta'
+ cleanup:
+ file.delete()
+ }
+
+
+ def testAppendInputStream() {
+
+ setup:
+ def file = new File('test_appendStream')
+ file.text = 'alpha'
+ when:
+ file.toPath().append( new ByteArrayInputStream('-delta'.getBytes()) )
+ then:
+ file.text == 'alpha-delta'
+ cleanup:
+ file.delete()
+
+ }
+
+ def testEachFile() {
+ setup:
+ def folder = Files.createTempDirectory('test')
+ def file1 = Files.createTempFile(folder, 'file_1_', null)
+ def file2 = Files.createTempFile(folder, 'file_2_', null)
+ def file3 = Files.createTempFile(folder, 'file_X_', null)
+ // sub-folder with two files
+ def sub1 = Files.createTempDirectory(folder, 'sub1_')
+ def file4 = Files.createTempFile(sub1, 'file_4_', null)
+ def file5 = Files.createTempFile(sub1, 'file_5_', null)
+ // sub-sub-folder with one file
+ def sub2 = Files.createTempDirectory(sub1, 'sub2_')
+ def file6 = Files.createTempFile(sub2, 'file_6_', null)
+
+ when:
+ def result = []
+ folder.eachFile() { result << it }
+ then:
+ result == [file1, file2, file3, sub1]
+
+ when:
+ def result2 = []
+ folder.eachFile(FileType.FILES) { result2 << it }
+ then:
+ result2 == [file1, file2, file3]
+
+ when:
+ def result3 = []
+ folder.eachFile(FileType.DIRECTORIES) { result3 << it }
+ then:
+ result3 == [sub1]
+
+ when:
+ def result4 = []
+ folder.eachFileMatch(FileType.FILES, ~/file_\d_.*/) { result4 << it }
+ then:
+ result4 == [file1, file2]
+
+ when:
+ def result5 = []
+ folder.eachFileMatch(FileType.DIRECTORIES, ~/sub\d_.*/) { result5 << it }
+ then:
+ result5 == [sub1]
+
+ when:
+ def result6 = []
+ folder.eachFileRecurse(FileType.FILES) { result6 << it }
+ then:
+ result6 == [file1, file2, file3, file4, file5, file6]
+
+ when:
+ def result7 = []
+ folder.eachFileRecurse(FileType.DIRECTORIES) { result7 << it }
+ then:
+ result7 == [sub1, sub2]
+
+ cleanup:
+ folder?.toFile()?.deleteDir()
+ }
+
+
+ def testEachDir() {
+
+ setup:
+ def folder = Files.createTempDirectory('test')
+ def sub1 = Files.createTempDirectory(folder, 'sub_1_')
+ def sub2 = Files.createTempDirectory(folder, 'sub_2_')
+ def sub3 = Files.createTempDirectory(folder, 'sub_X_')
+ def sub4 = Files.createTempDirectory(sub2, 'sub_2_4_')
+ def sub5 = Files.createTempDirectory(sub2, 'sub_2_5_')
+ def file1 = Files.createTempFile(folder, 'file1', null)
+ def file2 = Files.createTempFile(folder, 'file2', null)
+
+ // test *eachDir*
+ when:
+ def result = []