KT-45777: Take snapshots and compute changes for Java classes

Create `JavaClassDescriptor`s for Java classes
Ignore anonymous and synthetic classes
as they can't impact recompilation.
Clean up handling of local and anonymous classes

Bug: KT-45777
Test: New JavaClassDescriptorCreatorTest
This commit is contained in:
Hung Nguyen
2021-08-11 09:22:39 +01:00
committed by nataliya.valtman
parent 98e4d67900
commit a342c81a9f
25 changed files with 2993 additions and 197 deletions
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.incremental
import java.io.File
/** Information to locate a .class file. */
class ClassFile(
/** Directory or jar containing the .class file. */
val classRoot: File,
/**
* The relative path from [classRoot] to the .class file.
*
* Any '\' characters in the path will be replaced with '/' to create [unixStyleRelativePath].
*/
relativePath: String
) {
/** The Unix-style relative path (with '/' as separators) from [classRoot] to the .class file. */
val unixStyleRelativePath: String
init {
unixStyleRelativePath = relativePath.replace('\\', '/')
}
}
/** Information to locate a .class file, plus their contents. */
class ClassFileWithContents(
val classFile: ClassFile,
val contents: ByteArray
)
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.gradle.incremental
import com.google.common.annotations.VisibleForTesting
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.build.report.BuildReporter
import org.jetbrains.kotlin.build.report.ICReporter
import org.jetbrains.kotlin.build.report.metrics.*
@@ -66,39 +66,90 @@ object ClasspathChangesComputer {
for (key in current.classSnapshots.keys) {
val currentSnapshot = current.classSnapshots[key]!!
val previousSnapshot = previous.classSnapshots[key] ?: return Failure.AddedRemovedClasses
if (currentSnapshot !is KotlinClassSnapshot || previousSnapshot !is KotlinClassSnapshot) {
return Failure.NotYetImplemented
val result = collectClassChanges(currentSnapshot, previousSnapshot, changesCollector)
if (result !is Success) {
return result
}
// TODO: Store results in changesCollector
collectKotlinClassChanges(currentSnapshot, previousSnapshot)
}
return Success
}
@TestOnly
internal fun collectKotlinClassChanges(current: KotlinClassSnapshot, previous: KotlinClassSnapshot): DirtyData {
private fun collectClassChanges(
current: ClassSnapshot,
previous: ClassSnapshot,
@Suppress("UNUSED_PARAMETER") changesCollector: ChangesCollector
): ChangesCollectorResult {
if (current is JavaClassSnapshot && previous is JavaClassSnapshot &&
(current !is RegularJavaClassSnapshot || previous !is RegularJavaClassSnapshot)
) {
return Failure.NotYetImplemented
}
// TODO: Store results in changesCollector and return SUCCESS here
computeClassChanges(current, previous)
return Failure.NotYetImplemented
}
@VisibleForTesting
internal fun computeClassChanges(current: ClassSnapshot, previous: ClassSnapshot): DirtyData {
// TODO Create IncrementalJvmCache early once and reuse it here
val workingDir =
FileUtil.createTempDirectory(this::class.java.simpleName, "_WorkingDir_${UUID.randomUUID()}", /* deleteOnExit */ true)
val incrementalJvmCache = IncrementalJvmCache(workingDir, /* targetOutputDir */ null, FileToCanonicalPathConverter)
val changesCollector = ChangesCollector()
when {
current is KotlinClassSnapshot && previous is KotlinClassSnapshot ->
collectKotlinClassChanges(current, previous, incrementalJvmCache, changesCollector)
current is JavaClassSnapshot && previous is JavaClassSnapshot ->
collectJavaClassChanges(current, previous, incrementalJvmCache, changesCollector)
else -> {
// TODO: Handle current is KotlinClassSnapshot && previous is JavaClassSnapshot, and vice versa
error("Incompatible types: ${current.javaClass.name} vs. ${previous.javaClass.name}")
}
}
workingDir.deleteRecursively()
return changesCollector.getDirtyData(listOf(incrementalJvmCache), NoOpBuildReporter.NoOpICReporter)
}
private fun collectKotlinClassChanges(
current: KotlinClassSnapshot,
previous: KotlinClassSnapshot,
incrementalJvmCache: IncrementalJvmCache,
changesCollector: ChangesCollector
) {
// Store previous snapshot in incrementalJvmCache, the returned ChangesCollector result is not used.
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = previous.classInfo,
sourceFiles = null,
changesCollector = ChangesCollector()
)
incrementalJvmCache.clearCacheForRemovedClasses(changesCollector)
// Compute changes between the current snapshot and the previously stored snapshot, and store the result in changesCollector.
val changesCollector = ChangesCollector()
incrementalJvmCache.saveClassToCache(
kotlinClassInfo = current.classInfo,
sourceFiles = null,
changesCollector = changesCollector
)
incrementalJvmCache.clearCacheForRemovedClasses(changesCollector)
}
workingDir.deleteRecursively()
return changesCollector.getDirtyData(listOf(incrementalJvmCache), NoOpBuildReporter.NoOpICReporter)
private fun collectJavaClassChanges(
current: JavaClassSnapshot,
previous: JavaClassSnapshot,
incrementalJvmCache: IncrementalJvmCache,
changesCollector: ChangesCollector
) {
// Store previous snapshot in incrementalJvmCache, the returned ChangesCollector result is not used.
val previousSnapshot = (previous as RegularJavaClassSnapshot).serializedJavaClass // TODO Handle unsafe cast
incrementalJvmCache.saveJavaClassProto(/* source */ null, previousSnapshot, ChangesCollector())
incrementalJvmCache.clearCacheForRemovedClasses(changesCollector)
// Compute changes between the current snapshot and the previously stored snapshot, and store the result in changesCollector.
val currentSnapshot = (current as RegularJavaClassSnapshot).serializedJavaClass
incrementalJvmCache.saveJavaClassProto(/* source */ null, currentSnapshot, changesCollector)
incrementalJvmCache.clearCacheForRemovedClasses(changesCollector)
}
}
@@ -6,16 +6,22 @@
package org.jetbrains.kotlin.gradle.incremental
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.SerializedJavaClass
/** Snapshot of a classpath. It consists of a list of [ClasspathEntrySnapshot]s. */
class ClasspathSnapshot(val classpathEntrySnapshots: List<ClasspathEntrySnapshot>)
/** Snapshot of a classpath entry (directory or jar). It consists of a list of [ClassSnapshot]s. */
/**
* Snapshot of a classpath entry (directory or jar). It consists of a list of [ClassSnapshot]s.
*
* NOTE: It's important that the path to the classpath entry is not part of this snapshot. The reason is that classpath entries produced by
* different builds or on different machines but having the same contents should be considered the same for better build performance.
*/
class ClasspathEntrySnapshot(
/**
* Maps (Unix-like) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory or
* jar).
* Maps (Unix-style) relative paths of classes to their snapshots. The paths are relative to the containing classpath entry (directory
* or jar).
*/
val classSnapshots: LinkedHashMap<String, ClassSnapshot>
)
@@ -34,4 +40,17 @@ sealed class ClassSnapshot
class KotlinClassSnapshot(val classInfo: KotlinClassInfo) : ClassSnapshot()
/** [ClassSnapshot] of a Java class. */
object JavaClassSnapshot : ClassSnapshot()
sealed class JavaClassSnapshot : ClassSnapshot()
/** [JavaClassSnapshot] of a typical Java class. */
class RegularJavaClassSnapshot(
val serializedJavaClass: SerializedJavaClass
) : JavaClassSnapshot()
/**
* [JavaClassSnapshot] of a Java class where there is nothing to capture.
*
* For example, the snapshot of a local class is empty as a local class can't be referenced from other source files and therefore any
* changes in a local class will not cause recompilation of other source files.
*/
object EmptyJavaClassSnapshot : JavaClassSnapshot()
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.incremental
import com.intellij.util.io.DataExternalizer
import org.jetbrains.kotlin.incremental.JavaClassProtoMapValueExternalizer
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.storage.*
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
@@ -39,7 +40,7 @@ object ClasspathEntrySnapshotSerializer : DataSerializer<ClasspathEntrySnapshot>
object ClassSnapshotDataSerializer : DataSerializer<ClassSnapshot> {
override fun save(output: DataOutput, snapshot: ClassSnapshot) {
output.writeString(snapshot.javaClass.name)
output.writeBoolean(snapshot is KotlinClassSnapshot)
when (snapshot) {
is KotlinClassSnapshot -> KotlinClassSnapshotExternalizer.save(output, snapshot)
is JavaClassSnapshot -> JavaClassSnapshotExternalizer.save(output, snapshot)
@@ -47,10 +48,11 @@ object ClassSnapshotDataSerializer : DataSerializer<ClassSnapshot> {
}
override fun read(input: DataInput): ClassSnapshot {
return when (val className = input.readString()) {
KotlinClassSnapshot::class.java.name -> KotlinClassSnapshotExternalizer.read(input)
JavaClassSnapshot::class.java.name -> JavaClassSnapshotExternalizer.read(input)
else -> error("Unrecognized class: $className")
val isKotlinClassSnapshot = input.readBoolean()
return if (isKotlinClassSnapshot) {
KotlinClassSnapshotExternalizer.read(input)
} else {
JavaClassSnapshotExternalizer.read(input)
}
}
}
@@ -122,10 +124,42 @@ object FqNameExternalizer : DataExternalizer<FqName> {
object JavaClassSnapshotExternalizer : DataExternalizer<JavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: JavaClassSnapshot) {
output.writeBoolean(snapshot is RegularJavaClassSnapshot)
when (snapshot) {
is RegularJavaClassSnapshot -> RegularJavaClassSnapshotExternalizer.save(output, snapshot)
is EmptyJavaClassSnapshot -> EmptyJavaClassSnapshotExternalizer.save(output, snapshot)
}
}
override fun read(input: DataInput): JavaClassSnapshot {
return JavaClassSnapshot
val isPlainJavaClassSnapshot = input.readBoolean()
return if (isPlainJavaClassSnapshot) {
RegularJavaClassSnapshotExternalizer.read(input)
} else {
EmptyJavaClassSnapshotExternalizer.read(input)
}
}
}
object RegularJavaClassSnapshotExternalizer : DataExternalizer<RegularJavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: RegularJavaClassSnapshot) {
JavaClassProtoMapValueExternalizer.save(output, snapshot.serializedJavaClass)
}
override fun read(input: DataInput): RegularJavaClassSnapshot {
return RegularJavaClassSnapshot(serializedJavaClass = JavaClassProtoMapValueExternalizer.read(input))
}
}
object EmptyJavaClassSnapshotExternalizer : DataExternalizer<EmptyJavaClassSnapshot> {
override fun save(output: DataOutput, snapshot: EmptyJavaClassSnapshot) {
// Nothing to save
}
override fun read(input: DataInput): EmptyJavaClassSnapshot {
return EmptyJavaClassSnapshot
}
}
@@ -5,8 +5,8 @@
package org.jetbrains.kotlin.gradle.incremental
import org.jetbrains.kotlin.gradle.incremental.ClasspathEntryContentsReader.Companion.DEFAULT_CLASS_FILTER
import org.jetbrains.kotlin.incremental.KotlinClassInfo
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.name.ClassId
import java.io.File
import java.util.zip.ZipInputStream
@@ -14,102 +14,158 @@ import java.util.zip.ZipInputStream
@Suppress("SpellCheckingInspection")
object ClasspathEntrySnapshotter {
private val DEFAULT_CLASS_FILTER = { unixStyleRelativePath: String, isDirectory: Boolean ->
!isDirectory
&& unixStyleRelativePath.endsWith(".class", ignoreCase = true)
&& !unixStyleRelativePath.endsWith("module-info.class", ignoreCase = true)
&& !unixStyleRelativePath.startsWith("meta-inf", ignoreCase = true)
}
fun snapshot(classpathEntry: File): ClasspathEntrySnapshot {
val pathsToContents: LinkedHashMap<String, ByteArray> =
ClasspathEntryContentsReader.from(classpathEntry).readContents(DEFAULT_CLASS_FILTER)
val classes =
DirectoryOrJarContentsReader.read(classpathEntry, DEFAULT_CLASS_FILTER)
.map { (unixStyleRelativePath, contents) ->
ClassFileWithContents(ClassFile(classpathEntry, unixStyleRelativePath), contents)
}
val pathsToSnapshots = LinkedHashMap<String, ClassSnapshot>()
pathsToContents.mapValuesTo(pathsToSnapshots) { (_, classContents) ->
ClassSnapshotter.snapshot(classContents)
}
val snapshots = ClassSnapshotter.snapshot(classes)
return ClasspathEntrySnapshot(pathsToSnapshots)
val relativePathsToSnapshotsMap =
classes.map { it.classFile.unixStyleRelativePath }.zip(snapshots).toMap(LinkedHashMap())
return ClasspathEntrySnapshot(relativePathsToSnapshotsMap)
}
}
/** Computes a [ClassSnapshot] of a class. */
/** Creates [ClassSnapshot]s of classes. */
@Suppress("SpellCheckingInspection")
object ClassSnapshotter {
fun snapshot(classContents: ByteArray): ClassSnapshot {
return KotlinClassInfo.tryCreateFrom(classContents)?.let { KotlinClassSnapshot(it) }
?: JavaClassSnapshot
}
}
/** Utility to read the contents of a classpath entry (directory or jar). */
sealed class ClasspathEntryContentsReader {
companion object {
val DEFAULT_CLASS_FILTER = { invariantSeparatorsRelativePath: String, isDirectory: Boolean ->
!isDirectory
&& invariantSeparatorsRelativePath.endsWith(".class", ignoreCase = true)
&& !invariantSeparatorsRelativePath.endsWith("module-info.class", ignoreCase = true)
&& !invariantSeparatorsRelativePath.startsWith("meta-inf", ignoreCase = true)
/**
* Creates [ClassSnapshot]s of the given classes.
*
* Note that for Java (non-Kotlin) classes, creating a [ClassSnapshot] for a nested class will require accessing the outer class (and
* possibly vice versa). Therefore, outer classes and nested classes must be passed together in one invocation of this method.
*/
fun snapshot(classes: List<ClassFileWithContents>): List<ClassSnapshot> {
// Snapshot Kotlin classes first
val kotlinClassSnapshots: Map<ClassFile, KotlinClassSnapshot?> = classes.associate {
it.classFile to trySnapshotKotlinClass(it)
}
/** Creates a [ClasspathEntryContentsReader] for the given classpath entry (directory or jar). */
fun from(classpathEntry: File): ClasspathEntryContentsReader {
return if (classpathEntry.isDirectory) {
DirectoryContentsReader(classpathEntry)
} else {
JarContentsReader(classpathEntry)
}
// Snapshot Java classes in one invocation
val javaClasses: List<ClassFileWithContents> = classes.filter { kotlinClassSnapshots[it.classFile] == null }
val snapshots: List<JavaClassSnapshot> = snapshotJavaClasses(javaClasses)
val javaClassSnapshots: Map<ClassFile, JavaClassSnapshot> = javaClasses.map { it.classFile }.zip(snapshots).toMap()
// Return a snapshot for each class
return classes.map { kotlinClassSnapshots[it.classFile] ?: javaClassSnapshots[it.classFile]!! }
}
/** Creates [KotlinClassSnapshot] of the given class, or returns `null` if the class is not a Kotlin class. */
private fun trySnapshotKotlinClass(clazz: ClassFileWithContents): KotlinClassSnapshot? {
return KotlinClassInfo.tryCreateFrom(clazz.contents)?.let {
KotlinClassSnapshot(it)
}
}
/**
* Returns a map from (Unix-like) relative paths of classes to their contents. The paths are relative to the containing classpath entry
* (directory or jar).
* Creates [JavaClassSnapshot]s of the given Java classes.
*
* Note that creating a [JavaClassSnapshot] for a nested class will require accessing the outer class (and possibly vice versa).
* Therefore, outer classes and nested classes must be passed together in one invocation of this method.
*/
private fun snapshotJavaClasses(classes: List<ClassFileWithContents>): List<JavaClassSnapshot> {
val classFiles = classes.map { it.classFile }
val classesContents = classes.map { it.contents }
val classNames = classesContents.map { JavaClassName.compute(it) }
val classIds = computeJavaClassIds(classNames)
// Snapshot special cases first
// Map a class index to its snapshot, or `null` if it will be created later
val specialCaseSnapshots: Map<Int, JavaClassSnapshot?> = classFiles.indices.associateWith { index ->
val className = classNames[index]
val classId = classIds[index]
if (classId.isLocal) {
// A local class can't be referenced from other source files, so any changes in a local class will not cause recompilation
// of other source files. Therefore, the snapshot of a local class is empty.
// In that regard, a nested class of a local class is also considered local (which matches the definition of
// ClassId.isLocal, see ClassId's kdoc). Therefore, we checked `classId.isLocal`, which is a super set of `className is
// LocalClass`.
EmptyJavaClassSnapshot
} else if (className is NestedNonLocalClass && (className.isAnonymous || className.isSynthetic)) {
// An anonymous or synthetic class also can't be referenced from other source files, so its snapshot is also empty.
EmptyJavaClassSnapshot
} else {
null
}
}
// Snapshot the remaining classes in one invocation
val remainingClassesIndices: List<Int> = classFiles.indices.filter { specialCaseSnapshots[it] == null }
val remainingClassIds: List<ClassId> = remainingClassesIndices.map { classIds[it] }
val remainingClassesContents: List<ByteArray> = remainingClassesIndices.map { classes[it].contents }
val snapshots: List<JavaClassSnapshot> = JavaClassDescriptorCreator.create(remainingClassIds, remainingClassesContents).map {
RegularJavaClassSnapshot(it.toSerializedJavaClass())
}
val remainingSnapshots: Map<Int, JavaClassSnapshot> /* maps a class index to its snapshot */ =
remainingClassesIndices.zip(snapshots).toMap()
// Return a snapshot for each class
return classFiles.indices.map { specialCaseSnapshots[it] ?: remainingSnapshots[it]!! }
}
}
/** Utility to read the contents of a directory or jar. */
private object DirectoryOrJarContentsReader {
/**
* Returns a map from Unix-style relative paths of entries to their contents. The paths are relative to the container (directory or
* jar).
*
* The map entries need to satisfy the given filter.
*
* The map entries are sorted based on their (Unix-like) relative paths (to ensure deterministic results across filesystems).
* The map entries are sorted based on their Unix-style relative paths (to ensure deterministic results across filesystems).
*
* Note: If a jar has duplicate entries, only one of them will be used (there is no guarantee which one will be used).
*/
abstract fun readContents(filter: ((invariantSeparatorsRelativePath: String, isDirectory: Boolean) -> Boolean)? = null):
LinkedHashMap<String, ByteArray>
}
/** Utility to read the contents of a directory. */
class DirectoryContentsReader(private val directory: File) : ClasspathEntryContentsReader() {
init {
check(directory.isDirectory)
fun read(
directoryOrJar: File,
entryFilter: ((unixStyleRelativePath: String, isDirectory: Boolean) -> Boolean)? = null
): LinkedHashMap<String, ByteArray> {
return if (directoryOrJar.isDirectory) {
readDirectory(directoryOrJar, entryFilter)
} else {
check(directoryOrJar.isFile && directoryOrJar.path.endsWith(".jar", ignoreCase = true))
readJar(directoryOrJar, entryFilter)
}
}
override fun readContents(
filter: ((invariantSeparatorsRelativePath: String, isDirectory: Boolean) -> Boolean)?
private fun readDirectory(
directory: File,
entryFilter: ((unixStyleRelativePath: String, isDirectory: Boolean) -> Boolean)? = null
): LinkedHashMap<String, ByteArray> {
val relativePathsToContents: MutableList<Pair<String, ByteArray>> = mutableListOf()
directory.walk().forEach {
val invariantSeparatorsRelativePath = it.relativeTo(directory).invariantSeparatorsPath
if (filter == null || filter(invariantSeparatorsRelativePath, it.isDirectory)) {
relativePathsToContents.add(invariantSeparatorsRelativePath to it.readBytes())
directory.walk().forEach { file ->
val unixStyleRelativePath = file.relativeTo(directory).invariantSeparatorsPath
if (entryFilter == null || entryFilter(unixStyleRelativePath, file.isDirectory)) {
relativePathsToContents.add(unixStyleRelativePath to file.readBytes())
}
}
return relativePathsToContents.sortedBy { it.first }.toMap(LinkedHashMap())
}
}
/** Utility to read the contents of a jar. */
class JarContentsReader(private val jarFile: File) : ClasspathEntryContentsReader() {
init {
check(jarFile.path.endsWith(".jar", ignoreCase = true))
}
override fun readContents(
filter: ((invariantSeparatorsRelativePath: String, isDirectory: Boolean) -> Boolean)?
private fun readJar(
jarFile: File,
entryFilter: ((unixStyleRelativePath: String, isDirectory: Boolean) -> Boolean)? = null
): LinkedHashMap<String, ByteArray> {
val relativePathsToContents: MutableList<Pair<String, ByteArray>> = mutableListOf()
ZipInputStream(jarFile.inputStream().buffered()).use { zipInputStream ->
while (true) {
val entry = zipInputStream.nextEntry ?: break
if (filter == null || filter(entry.name, entry.isDirectory)) {
relativePathsToContents.add(entry.name to zipInputStream.readBytes())
val unixStyleRelativePath = entry.name
if (entryFilter == null || entryFilter(unixStyleRelativePath, entry.isDirectory)) {
relativePathsToContents.add(unixStyleRelativePath to zipInputStream.readBytes())
}
}
}
@@ -46,7 +46,7 @@ abstract class KaptGenerateStubsTask : KotlinCompile(KotlinJvmOptionsImpl()) {
) : KotlinCompile.Configurator<KaptGenerateStubsTask>(kotlinCompilation, properties) {
override fun getClasspathSnapshotDir(task: KaptGenerateStubsTask): Provider<Directory> =
task.project.objects.directoryProperty().dir(classpathSnapshotDir.path)
task.project.objects.directoryProperty().fileValue(classpathSnapshotDir)
override fun configure(task: KaptGenerateStubsTask) {
super.configure(task)
@@ -33,19 +33,17 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
@Test
fun testCollectClassChanges_changedPublicMethodSignature() {
val updatedSnapshot = testSourceFile.changePublicMethodSignature().compileAndSnapshot()
val dirtyData = ClasspathChangesComputer.collectKotlinClassChanges(
updatedSnapshot as KotlinClassSnapshot,
originalSnapshot as KotlinClassSnapshot
)
val dirtyData = ClasspathChangesComputer.computeClassChanges(updatedSnapshot, originalSnapshot)
val testClass = testSourceFile.sourceFile.unixStyleRelativePath.substringBeforeLast('.').replace('/', '.')
assertEquals(
DirtyData(
dirtyLookupSymbols = setOf(
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = "com.example.SimpleKotlinClass"),
LookupSymbol(name = "publicMethod", scope = "com.example.SimpleKotlinClass"),
LookupSymbol(name = "changedPublicMethod", scope = "com.example.SimpleKotlinClass")
LookupSymbol(name = SAM_LOOKUP_NAME.asString(), scope = testClass),
LookupSymbol(name = "publicMethod", scope = testClass),
LookupSymbol(name = "changedPublicMethod", scope = testClass)
),
dirtyClassesFqNames = setOf(FqName("com.example.SimpleKotlinClass")),
dirtyClassesFqNames = setOf(FqName(testClass)),
dirtyClassesFqNamesForceRecompile = emptySet()
),
dirtyData
@@ -55,10 +53,7 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
@Test
fun testCollectClassChanges_changedMethodImplementation() {
val updatedSnapshot = testSourceFile.changeMethodImplementation().compileAndSnapshot()
val dirtyData = ClasspathChangesComputer.collectKotlinClassChanges(
updatedSnapshot as KotlinClassSnapshot,
originalSnapshot as KotlinClassSnapshot
)
val dirtyData = ClasspathChangesComputer.computeClassChanges(updatedSnapshot, originalSnapshot)
assertEquals(DirtyData(emptySet(), emptySet(), emptySet()), dirtyData)
}
@@ -67,3 +62,7 @@ abstract class ClasspathChangesComputerTest : ClasspathSnapshotTestCommon() {
class KotlinClassesClasspathChangesComputerTest : ClasspathChangesComputerTest() {
override val testSourceFile = SimpleKotlinClass(tmpDir)
}
class JavaClassesClasspathChangesComputerTest : ClasspathChangesComputerTest() {
override val testSourceFile = SimpleJavaClass(tmpDir)
}
@@ -5,7 +5,7 @@
package org.jetbrains.kotlin.gradle.incremental
import org.junit.Assert.assertEquals
import org.junit.Assert.*
import org.junit.Test
abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
@@ -13,7 +13,7 @@ abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
protected abstract val testSourceFile: ChangeableTestSourceFile
@Test
fun `test ClassSnapshotDataSerializer`() {
open fun `test ClassSnapshotDataSerializer`() {
val originalSnapshot = testSourceFile.compileAndSnapshot()
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
@@ -25,3 +25,30 @@ abstract class ClasspathSnapshotSerializerTest : ClasspathSnapshotTestCommon() {
class KotlinClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTest() {
override val testSourceFile = SimpleKotlinClass(tmpDir)
}
class JavaClassesClasspathSnapshotSerializerTest : ClasspathSnapshotSerializerTest() {
override val testSourceFile = SimpleJavaClass(tmpDir)
@Test
override fun `test ClassSnapshotDataSerializer`() {
val originalSnapshot = testSourceFile.compileAndSnapshot()
val serializedSnapshot = ClassSnapshotDataSerializer.toByteArray(originalSnapshot)
val deserializedSnapshot = ClassSnapshotDataSerializer.fromByteArray(serializedSnapshot)
// The deserialized object does not exactly match the original object as they contain fields called `memoizedSerializedSize` which
// are not part of the serialized data and their values seem to be generated separately. Therefore, we remove these fields from the
// check below.
assertNotEquals(originalSnapshot.toGson(), deserializedSnapshot.toGson())
assertEquals(
originalSnapshot.toGson().stripLinesContainingText("memoizedSerializedSize"),
deserializedSnapshot.toGson().stripLinesContainingText("memoizedSerializedSize")
)
// Add another check to confirm that those fields are not part of the serialized data.
assertArrayEquals(serializedSnapshot, ClassSnapshotDataSerializer.toByteArray(deserializedSnapshot))
}
private fun String.stripLinesContainingText(text: String): String {
return lines().filterNot { it.contains(text, ignoreCase = true) }.joinToString("\n")
}
}
@@ -6,6 +6,9 @@
package org.jetbrains.kotlin.gradle.incremental
import com.google.gson.GsonBuilder
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.readBytes
import org.jetbrains.kotlin.gradle.incremental.ClasspathSnapshotTestCommon.Util.snapshot
import org.jetbrains.kotlin.gradle.util.compileSources
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import java.io.File
@@ -19,51 +22,99 @@ abstract class ClasspathSnapshotTestCommon {
@get:Rule
val tmpDir = TemporaryFolder()
abstract class RelativeFile(val baseDir: File, val relativePath: String) {
fun asFile() = File(baseDir, relativePath)
}
// Use Gson to compare objects
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
protected fun Any.toGson(): String = gson.toJson(this)
class SourceFile(baseDir: File, relativePath: String) : RelativeFile(baseDir, relativePath) {
init {
check(relativePath.endsWith(".kt") || relativePath.endsWith(".java"))
}
}
class SourceFile(val baseDir: File, relativePath: String) {
val unixStyleRelativePath: String
class ClassFile(baseDir: File, relativePath: String) : RelativeFile(baseDir, relativePath) {
init {
check(relativePath.endsWith(".class"))
unixStyleRelativePath = relativePath.replace('\\', '/')
}
fun snapshot() = ClassSnapshotter.snapshot(asFile().readBytes())
fun asFile() = File(baseDir, unixStyleRelativePath)
}
open class TestSourceFile(val sourceFile: SourceFile, val tmpDir: TemporaryFolder) {
/** Same as [SourceFile] but with a [TemporaryFolder] to store the results of operations on the [SourceFile]. */
open class TestSourceFile(val sourceFile: SourceFile, protected val tmpDir: TemporaryFolder) {
fun replace(oldValue: String, newValue: String): TestSourceFile {
fun replace(oldValue: String, newValue: String, newBaseDir: File? = null): TestSourceFile {
val fileContents = sourceFile.asFile().readText()
check(fileContents.contains(oldValue)) { "String '$oldValue' not found in file '${sourceFile.asFile().path}'" }
val newSourceFile = SourceFile(tmpDir.newFolder(), sourceFile.relativePath).also { it.asFile().parentFile.mkdirs() }
val newSourceFile = SourceFile(newBaseDir ?: tmpDir.newFolder(), sourceFile.unixStyleRelativePath)
newSourceFile.asFile().parentFile.mkdirs()
newSourceFile.asFile().writeText(fileContents.replace(oldValue, newValue))
return TestSourceFile(newSourceFile, tmpDir)
}
fun compile(): ClassFile {
/**
* Compiles this source file and returns a single generated .class file, or fails if zero or more than one .class file was
* generated.
*
* Alternatively, the caller can call [compileAll] to get all generated .class files.
*/
fun compile(): ClassFile = compileAll().single()
/** Compiles this source file and returns all generated .class files. */
@Suppress("MemberVisibilityCanBePrivate")
fun compileAll(): List<ClassFile> {
val filePath = sourceFile.asFile().path
return when {
sourceFile.relativePath.endsWith(".kt") -> compileKotlin()
else -> error("Unexpected file name extension: '${sourceFile.asFile().path}'")
filePath.endsWith(".kt") -> compileKotlin()
filePath.endsWith(".java") -> compileJava()
else -> error("Unexpected file name extension: $filePath")
}
}
private fun compileKotlin(): ClassFile {
private fun compileKotlin(): List<ClassFile> {
// TODO: Call Kotlin compiler to generate classes (see https://github.com/JetBrains/kotlin/pull/4512#discussion_r679432232)
return ClassFile(
File(sourceFile.baseDir.path.substringBeforeLast("src") + "classes" + sourceFile.baseDir.path.substringAfterLast("src")),
sourceFile.relativePath.substringBeforeLast('.') + ".class"
// Currently, we use the precompiled classes in the test data.
return listOf(
ClassFile(
File(testDataDir.path + "/classes/" + sourceFile.baseDir.name),
sourceFile.unixStyleRelativePath.replace(".kt", ".class")
)
)
}
fun compileAndSnapshot(): ClassSnapshot = compile().snapshot()
private fun compileJava(): List<ClassFile> {
val classesDir = tmpDir.newFolder()
compileSources(listOf(sourceFile.asFile()), classesDir)
return classesDir.walk().filter { it.isFile }
.map { ClassFile(classesDir, it.toRelativeString(classesDir)) }
.sortedBy { it.unixStyleRelativePath.substringBefore(".class") }
.toList()
}
/**
* Compiles this source file and returns the snapshot of a single generated .class file, or fails if zero or more than one .class
* file was generated.
*
* Alternatively, the caller can call [compileAndSnapshotAll] to get the snapshots of all generated .class files.
*/
fun compileAndSnapshot() = compile().snapshot()
/** Compiles this source file and returns the snapshots of all generated .class files. */
fun compileAndSnapshotAll(): List<ClassSnapshot> {
val classes = compileAll().map {
ClassFileWithContents(it, it.readBytes())
}
return ClassSnapshotter.snapshot(classes)
}
}
object Util {
fun ClassFile.readBytes(): ByteArray {
// The class files in tests are currently in a directory, so we don't need to handle jars
return File(classRoot, unixStyleRelativePath).readBytes()
}
fun ClassFile.snapshot(): ClassSnapshot {
return ClassSnapshotter.snapshot(listOf(ClassFileWithContents(this, readBytes()))).single()
}
}
abstract class ChangeableTestSourceFile(sourceFile: SourceFile, tmpDir: TemporaryFolder) : TestSourceFile(sourceFile, tmpDir) {
@@ -77,26 +128,35 @@ abstract class ClasspathSnapshotTestCommon {
SourceFile(File(testDataDir, "src/original"), "com/example/SimpleKotlinClass.kt"), tmpDir
) {
override fun changePublicMethodSignature(): TestSourceFile {
return TestSourceFile(
SourceFile(
File(sourceFile.baseDir.path.replace("original", "changedPublicMethodSignature")),
sourceFile.relativePath
), tmpDir
)
}
override fun changePublicMethodSignature() = replace(
"publicMethod()", "changedPublicMethod()",
newBaseDir = File(tmpDir.newFolder(), "changedPublicMethodSignature")
)
override fun changeMethodImplementation(): TestSourceFile {
return TestSourceFile(
SourceFile(
File(sourceFile.baseDir.path.replace("original", "changedMethodImplementation")),
sourceFile.relativePath
), tmpDir
)
}
override fun changeMethodImplementation() = replace(
"I'm in a public method", "This method implementation has changed!",
newBaseDir = File(tmpDir.newFolder(), "changedMethodImplementation")
)
}
// Use Gson to compare objects
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
protected fun Any.toGson(): String = gson.toJson(this)
class SimpleJavaClass(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
SourceFile(File(testDataDir, "src/original"), "com/example/SimpleJavaClass.java"), tmpDir
) {
override fun changePublicMethodSignature() = replace("publicMethod()", "changedPublicMethod()")
override fun changeMethodImplementation() = replace("I'm in a public method", "This method implementation has changed!")
}
class JavaClassWithNestedClasses(tmpDir: TemporaryFolder) : ChangeableTestSourceFile(
SourceFile(File(testDataDir, "src/original"), "com/example/JavaClassWithNestedClasses.java"), tmpDir
) {
/** The source file contains multiple classes, select the one we are mostly interested in. */
val nestedClassToTest = "com/example/JavaClassWithNestedClasses\$InnerClass"
override fun changePublicMethodSignature() = replace("publicMethod()", "changedPublicMethod()")
override fun changeMethodImplementation() = replace("I'm in a public method", "This method implementation has changed!")
}
}
@@ -52,3 +52,47 @@ abstract class ClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
class KotlinClassesClasspathSnapshotterTest : ClasspathSnapshotterTest() {
override val testSourceFile = SimpleKotlinClass(tmpDir)
}
class JavaClassesClasspathSnapshotterTest : ClasspathSnapshotterTest() {
override val testSourceFile = SimpleJavaClass(tmpDir)
}
class JavaClassWithNestedClassesClasspathSnapshotterTest : ClasspathSnapshotTestCommon() {
private val testSourceFile = JavaClassWithNestedClasses(tmpDir)
private lateinit var testClassSnapshot: ClassSnapshot
@Before
fun setUp() {
testClassSnapshot = testSourceFile.compileAndSnapshotNestedClass()
}
private fun TestSourceFile.compileAndSnapshotNestedClass(): ClassSnapshot {
return compileAndSnapshotAll()[5].also {
assertEquals(
testSourceFile.nestedClassToTest,
(it as RegularJavaClassSnapshot).serializedJavaClass.classId.asString().replace('.', '$')
)
}
}
@Test
fun `test ClassSnapshotter's result against expected snapshot`() {
val expectedSnapshot =
File("${testDataDir.path}/src/original/${testSourceFile.nestedClassToTest}-expected-snapshot.json").readText()
assertEquals(expectedSnapshot, testClassSnapshot.toGson())
}
@Test
fun `test ClassSnapshotter extracts ABI info from a class`() {
val updatedSnapshot = testSourceFile.changePublicMethodSignature().compileAndSnapshotNestedClass()
assertNotEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
}
@Test
fun `test ClassSnapshotter does not extract non-ABI info from a class`() {
val updatedSnapshot = testSourceFile.changeMethodImplementation().compileAndSnapshotNestedClass()
assertEquals(testClassSnapshot.toGson(), updatedSnapshot.toGson())
}
}
@@ -1,12 +0,0 @@
package com.example
class SimpleKotlinClass {
fun publicMethod() {
println("This method implementation has changed!")
}
private fun privateMethod() {
println("I'm in a private method")
}
}
@@ -1,12 +0,0 @@
package com.example
class SimpleKotlinClass {
fun changedPublicMethod() {
println("I'm in a public method")
}
private fun privateMethod() {
println("I'm in a private method")
}
}
@@ -0,0 +1,51 @@
package com.example;
public class JavaClassWithNestedClasses {
public class InnerClass {
public void publicMethod() {
System.out.println("I'm in a public method");
}
private void privateMethod() {
System.out.println("I'm in a private method");
}
public class InnerClassWithinInnerClass {
}
public void someMethod() {
class LocalClassWithinInnerClass {
}
}
}
public static class StaticNestedClass {
}
public void someMethod() {
class LocalClass {
class InnerClassWithinLocalClass {
}
}
Runnable objectOfAnonymousLocalClass = new Runnable() {
@Override
public void run() {
}
};
}
private Runnable objectOfAnonymousNonLocalClass = new Runnable() {
@Override
public void run() {
}
};
public class InnerClassWith$Sign {
}
}
@@ -0,0 +1,697 @@
{
"serializedJavaClass": {
"proto": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 3,
"flags_": 22,
"fqName_": 2,
"companionObjectName_": 0,
"typeParameter_": [],
"supertype_": [
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 16,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBound_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"flexibleUpperBoundId_": 0,
"className_": 5,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"outerTypeId_": 0,
"abbreviatedType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
}
],
"supertypeId_": [],
"supertypeIdMemoizedSerializedSize": -1,
"nestedClassName_": [],
"nestedClassNameMemoizedSerializedSize": -1,
"constructor_": [
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 1,
"flags_": 54,
"valueParameter_": [],
"versionRequirement_": [],
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
}
],
"function_": [
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 13,
"flags_": 32786,
"oldFlags_": 6,
"name_": 6,
"returnType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 16,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBound_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"flexibleUpperBoundId_": 0,
"className_": 7,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"outerTypeId_": 0,
"abbreviatedType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"returnTypeId_": 0,
"typeParameter_": [],
"receiverType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"receiverTypeId_": 0,
"valueParameter_": [],
"typeTable_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"type_": [],
"firstNullable_": -1,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"versionRequirement_": [],
"contract_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"effect_": [],
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 13,
"flags_": 32790,
"oldFlags_": 6,
"name_": 9,
"returnType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 16,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBound_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"flexibleUpperBoundId_": 0,
"className_": 7,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"outerTypeId_": 0,
"abbreviatedType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"returnTypeId_": 0,
"typeParameter_": [],
"receiverType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"receiverTypeId_": 0,
"valueParameter_": [],
"typeTable_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"type_": [],
"firstNullable_": -1,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"versionRequirement_": [],
"contract_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"effect_": [],
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
}
],
"property_": [],
"typeAlias_": [],
"enumEntry_": [],
"sealedSubclassFqName_": [],
"sealedSubclassFqNameMemoizedSerializedSize": -1,
"inlineClassUnderlyingPropertyName_": 0,
"inlineClassUnderlyingType_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"argument_": [],
"nullable_": false,
"flexibleTypeCapabilitiesId_": 0,
"flexibleUpperBoundId_": 0,
"className_": 0,
"typeParameter_": 0,
"typeParameterName_": 0,
"typeAliasName_": 0,
"outerTypeId_": 0,
"abbreviatedTypeId_": 0,
"flags_": 0,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": false,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"inlineClassUnderlyingTypeId_": 0,
"typeTable_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 0,
"type_": [],
"firstNullable_": -1,
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"versionRequirement_": [],
"versionRequirementTable_": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"requirement_": [],
"memoizedIsInitialized": -1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"extensions": {
"fields": {},
"isImmutable": true,
"hasLazyField": false
},
"memoizedHashCode": 0
},
"stringTable": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"string_": [
"com",
"example",
"SimpleJavaClass",
"java",
"lang",
"Object",
"privateMethod",
"kotlin",
"Unit",
"publicMethod"
],
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
"qualifiedNameTable": {
"unknownFields": {
"bytes": [],
"hash": 0
},
"qualifiedName_": [
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 2,
"parentQualifiedName_": -1,
"shortName_": 0,
"kind_": "PACKAGE",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 3,
"parentQualifiedName_": 0,
"shortName_": 1,
"kind_": "PACKAGE",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 7,
"parentQualifiedName_": 1,
"shortName_": 2,
"kind_": "CLASS",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 2,
"parentQualifiedName_": -1,
"shortName_": 3,
"kind_": "PACKAGE",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 3,
"parentQualifiedName_": 3,
"shortName_": 4,
"kind_": "PACKAGE",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 7,
"parentQualifiedName_": 4,
"shortName_": 5,
"kind_": "CLASS",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 2,
"parentQualifiedName_": -1,
"shortName_": 7,
"kind_": "PACKAGE",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
},
{
"unknownFields": {
"bytes": [],
"hash": 0
},
"bitField0_": 7,
"parentQualifiedName_": 6,
"shortName_": 8,
"kind_": "CLASS",
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
}
],
"memoizedIsInitialized": 1,
"memoizedSerializedSize": -1,
"memoizedHashCode": 0
}
}
}
@@ -0,0 +1,12 @@
package com.example;
public class SimpleJavaClass {
public void publicMethod() {
System.out.println("I'm in a public method");
}
private void privateMethod() {
System.out.println("I'm in a private method");
}
}
@@ -9,4 +9,4 @@ class SimpleKotlinClass {
private fun privateMethod() {
println("I'm in a private method")
}
}
}