diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/ProcessedFilesCache.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/ProcessedFilesCache.kt index c92fb5b2f30..53be8012d9d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/ProcessedFilesCache.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/ProcessedFilesCache.kt @@ -6,7 +6,9 @@ package org.jetbrains.kotlin.gradle.internal import com.google.gson.GsonBuilder -import com.google.gson.JsonParseException +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter import org.gradle.api.Project import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.hash.FileHasher @@ -15,8 +17,8 @@ import java.io.File /** * Cache for preventing processing some files twice. - * Files are precessed some subdirectories of [targetDir] called `target`. - * For each target [getOrCompute] should be called. + * Files are precessed to some subdirectories of [targetDir] called `target`. + * [getOrCompute] returns path to directory with files produced for given input file. * Absent target directories will be deleted on [close]. * `compute` will be called only for source files that was changed since last processing. * @@ -33,16 +35,104 @@ internal open class ProcessedFilesCache( private val hasher = (project as ProjectInternal).services.get(FileHasher::class.java) private val gson = GsonBuilder().setPrettyPrinting().create() + private fun readFrom(json: JsonReader): State? { + val result = State() + + json.obj { + check(json.nextName() == "version") + val version = json.nextString() + if (version != this.version) return null + + check(json.nextName() == "items") + json.obj { + while (json.peek() == JsonToken.NAME) { + val key = json.nextName() + json.beginObject() + check(json.nextName() == "src") + val src = json.nextString() + + var target: String? = null + if (json.peek() == JsonToken.NAME) { + check(json.nextName() == "target") + if (json.peek() != JsonToken.NULL) { + target = json.nextString() + } + } + json.endObject() + + result[decodeHexString(key)] = Element(src, target) + } + } + } + + return result + } + + private fun State.writeTo(json: JsonWriter) { + json.obj { + json.name("version").value(version) + json.name("items") + json.obj { + byHash.forEach { + json.name(it.key.toHex()) + json.obj { + json.name("src").value(it.value.src) + json.name("target") + if (it.value.target == null) json.nullValue() else json.value(it.value.target) + } + } + } + + } + } + + private inline fun JsonReader.obj(body: () -> Unit) { + beginObject() + body() + endObject() + } + + private inline fun JsonWriter.obj(body: () -> Unit) { + beginObject() + body() + endObject() + } + + fun ByteArray.toHex(): String { + val result = CharArray(size * 2) { ' ' } + var i = 0 + forEach { + val n = it.toInt() + result[i++] = Character.forDigit(n shr 4 and 0xF, 16) + result[i++] = Character.forDigit(n and 0xF, 16) + } + return String(result) + } + + private fun decodeHexString(hexString: String): ByteArray { + check(hexString.length % 2 == 0) + val bytes = ByteArray(hexString.length / 2) + var i = 0 + var o = 0 + while (i < hexString.length) { + bytes[o++] = hexToByte(hexString[i++], hexString[i++]) + } + return bytes + } + + private fun hexToByte(a: Char, b: Char): Byte = ((a.toDigit() shl 4) + b.toDigit()).toByte() + + private fun Char.toDigit(): Int = Character.digit(this, 16).also { check(it != -1) } + private class State { - var version: String? = null - val byHash = mutableMapOf() + val byHash = mutableMapOf() val byTarget = mutableMapOf() - operator fun get(elementHash: ByteArray) = - byHash[elementHash.toHexString()] + operator fun get(elementHash: ByteArray) = byHash[elementHash] operator fun set(elementHash: ByteArray, element: Element) { - byHash[elementHash.toHexString()] = element + elementHash.toHexString() + byHash[elementHash] = element val target = element.target if (target != null) { byTarget[target] = element @@ -62,15 +152,10 @@ internal open class ProcessedFilesCache( targetDir.mkdirs() val state: State? = if (stateFile.exists()) try { - stateFile.reader().use { - gson.fromJson(it, State::class.java) - }.let { - if (it.version != version) { - targetDir.deleteRecursively() - null - } else it + gson.newJsonReader(stateFile.reader()).use { + readFrom(it) } - } catch (e: JsonParseException) { + } catch (e: Throwable) { project.logger.warn("Cannot read $stateFile", e) if (targetDir.exists()) { targetDir.deleteRecursively() @@ -83,10 +168,6 @@ internal open class ProcessedFilesCache( private val new = State() - init { - new.version = version - } - internal fun getOrCompute( file: File, compute: () -> String? @@ -144,8 +225,8 @@ internal open class ProcessedFilesCache( } stateFile.parentFile.mkdirs() - stateFile.writer().use { - gson.toJson(new, it) + gson.newJsonWriter(stateFile.writer()).use { + new.writeTo(it) } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt index 6e80c885554..dba88766e51 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModulesCache.kt @@ -21,7 +21,7 @@ internal class GradleNodeModulesCache(val nodeJs: NodeJsRootExtension) : AutoClo val project: Project get() = nodeJs.project internal val dir = nodeJs.root.nodeModulesGradleCacheDir - private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "8") + private val cache = ProcessedFilesCache(project, dir, STATE_FILE_NAME, "9") @Synchronized fun get(