Skip to content

Commit

Permalink
Improve caching of API for retrieving the list of files that will be …
Browse files Browse the repository at this point in the history
…accessed (#1660)

Closes #1446
Closes #1659
  • Loading branch information
paul-dingemans authored Sep 28, 2022
1 parent d1e4fe8 commit a68d924
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.SimpleFileVisitor
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.locks.ReentrantReadWriteLock
import kotlin.concurrent.read
import kotlin.concurrent.write
import kotlin.io.path.isDirectory
import kotlin.system.measureTimeMillis
import mu.KotlinLogging
import org.ec4j.core.Resource
import org.ec4j.core.ResourcePropertiesService
Expand All @@ -17,58 +21,84 @@ import org.jetbrains.kotlin.konan.file.File
private val logger = KotlinLogging.logger {}.initKtLintKLogger()

internal class EditorConfigFinder {
// Do not reuse the generic threadSafeEditorConfigCache to prevent that results are incorrect due to other calls to
// KtLint that result in changing the cache
private val editorConfigCache = ThreadSafeEditorConfigCache()

/**
* Finds all relevant ".editorconfig" files for the given path.
*/
fun findEditorConfigs(path: Path): List<Path> {
readWriteLock.read {
val cacheValue = inMemoryMap[path]
?.also {
logger.info { "Retrieving EditorConfig cache entry for path $path" }
}
return cacheValue
?: readWriteLock.write {
cacheEditorConfigs(path)
.also { cacheValue ->
logger.info { "Creating cache entry for path $path with value $cacheValue" }
}
}
}
}

private fun cacheEditorConfigs(path: Path): List<Path> {
val result = mutableListOf<Path>()
val normalizedPath = path.normalize().toAbsolutePath()
if (path.isDirectory()) {
result += findEditorConfigsInSubDirectories(normalizedPath)
}
result += findEditorConfigsInParentDirectories(normalizedPath)
return result
.map {
// Resolve against original path as the drive letter seems to get lost on WindowsOs
path.resolve(it)
}.toList()

val editorConfigPaths =
result
.map {
// Resolve against original path as the drive letter seems to get lost on WindowsOs
path.resolve(it)
}.toList()
inMemoryMap[path] = editorConfigPaths

return editorConfigPaths
}

private fun findEditorConfigsInSubDirectories(path: Path): List<Path> {
val result = mutableListOf<Path>()
var visitedDirectoryCount = 0

Files.walkFileTree(
path,
object : SimpleFileVisitor<Path>() {
override fun visitFile(
filePath: Path,
fileAttrs: BasicFileAttributes,
): FileVisitResult {
if (filePath.File().name == ".editorconfig") {
logger.trace { "- File: $filePath: add to list of accessed files" }
result.add(filePath)
measureTimeMillis {
Files.walkFileTree(
path,
object : SimpleFileVisitor<Path>() {
override fun visitFile(
filePath: Path,
fileAttrs: BasicFileAttributes,
): FileVisitResult {
if (filePath.File().name == ".editorconfig") {
logger.trace { "- File: $filePath: add to list of accessed files" }
result.add(filePath)
}
return FileVisitResult.CONTINUE
}
return FileVisitResult.CONTINUE
}

override fun preVisitDirectory(
dirPath: Path,
dirAttr: BasicFileAttributes,
): FileVisitResult {
return if (Files.isHidden(dirPath)) {
logger.trace { "- Dir: $dirPath: Ignore" }
FileVisitResult.SKIP_SUBTREE
} else {
logger.trace { "- Dir: $dirPath: Traverse" }
FileVisitResult.CONTINUE
override fun preVisitDirectory(
dirPath: Path,
dirAttr: BasicFileAttributes,
): FileVisitResult {
visitedDirectoryCount++
return if (Files.isHidden(dirPath)) {
logger.trace { "- Dir: $dirPath: Ignore" }
FileVisitResult.SKIP_SUBTREE
} else {
logger.trace { "- Dir: $dirPath: Traverse" }
FileVisitResult.CONTINUE
}
}
}
},
)
},
)
}.also { duration ->
// TODO: Remove (or reduce loglevel to debug/trace) before release 0.48
logger.info {
"Scanning file system to find all '.editorconfig' files in directory '$path' scanned $visitedDirectoryCount directories in $duration ms"
}
}

return result.toList()
}
Expand All @@ -89,4 +119,13 @@ internal class EditorConfigFinder {
.cache(editorConfigCache)
.loader(org.ec4j.core.EditorConfigLoader.of(Version.CURRENT))
.build()

private companion object {
// Do not reuse the generic threadSafeEditorConfigCache to prevent that results are incorrect due to other
// calls to KtLint that result in changing the cache
val editorConfigCache = ThreadSafeEditorConfigCache()

private val readWriteLock = ReentrantReadWriteLock()
private val inMemoryMap = HashMap<Path, List<Path>>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class EditorConfigFinderTest {
}

@Test
fun `Given a directory containing an editorconfig file and multiple subdirectores containing a editorconfig file then get the path of all editorconfig files`(
fun `Given a directory containing an editorconfig file and multiple subdirectories containing a editorconfig file then get the path of all editorconfig files`(
@TempDir
tempDir: Path,
) {
Expand Down

0 comments on commit a68d924

Please sign in to comment.