0
# Tree Traversal Operations
1
2
Advanced directory tree traversal operations with walking sequences, customizable file visitor patterns, and comprehensive options for complex file system navigation and processing.
3
4
## Capabilities
5
6
### Path Walking
7
8
Traverse directory trees using lazy sequences with multiple traversal options.
9
10
```kotlin { .api }
11
/**
12
* Returns a sequence of paths for visiting this directory and all its contents.
13
* By default, the returned sequence includes only files in the file tree, in depth-first order.
14
* @param options options to control walk behavior
15
*/
16
fun Path.walk(vararg options: PathWalkOption): Sequence<Path>
17
18
/**
19
* An enumeration to provide walk options for the Path.walk function.
20
*/
21
@SinceKotlin("2.1")
22
enum class PathWalkOption {
23
/** Includes directory paths in the resulting sequence of the walk */
24
INCLUDE_DIRECTORIES,
25
/** Walks in breadth-first order instead of depth-first */
26
BREADTH_FIRST,
27
/** Follows symbolic links to the directories they point to */
28
FOLLOW_LINKS
29
}
30
```
31
32
**Usage Examples:**
33
34
```kotlin
35
import kotlin.io.path.*
36
import java.nio.file.Paths
37
38
val projectDir = Paths.get("/home/user/my-project")
39
40
// Basic walk - files only, depth-first
41
projectDir.walk().forEach { file ->
42
println("File: ${file.relativeTo(projectDir)}")
43
}
44
45
// Include directories in the walk
46
projectDir.walk(PathWalkOption.INCLUDE_DIRECTORIES).forEach { path ->
47
val type = if (path.isDirectory()) "DIR" else "FILE"
48
println("$type: ${path.relativeTo(projectDir)}")
49
}
50
51
// Breadth-first traversal with directories
52
projectDir.walk(
53
PathWalkOption.INCLUDE_DIRECTORIES,
54
PathWalkOption.BREADTH_FIRST
55
).forEach { path ->
56
val depth = path.relativeTo(projectDir).nameCount
57
val indent = " ".repeat(depth)
58
println("$indent${path.name}")
59
}
60
61
// Follow symbolic links (be careful of cycles)
62
try {
63
projectDir.walk(PathWalkOption.FOLLOW_LINKS).forEach { file ->
64
println("File (following links): ${file.relativeTo(projectDir)}")
65
}
66
} catch (e: java.nio.file.FileSystemLoopException) {
67
println("Detected symbolic link loop: ${e.message}")
68
}
69
70
// Filter and process specific files
71
val kotlinFiles = projectDir.walk()
72
.filter { it.extension == "kt" }
73
.toList()
74
75
println("Found ${kotlinFiles.size} Kotlin files")
76
77
// Count files by extension
78
val filesByExtension = projectDir.walk()
79
.groupBy { it.extension }
80
.mapValues { (_, files) -> files.size }
81
82
filesByExtension.forEach { (ext, count) ->
83
val extension = if (ext.isEmpty()) "(no extension)" else ext
84
println(".$extension: $count files")
85
}
86
```
87
88
### File Visitor Pattern
89
90
Use the visitor pattern for complex file tree operations with precise control over traversal behavior.
91
92
```kotlin { .api }
93
/**
94
* Visits this directory and all its contents with the specified visitor.
95
* The traversal is in depth-first order and starts at this directory.
96
*/
97
fun Path.visitFileTree(
98
visitor: FileVisitor<Path>,
99
maxDepth: Int = Int.MAX_VALUE,
100
followLinks: Boolean = false
101
)
102
103
/**
104
* Visits this directory and all its contents with the FileVisitor defined in builderAction.
105
*/
106
fun Path.visitFileTree(
107
maxDepth: Int = Int.MAX_VALUE,
108
followLinks: Boolean = false,
109
builderAction: FileVisitorBuilder.() -> Unit
110
)
111
112
/**
113
* Builds a FileVisitor whose implementation is defined in builderAction.
114
*/
115
fun fileVisitor(builderAction: FileVisitorBuilder.() -> Unit): FileVisitor<Path>
116
```
117
118
**Usage Examples:**
119
120
```kotlin
121
import kotlin.io.path.*
122
import java.nio.file.*
123
import java.nio.file.attribute.BasicFileAttributes
124
125
val projectDir = Paths.get("/home/user/project")
126
127
// Clean build artifacts using file visitor
128
val cleanVisitor = fileVisitor {
129
onPreVisitDirectory { directory, _ ->
130
when (directory.name) {
131
"build", "target", ".gradle", "node_modules" -> {
132
println("Cleaning: ${directory.relativeTo(projectDir)}")
133
directory.deleteRecursively()
134
FileVisitResult.SKIP_SUBTREE
135
}
136
else -> FileVisitResult.CONTINUE
137
}
138
}
139
140
onVisitFile { file, _ ->
141
when (file.extension) {
142
"class", "o", "obj", "pyc" -> {
143
println("Removing: ${file.relativeTo(projectDir)}")
144
file.deleteIfExists()
145
}
146
}
147
FileVisitResult.CONTINUE
148
}
149
150
onVisitFileFailed { file, exception ->
151
println("Failed to visit ${file.relativeTo(projectDir)}: ${exception.message}")
152
FileVisitResult.CONTINUE
153
}
154
}
155
156
projectDir.visitFileTree(cleanVisitor)
157
158
// Count files and directories with size calculation
159
var totalFiles = 0
160
var totalDirs = 0
161
var totalSize = 0L
162
163
projectDir.visitFileTree {
164
onPreVisitDirectory { directory, _ ->
165
totalDirs++
166
FileVisitResult.CONTINUE
167
}
168
169
onVisitFile { file, attributes ->
170
totalFiles++
171
totalSize += attributes.size()
172
FileVisitResult.CONTINUE
173
}
174
}
175
176
println("Project statistics:")
177
println("- Directories: $totalDirs")
178
println("- Files: $totalFiles")
179
println("- Total size: ${totalSize / 1024 / 1024} MB")
180
181
// Find large files with depth limit
182
val largeFiles = mutableListOf<Pair<Path, Long>>()
183
184
projectDir.visitFileTree(maxDepth = 3) {
185
onVisitFile { file, attributes ->
186
val size = attributes.size()
187
if (size > 10_000_000) { // Files larger than 10MB
188
largeFiles.add(file to size)
189
}
190
FileVisitResult.CONTINUE
191
}
192
}
193
194
println("Large files (>10MB):")
195
largeFiles.sortedByDescending { it.second }.forEach { (file, size) ->
196
println("${size / 1024 / 1024} MB: ${file.relativeTo(projectDir)}")
197
}
198
```
199
200
### File Visitor Builder Interface
201
202
Comprehensive interface for building custom file visitors with event handlers.
203
204
```kotlin { .api }
205
/**
206
* The builder to provide implementation of the FileVisitor that fileVisitor function builds.
207
*/
208
@SinceKotlin("2.1")
209
interface FileVisitorBuilder {
210
/**
211
* Overrides the corresponding function of the built FileVisitor with the provided function.
212
* The provided callback is invoked for a directory before its contents are visited.
213
*/
214
fun onPreVisitDirectory(function: (directory: Path, attributes: BasicFileAttributes) -> FileVisitResult)
215
216
/**
217
* Overrides the corresponding function of the built FileVisitor with the provided function.
218
* The provided callback is invoked when a file is visited.
219
*/
220
fun onVisitFile(function: (file: Path, attributes: BasicFileAttributes) -> FileVisitResult)
221
222
/**
223
* Overrides the corresponding function of the built FileVisitor with the provided function.
224
* The provided callback is invoked for an entry that could not be visited for some reason.
225
*/
226
fun onVisitFileFailed(function: (file: Path, exception: IOException) -> FileVisitResult)
227
228
/**
229
* Overrides the corresponding function of the built FileVisitor with the provided function.
230
* The provided callback is invoked for a directory after its contents have been visited.
231
*/
232
fun onPostVisitDirectory(function: (directory: Path, exception: IOException?) -> FileVisitResult)
233
}
234
```
235
236
**Usage Examples:**
237
238
```kotlin
239
import kotlin.io.path.*
240
import java.nio.file.*
241
import java.nio.file.attribute.BasicFileAttributes
242
import java.io.IOException
243
244
val sourceDir = Paths.get("/home/user/source")
245
val reportFile = Paths.get("/home/user/analysis-report.txt")
246
247
// Comprehensive file analysis visitor
248
val analysisReport = mutableListOf<String>()
249
250
val analysisVisitor = fileVisitor {
251
onPreVisitDirectory { directory, attributes ->
252
analysisReport.add("ENTER DIR: ${directory.name} (created: ${attributes.creationTime()})")
253
FileVisitResult.CONTINUE
254
}
255
256
onVisitFile { file, attributes ->
257
val size = attributes.size()
258
val modified = attributes.lastModifiedTime()
259
val type = when (file.extension.lowercase()) {
260
"kt", "java", "scala" -> "Source Code"
261
"xml", "json", "yaml", "yml" -> "Configuration"
262
"md", "txt", "rst" -> "Documentation"
263
"jpg", "png", "gif", "svg" -> "Image"
264
else -> "Other"
265
}
266
267
analysisReport.add("FILE: ${file.name} | Type: $type | Size: $size bytes | Modified: $modified")
268
FileVisitResult.CONTINUE
269
}
270
271
onVisitFileFailed { file, exception ->
272
analysisReport.add("FAILED: ${file.name} | Error: ${exception.message}")
273
FileVisitResult.CONTINUE // Continue despite errors
274
}
275
276
onPostVisitDirectory { directory, exception ->
277
if (exception != null) {
278
analysisReport.add("DIR ERROR: ${directory.name} | ${exception.message}")
279
} else {
280
analysisReport.add("EXIT DIR: ${directory.name}")
281
}
282
FileVisitResult.CONTINUE
283
}
284
}
285
286
// Run analysis
287
sourceDir.visitFileTree(analysisVisitor)
288
289
// Write report
290
reportFile.writeLines(analysisReport)
291
println("Analysis complete. Report written to: $reportFile")
292
293
// Search visitor with early termination
294
var targetFound = false
295
val searchTerm = "important-file.txt"
296
297
val searchVisitor = fileVisitor {
298
onVisitFile { file, _ ->
299
if (file.name == searchTerm) {
300
println("Found target file: $file")
301
targetFound = true
302
FileVisitResult.TERMINATE // Stop the entire traversal
303
} else {
304
FileVisitResult.CONTINUE
305
}
306
}
307
308
onPreVisitDirectory { directory, _ ->
309
// Skip hidden directories
310
if (directory.name.startsWith(".")) {
311
FileVisitResult.SKIP_SUBTREE
312
} else {
313
FileVisitResult.CONTINUE
314
}
315
}
316
}
317
318
sourceDir.visitFileTree(searchVisitor)
319
320
if (targetFound) {
321
println("Search completed - target found!")
322
} else {
323
println("Search completed - target not found.")
324
}
325
```
326
327
### Advanced Tree Traversal Patterns
328
329
Complex patterns combining walking and visitor approaches for sophisticated file operations.
330
331
**Usage Examples:**
332
333
```kotlin
334
import kotlin.io.path.*
335
import java.nio.file.*
336
import java.time.Instant
337
import java.time.temporal.ChronoUnit
338
339
val projectRoot = Paths.get("/home/user/projects")
340
val backupDir = Paths.get("/backup/projects")
341
val archiveDir = Paths.get("/archive")
342
343
// Multi-pass processing: analyze then act
344
// Pass 1: Collect statistics
345
data class DirectoryStats(var fileCount: Int = 0, var totalSize: Long = 0, var lastModified: Instant = Instant.MIN)
346
347
val dirStats = mutableMapOf<Path, DirectoryStats>()
348
349
projectRoot.walk(PathWalkOption.INCLUDE_DIRECTORIES).forEach { path ->
350
if (path.isDirectory()) {
351
dirStats[path] = DirectoryStats()
352
}
353
}
354
355
projectRoot.walk().forEach { file ->
356
val parent = file.parent
357
if (parent != null && parent in dirStats) {
358
val stats = dirStats[parent]!!
359
stats.fileCount++
360
stats.totalSize += file.fileSize()
361
val fileModified = file.getLastModifiedTime().toInstant()
362
if (fileModified.isAfter(stats.lastModified)) {
363
stats.lastModified = fileModified
364
}
365
}
366
}
367
368
// Pass 2: Archive old directories
369
val oneYearAgo = Instant.now().minus(365, ChronoUnit.DAYS)
370
371
dirStats.filter { (_, stats) ->
372
stats.lastModified.isBefore(oneYearAgo) && stats.fileCount > 0
373
}.forEach { (dir, stats) ->
374
println("Archiving old directory: $dir (${stats.fileCount} files, ${stats.totalSize / 1024 / 1024} MB)")
375
376
val archivePath = archiveDir / dir.relativeTo(projectRoot)
377
archivePath.createParentDirectories()
378
dir.copyToRecursively(archivePath, followLinks = false, overwrite = true)
379
dir.deleteRecursively()
380
}
381
382
// Selective backup with filtering
383
val importantExtensions = setOf("kt", "java", "xml", "md", "gradle")
384
val lastBackup = Instant.now().minus(7, ChronoUnit.DAYS)
385
386
projectRoot.walk()
387
.filter { file ->
388
file.extension in importantExtensions &&
389
file.getLastModifiedTime().toInstant().isAfter(lastBackup)
390
}
391
.forEach { file ->
392
val backupPath = backupDir / file.relativeTo(projectRoot)
393
backupPath.createParentDirectories()
394
file.copyTo(backupPath, overwrite = true)
395
println("Backed up: ${file.relativeTo(projectRoot)}")
396
}
397
398
// Complex filtering with visitor pattern
399
val duplicateFiles = mutableMapOf<Long, MutableList<Path>>()
400
401
projectRoot.visitFileTree {
402
onVisitFile { file, attributes ->
403
val size = attributes.size()
404
duplicateFiles.computeIfAbsent(size) { mutableListOf() }.add(file)
405
FileVisitResult.CONTINUE
406
}
407
}
408
409
// Find potential duplicates (same size)
410
val potentialDuplicates = duplicateFiles.filter { (_, files) -> files.size > 1 }
411
412
println("Potential duplicate files (same size):")
413
potentialDuplicates.forEach { (size, files) ->
414
println("Size: $size bytes")
415
files.forEach { file ->
416
println(" - ${file.relativeTo(projectRoot)}")
417
}
418
println()
419
}
420
```