NIO extensions for Apache Groovy providing enhanced file system operations and path handling
—
Traverse directory structures with filtering, sorting, and recursion options. Support for file type filtering, name matching, and advanced traversal patterns with comprehensive closure-based processing.
Iterate through files and directories in a directory with type filtering.
/**
* 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFile(Path self, Closure closure);
/**
* 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFile(Path self, FileType fileType, Closure 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachDir(Path self, Closure closure);Usage Examples:
import java.nio.file.Path
import java.nio.file.Paths
import groovy.io.FileType
Path directory = Paths.get("/home/user/documents")
// Process all files and directories
directory.eachFile { file ->
println "${file.fileName} - ${Files.isDirectory(file) ? 'DIR' : 'FILE'}"
}
// Process only regular files
directory.eachFile(FileType.FILES) { file ->
println "File: ${file.fileName} (${file.size()} bytes)"
}
// Process only directories
directory.eachFile(FileType.DIRECTORIES) { dir ->
println "Directory: ${dir.fileName}"
}
// or use the convenience method
directory.eachDir { dir ->
println "Directory: ${dir.fileName}"
}
// Process with file type checking
directory.eachFile { path ->
if (Files.isRegularFile(path)) {
println "File: ${path.fileName}"
} else if (Files.isDirectory(path)) {
println "Directory: ${path.fileName}/"
}
}Recursively traverse directory trees with type filtering and processing options.
/**
* Processes each descendant file in this directory and any sub-directories.
* Processing consists of calling closure 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFileRecurse(Path self, Closure closure);
/**
* Processes each descendant file in this directory and any sub-directories.
* Processing consists of potentially calling closure passing it the current file 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFileRecurse(Path self, FileType fileType, Closure closure);
/**
* Recursively processes each descendant subdirectory in this directory.
* Processing consists of calling closure 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 FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachDirRecurse(Path self, Closure closure);Usage Examples:
import java.nio.file.Path
import java.nio.file.Paths
import groovy.io.FileType
Path rootDir = Paths.get("/project")
// Recursively process all files and directories
rootDir.eachFileRecurse { file ->
println file.toString()
}
// Recursively process only regular files
rootDir.eachFileRecurse(FileType.FILES) { file ->
if (file.fileName.toString().endsWith(".java")) {
println "Java file: ${file}"
}
}
// Recursively process only directories
rootDir.eachFileRecurse(FileType.DIRECTORIES) { dir ->
println "Directory: ${dir}"
}
// or use the convenience method
rootDir.eachDirRecurse { dir ->
println "Directory: ${dir}"
}
// Count files by extension
Map<String, Integer> extensionCounts = [:]
rootDir.eachFileRecurse(FileType.FILES) { file ->
String filename = file.fileName.toString()
int lastDot = filename.lastIndexOf('.')
String extension = lastDot > 0 ? filename.substring(lastDot) : "(no extension)"
extensionCounts[extension] = (extensionCounts[extension] ?: 0) + 1
}
extensionCounts.each { ext, count ->
println "${ext}: ${count} files"
}Advanced traversal with extensive configuration options including filtering, sorting, depth control, and pre/post processing.
/**
* Processes each descendant file in this directory and any sub-directories.
* Convenience method for traverse(Path, Map, 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 FileVisitResult value
* which can be used to control subsequent processing
* @throws FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void traverse(Path self, Closure closure);
/**
* Processes each descendant file in this directory and any sub-directories.
* The traversal can be adapted by providing various options in the options Map
* @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 FileVisitResult value
* which can be used to control subsequent processing
* @throws 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
*/
void traverse(Path self, Map<String, Object> options, Closure closure);
/**
* Invokes the closure specified with key 'visit' in the options Map
* for each descendant file in this directory tree. Convenience method
* for traverse(Path, Map, 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 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
*/
void traverse(Path self, Map<String, Object> options);Traversal Options:
type: A FileType enum to determine if normal files or directories or both are processedpreDir: A closure run before each directory is processed, optionally returning a FileVisitResult valuepreRoot: A boolean indicating that the 'preDir' closure should be applied at the root levelpostDir: A closure run after each directory is processed, optionally returning a FileVisitResult valuepostRoot: A boolean indicating that the 'postDir' closure should be applied at the root levelvisitRoot: A boolean indicating that the given closure should be applied for the root dirmaxDepth: The maximum number of directory levels when recursing (default is -1 which means infinite, set to 0 for no recursion)filter: A filter to perform on traversed files/directories. If set, only files/dirs which match are candidates for visitingnameFilter: A filter to perform on the name of traversed files/directories. If set, only files/dirs which match are candidates for visiting. (Must not be set if 'filter' is set)excludeFilter: A filter to perform on traversed files/directories. If set, any candidates which match won't be visitedexcludeNameFilter: A filter to perform on the names of traversed files/directories. If set, any candidates which match won't be visited. (Must not be set if 'excludeFilter' is set)sort: A closure which if set causes the files and subdirectories for each directory to be processed in sorted orderUsage Examples:
import java.nio.file.Path
import java.nio.file.Paths
import groovy.io.FileType
import groovy.io.FileVisitResult
Path projectDir = Paths.get("/project")
// Simple traversal
projectDir.traverse { file ->
println file.toString()
}
// Advanced traversal with options
def totalSize = 0
def count = 0
projectDir.traverse([
type: FileType.FILES,
nameFilter: ~/.*\.groovy$/,
maxDepth: 3,
preDir: { dir ->
println "Entering directory: ${dir.fileName}"
if (dir.fileName.toString() == '.git') {
return FileVisitResult.SKIP_SUBTREE
}
},
postDir: { dir ->
println "Exiting directory: ${dir.fileName}"
},
sort: { a, b ->
// Sort by type (directories first), then by name
if (Files.isDirectory(a) != Files.isDirectory(b)) {
return Files.isDirectory(a) ? -1 : 1
}
return a.fileName.toString().compareTo(b.fileName.toString())
}
]) { file ->
totalSize += file.size()
count++
println "Groovy file: ${file} (${file.size()} bytes)"
}
println "Found ${count} Groovy files totaling ${totalSize} bytes"
// Using visit closure in options
projectDir.traverse([
type: FileType.FILES,
excludeNameFilter: ~/\.(class|jar)$/,
visit: { file ->
println "Processing: ${file}"
}
])
// Control traversal flow with FileVisitResult
projectDir.traverse([
type: FileType.FILES,
nameFilter: ~/.*\.txt$/
]) { file ->
if (file.size() > 1024 * 1024) { // Files larger than 1MB
println "Large file found: ${file}"
return FileVisitResult.TERMINATE // Stop traversal
}
println "Text file: ${file}"
return FileVisitResult.CONTINUE
}Match files and directories based on name patterns with various filtering options.
/**
* Invokes the closure for each file whose name matches the given nameFilter in the given directory
* 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
* @param closure the closure to invoke
* @throws FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFileMatch(Path self, Object nameFilter, Closure closure);
/**
* Invokes the closure for each file whose name matches the given nameFilter in the given directory
* Both regular files and subdirectories may be candidates for matching depending on the value of fileType
* @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
* @param closure the closure to invoke
* @throws FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachFileMatch(Path self, FileType fileType, Object nameFilter, Closure closure);
/**
* Invokes the closure for each subdirectory whose name matches the given nameFilter in the given directory
* 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
* @param closure the closure to invoke
* @throws FileNotFoundException if the given directory does not exist
* @throws IllegalArgumentException if the provided Path object does not represent a directory
*/
void eachDirMatch(Path self, Object nameFilter, Closure closure);Usage Examples:
import java.nio.file.Path
import java.nio.file.Paths
import groovy.io.FileType
Path directory = Paths.get("/project/src")
// Match files with regex pattern
directory.eachFileMatch(~/.*\.java$/) { file ->
println "Java file: ${file.fileName}"
}
// Match only directories with pattern
directory.eachFileMatch(FileType.DIRECTORIES, ~/test.*/) { dir ->
println "Test directory: ${dir.fileName}"
}
// Use convenience method for directories
directory.eachDirMatch(~/lib.*/) { dir ->
println "Library directory: ${dir.fileName}"
}
// Match with closure filter
directory.eachFileMatch({ fileName ->
fileName.length() > 10 && fileName.endsWith(".groovy")
}) { file ->
println "Long Groovy file: ${file.fileName}"
}
// Complex pattern matching
directory.eachFileMatch(FileType.FILES, ~/.*\.(java|groovy|scala)$/) { file ->
def lang = file.fileName.toString().split('\\.').last()
println "${lang.toUpperCase()} source: ${file.fileName}"
}
// Match backup files for deletion
directory.eachFileMatch(~/.*\.bak$/) { backup ->
println "Deleting backup: ${backup.fileName}"
Files.delete(backup)
}Operations for managing directories including deletion and renaming.
/**
* Deletes a directory with all contained files and subdirectories
* @param self a Path
* @return true if the file doesn't exist or deletion was successful, false otherwise
*/
boolean deleteDir(Path self);
/**
* Renames a file
* @param self a Path
* @param newPathName The new pathname for the named file
* @return true if and only if the renaming succeeded; false otherwise
*/
boolean renameTo(Path self, String newPathName);
/**
* Renames a file
* @param self a Path
* @param newPathName The new target path specified as a URI object
* @return true if and only if the renaming succeeded; false otherwise
*/
boolean renameTo(Path self, URI newPathName);Usage Examples:
import java.nio.file.Path
import java.nio.file.Paths
Path tempDir = Paths.get("/tmp/my-temp-dir")
Path oldName = Paths.get("/project/old-name")
Path targetDir = Paths.get("/project/archive")
// Delete directory and all contents
if (tempDir.deleteDir()) {
println "Successfully deleted ${tempDir}"
} else {
println "Failed to delete ${tempDir}"
}
// Rename directory
if (oldName.renameTo("/project/new-name")) {
println "Successfully renamed directory"
}
// Rename using URI
URI newLocation = new URI("file:///project/renamed-dir")
if (oldName.renameTo(newLocation)) {
println "Successfully moved to new location"
}
// Safe directory cleanup
Path backupDir = Paths.get("/backups/old")
if (Files.exists(backupDir)) {
if (backupDir.deleteDir()) {
println "Old backup directory cleaned up"
} else {
println "Warning: Could not delete backup directory"
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-nio