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:
committed by
nataliya.valtman
parent
98e4d67900
commit
a342c81a9f
@@ -118,7 +118,12 @@ abstract class AbstractIncrementalCache<ClassName>(
|
||||
}
|
||||
}
|
||||
|
||||
protected fun addToClassStorage(proto: ProtoBuf.Class, nameResolver: NameResolver, srcFile: File) {
|
||||
/**
|
||||
* Updates class storage based on the given class proto.
|
||||
*
|
||||
* The `srcFile` argument may be `null` (e.g., if we are processing .class files in jars where source files are not available).
|
||||
*/
|
||||
protected fun addToClassStorage(proto: ProtoBuf.Class, nameResolver: NameResolver, srcFile: File?) {
|
||||
val supertypes = proto.supertypes(TypeTable(proto.typeTable))
|
||||
val parents = supertypes.map { nameResolver.getClassId(it.className).asSingleFqName() }
|
||||
.filter { it.asString() != "kotlin.Any" }
|
||||
@@ -131,7 +136,7 @@ abstract class AbstractIncrementalCache<ClassName>(
|
||||
removedSupertypes.forEach { subtypesMap.removeValues(it, setOf(child)) }
|
||||
|
||||
supertypesMap[child] = parents
|
||||
classFqNameToSourceMap[child] = srcFile
|
||||
srcFile?.let { classFqNameToSourceMap[child] = it }
|
||||
classAttributesMap[child] = ICClassesAttributes(ProtoBuf.Modality.SEALED == Flags.MODALITY.get(proto.flags))
|
||||
}
|
||||
|
||||
|
||||
@@ -184,7 +184,9 @@ open class IncrementalJvmCache(
|
||||
KotlinClassHeader.Kind.CLASS -> {
|
||||
if (sourceFiles != null) {
|
||||
assert(sourceFiles.size == 1) { "Class is expected to have only one source file: $sourceFiles" }
|
||||
addToClassStorage(kotlinClassInfo, sourceFiles.first())
|
||||
addToClassStorage(kotlinClassInfo, sourceFiles.single())
|
||||
} else {
|
||||
addToClassStorage(kotlinClassInfo, null)
|
||||
}
|
||||
|
||||
protoMap.process(kotlinClassInfo, changesCollector)
|
||||
@@ -196,10 +198,10 @@ open class IncrementalJvmCache(
|
||||
}
|
||||
}
|
||||
|
||||
fun saveJavaClassProto(source: File, serializedJavaClass: SerializedJavaClass, collector: ChangesCollector) {
|
||||
fun saveJavaClassProto(source: File?, serializedJavaClass: SerializedJavaClass, collector: ChangesCollector) {
|
||||
val jvmClassName = JvmClassName.byClassId(serializedJavaClass.classId)
|
||||
javaSourcesProtoMap.process(jvmClassName, serializedJavaClass, collector)
|
||||
sourceToClassesMap.add(source, jvmClassName)
|
||||
source?.let { sourceToClassesMap.add(source, jvmClassName) }
|
||||
val (proto, nameResolver) = serializedJavaClass.toProtoData()
|
||||
addToClassStorage(proto, nameResolver, source)
|
||||
// collector.addJavaProto(ClassProtoData(proto, nameResolver))
|
||||
@@ -501,7 +503,7 @@ open class IncrementalJvmCache(
|
||||
value.dumpCollection()
|
||||
}
|
||||
|
||||
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File) {
|
||||
private fun addToClassStorage(classInfo: KotlinClassInfo, srcFile: File?) {
|
||||
val (nameResolver, proto) = JvmProtoBufUtil.readClassDataFrom(classInfo.classHeaderData, classInfo.classHeaderStrings)
|
||||
addToClassStorage(proto, nameResolver, srcFile)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* 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.incremental
|
||||
|
||||
import org.jetbrains.kotlin.builtins.StandardNames
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.SourceFile
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.load.java.JavaClassFinder
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.sources.JavaSourceElement
|
||||
import org.jetbrains.kotlin.load.java.sources.JavaSourceElementFactory
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaAnnotation
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaElement
|
||||
import org.jetbrains.kotlin.load.java.structure.JavaPackage
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass
|
||||
import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext
|
||||
import org.jetbrains.kotlin.load.kotlin.DeserializationComponentsForJava.Companion.createModuleData
|
||||
import org.jetbrains.kotlin.load.kotlin.KotlinClassFinder
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.serialization.DescriptorSerializer
|
||||
import org.jetbrains.kotlin.serialization.deserialization.ErrorReporter
|
||||
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInSerializerProtocol
|
||||
import org.jetbrains.kotlin.serialization.deserialization.builtins.BuiltInsResourceLoader
|
||||
import java.io.InputStream
|
||||
|
||||
/** Creates [JavaClassDescriptor]s of Java classes. */
|
||||
object JavaClassDescriptorCreator {
|
||||
|
||||
/**
|
||||
* Creates [JavaClassDescriptor]s of the given Java classes.
|
||||
*
|
||||
* Note that creating a [JavaClassDescriptor] 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 create(classIds: List<ClassId>, classesContents: List<ByteArray>): List<JavaClassDescriptor> {
|
||||
val binaryJavaClasses = classIds.mapIndexed { index, classId ->
|
||||
createBinaryJavaClass(classId, classesContents[index])
|
||||
}
|
||||
val moduleDescriptor = createModuleData(
|
||||
kotlinClassFinder = NoOpKotlinClassFinder,
|
||||
jvmBuiltInsKotlinClassFinder = JvmBuiltInsKotlinClassFinder(),
|
||||
javaClassFinder = BinaryJavaClassFinder(binaryJavaClasses),
|
||||
moduleName = JavaClassDescriptorCreator::class.java.simpleName,
|
||||
errorReporter = ThrowImmediatelyErrorReporter,
|
||||
javaSourceElementFactory = NoSourceJavaSourceElementFactory
|
||||
).deserializationComponentsForJava.components.moduleDescriptor
|
||||
|
||||
return classIds.map { classId ->
|
||||
moduleDescriptor.findClassAcrossModuleDependencies(classId) as? JavaClassDescriptor
|
||||
?: error("Failed to create JavaClassDescriptor for class '$classId'")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBinaryJavaClass(classId: ClassId, classContents: ByteArray): BinaryJavaClass {
|
||||
val context = ClassifierResolutionContext {
|
||||
null // TODO: Implement this?
|
||||
}
|
||||
val outerClass: JavaClass? = null // TODO: Compute outer class?
|
||||
val innerClassFinder: ((Name) -> JavaClass?) = { _ ->
|
||||
null // TODO: Implement this?
|
||||
}
|
||||
|
||||
return try {
|
||||
BinaryJavaClass(
|
||||
virtualFile = null,
|
||||
fqName = classId.asSingleFqName(),
|
||||
context = context,
|
||||
signatureParser = BinaryClassSignatureParser(),
|
||||
access = 0,
|
||||
outerClass = outerClass,
|
||||
classContent = classContents,
|
||||
innerClassFinder = innerClassFinder
|
||||
)
|
||||
} catch (e: NoSuchMethodError) {
|
||||
// When running unit tests, the above call currently fails as JavaClassDescriptorCreator and BinaryJavaClass are located in
|
||||
// different jars (kotlin-build-common.jar and kotlin-compiler-embeddable.jar), and in the second jar, `com.intellij.*` classes are
|
||||
// repackaged as `org.jetbrains.kotlin.com.intellij.*`. The `virtualFile` argument above (even though it is null) has type
|
||||
// `com.intellij.openapi.vfs.VirtualFile`, which has been repackaged, so the method signatures won't match at run time.
|
||||
// In integrations tests or regular builds, JavaClassDescriptorCreator and BinaryJavaClass are both located in
|
||||
// kotlin-compiler-embeddable.jar with all references renamed together, so there are no issues.
|
||||
// We use reflection here as a workaround for unit tests.
|
||||
val binaryJavaClass = BinaryJavaClass::class.java
|
||||
val constructor = binaryJavaClass.constructors.sortedBy { it.parameters.size }[0]
|
||||
return constructor.newInstance(
|
||||
/* virtualFile */ null,
|
||||
/* fqName */ classId.asSingleFqName(),
|
||||
/* context */ context,
|
||||
/* signatureParser */ BinaryClassSignatureParser(),
|
||||
/* access */ 0,
|
||||
/* outerClass */ outerClass,
|
||||
/* classContent */ classContents,
|
||||
/* innerClassFinder */ innerClassFinder
|
||||
) as BinaryJavaClass
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [JavaClassFinder] that returns results based on the given [BinaryJavaClass] list.
|
||||
*
|
||||
* Note that some returned results are fake/empty (similar to ReflectJavaClassFinder).
|
||||
* TODO: Revise and see if there are any correctness issues.
|
||||
*/
|
||||
private class BinaryJavaClassFinder(binaryJavaClasses: List<BinaryJavaClass>) : JavaClassFinder {
|
||||
|
||||
private val nameToJavaClass: Map<FqName, BinaryJavaClass> = binaryJavaClasses.associateBy { it.fqName }
|
||||
|
||||
override fun findClass(request: JavaClassFinder.Request): JavaClass? {
|
||||
return nameToJavaClass[request.classId.asSingleFqName()]
|
||||
}
|
||||
|
||||
override fun findPackage(fqName: FqName): JavaPackage {
|
||||
return object : JavaPackage {
|
||||
|
||||
override val fqName: FqName
|
||||
get() = fqName
|
||||
|
||||
override val subPackages: Collection<JavaPackage>
|
||||
get() = emptyList()
|
||||
|
||||
override val annotations: Collection<JavaAnnotation>
|
||||
get() = emptyList()
|
||||
|
||||
override val isDeprecatedInJavaDoc: Boolean
|
||||
get() = false
|
||||
|
||||
override fun getClasses(nameFilter: (Name) -> Boolean): Collection<JavaClass> = emptyList()
|
||||
|
||||
override fun findAnnotation(fqName: FqName): JavaAnnotation? = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun knownClassNamesInPackage(packageFqName: FqName): Set<String>? = null
|
||||
}
|
||||
|
||||
/** [KotlinClassFinder] that returns no results (e.g., when we know that the classes are not Kotlin classes). */
|
||||
private object NoOpKotlinClassFinder : KotlinClassFinder {
|
||||
override fun findKotlinClassOrContent(classId: ClassId): KotlinClassFinder.Result? = null
|
||||
override fun findKotlinClassOrContent(javaClass: JavaClass): KotlinClassFinder.Result? = null
|
||||
override fun findMetadata(classId: ClassId): InputStream? = null
|
||||
override fun hasMetadataPackage(fqName: FqName): Boolean = false
|
||||
override fun findBuiltInsData(packageFqName: FqName): InputStream? = null
|
||||
}
|
||||
|
||||
/** [KotlinClassFinder] for Kotlin JVM built-in classes. */
|
||||
private class JvmBuiltInsKotlinClassFinder : KotlinClassFinder {
|
||||
override fun findKotlinClassOrContent(classId: ClassId): KotlinClassFinder.Result? = null
|
||||
override fun findKotlinClassOrContent(javaClass: JavaClass): KotlinClassFinder.Result? = null
|
||||
override fun findMetadata(classId: ClassId): InputStream? = null
|
||||
override fun hasMetadataPackage(fqName: FqName): Boolean = false
|
||||
|
||||
private val builtInsResourceLoader by lazy { BuiltInsResourceLoader() }
|
||||
|
||||
override fun findBuiltInsData(packageFqName: FqName): InputStream? {
|
||||
// Same as ReflectKotlinClassFinder.findBuiltInsData
|
||||
return if (packageFqName.startsWith(StandardNames.BUILT_INS_PACKAGE_NAME)) {
|
||||
builtInsResourceLoader.loadResource(BuiltInSerializerProtocol.getBuiltInsFilePath(packageFqName))
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
/** [ErrorReporter] that throws errors as soon as they are reported. */
|
||||
private object ThrowImmediatelyErrorReporter : ErrorReporter {
|
||||
|
||||
override fun reportIncompleteHierarchy(descriptor: ClassDescriptor, unresolvedSuperClasses: MutableList<String>) {
|
||||
// BinaryJavaClassFinder doesn't have the whole classpath so it is unable to resolve classes such as java.lang.Object.
|
||||
// Ignore this error for now.
|
||||
// TODO: Revisit later to see how we can address this or if we can safely ignore this error.
|
||||
}
|
||||
|
||||
override fun reportCannotInferVisibility(descriptor: CallableMemberDescriptor) {
|
||||
error("Cannot infer visibility for $descriptor")
|
||||
}
|
||||
}
|
||||
|
||||
/** [JavaSourceElementFactory] that doesn't provide a source file for a [JavaElement] (e.g., because the source file is not available). */
|
||||
private object NoSourceJavaSourceElementFactory : JavaSourceElementFactory {
|
||||
|
||||
private class NoSourceJavaElement(override val javaElement: JavaElement) : JavaSourceElement {
|
||||
override fun getContainingFile(): SourceFile = SourceFile.NO_SOURCE_FILE
|
||||
}
|
||||
|
||||
override fun source(javaElement: JavaElement): JavaSourceElement = NoSourceJavaElement(javaElement)
|
||||
}
|
||||
|
||||
/** Similar to [JavaClassDescriptor.convertToProto] but without an associated source file. */
|
||||
fun JavaClassDescriptor.toSerializedJavaClass(): SerializedJavaClass {
|
||||
val extension = JavaClassesSerializerExtension()
|
||||
|
||||
val descriptorSerializer = try {
|
||||
DescriptorSerializer.create(
|
||||
descriptor = this,
|
||||
extension = extension,
|
||||
parentSerializer = null,
|
||||
project = null
|
||||
)
|
||||
} catch (e: NoSuchMethodError) {
|
||||
// When running unit tests, the above call currently fails as the `project` argument above has type
|
||||
// `com.intellij.openapi.project.Project`, which is repackaged as `org.jetbrains.kotlin.com.intellij.openapi.project.Project` in
|
||||
// kotlin-compiler-embeddable.jar (see the comment at the createBinaryJavaClass method for more context).
|
||||
// We use reflection here as a workaround for unit tests.
|
||||
val createMethod = DescriptorSerializer::class.java.methods.single { it.name == "create" }
|
||||
createMethod.invoke(
|
||||
/* static method */ null,
|
||||
/* descriptor */ this,
|
||||
/* extension */ extension,
|
||||
/* parentSerializer */ null,
|
||||
/* project */ null
|
||||
) as DescriptorSerializer
|
||||
}
|
||||
|
||||
val classProto = descriptorSerializer.classProto(this).build()
|
||||
val (stringTable, qualifiedNameTable) = extension.stringTable.buildProto()
|
||||
|
||||
return SerializedJavaClass(classProto, stringTable, qualifiedNameTable)
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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.incremental
|
||||
|
||||
import com.intellij.openapi.util.Ref
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
/**
|
||||
* Information about the name of a compiled Java class regarding its nesting level.
|
||||
*
|
||||
* A [JavaClassName] can be:
|
||||
* - [TopLevelClass]
|
||||
* - [NestedClass] (https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html)
|
||||
*
|
||||
* A [NestedClass] can be:
|
||||
* - [NestedNonLocalClass]
|
||||
* - [LocalClass] (https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html)
|
||||
*/
|
||||
sealed class JavaClassName(
|
||||
|
||||
/** The full name of this class (e.g., "com/example/Foo$Bar"). */
|
||||
val name: String
|
||||
) {
|
||||
|
||||
/** The package name of this class (e.g., "com/example"). */
|
||||
val packageName: String
|
||||
get() = name.substringBeforeLast('/', "")
|
||||
|
||||
companion object {
|
||||
|
||||
fun compute(classContents: ByteArray): JavaClassName {
|
||||
val nameRef = Ref.create<String>()
|
||||
val isTopLevelRef = Ref.create<Boolean>()
|
||||
val outerNameRef = Ref.create<String>()
|
||||
val isAnonymousInnerClassRef = Ref.create<Boolean>()
|
||||
val isSyntheticInnerClassRef = Ref.create<Boolean>()
|
||||
|
||||
ClassReader(classContents).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visit(
|
||||
version: Int, access: Int, name: String,
|
||||
signature: String?, superName: String?, interfaces: Array<String?>?
|
||||
) {
|
||||
nameRef.set(name)
|
||||
}
|
||||
|
||||
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
|
||||
if (name == nameRef.get()!!) {
|
||||
isTopLevelRef.set(false)
|
||||
outerNameRef.set(outerName)
|
||||
isAnonymousInnerClassRef.set(innerName == null)
|
||||
isSyntheticInnerClassRef.set((access and Opcodes.ACC_SYNTHETIC) != 0)
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
|
||||
|
||||
val name = nameRef.get()!!
|
||||
val isTopLevel = isTopLevelRef.get() ?: true
|
||||
val outerName = outerNameRef.get()
|
||||
|
||||
return when {
|
||||
isTopLevel -> TopLevelClass(name)
|
||||
outerName != null -> NestedNonLocalClass(
|
||||
name,
|
||||
outerName,
|
||||
isAnonymousInnerClassRef.get()!!,
|
||||
isSyntheticInnerClassRef.get()!!
|
||||
)
|
||||
else -> LocalClass(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** See [JavaClassName]. */
|
||||
class TopLevelClass(name: String) : JavaClassName(name) {
|
||||
|
||||
/**
|
||||
* The simple name of this class (e.g., the simple name of class "com/example/Foo" is "Foo", the simple name of class
|
||||
* "com/example/ClassWith$Sign" is "ClassWith$Sign").
|
||||
*/
|
||||
val simpleName: String
|
||||
get() = name.substringAfterLast('/')
|
||||
}
|
||||
|
||||
/** See [JavaClassName]. */
|
||||
sealed class NestedClass(name: String) : JavaClassName(name)
|
||||
|
||||
/** See [JavaClassName]. */
|
||||
class NestedNonLocalClass(
|
||||
name: String,
|
||||
|
||||
/**
|
||||
* The full name of the outer class of this class (e.g., the outer name of "com/example/OuterClass$NestedClass" is
|
||||
* "com/example/OuterClass", the outer name of class "com/example/OuterClassWith$Sign$NestedClassWith$Sign" is
|
||||
* "com/example/OuterClassWith$Sign").
|
||||
*
|
||||
* The outer class can be of any type ([TopLevelClass], [NestedNonLocalClass], or [LocalClass]).
|
||||
*/
|
||||
val outerName: String,
|
||||
|
||||
/**
|
||||
* Whether this class is an anonymous class.
|
||||
*
|
||||
* Note: Even though an anonymous class has no name in the source code, it always has a (not-null, not-empty) name in the compiled
|
||||
* class. Therefore, [simpleName] is not `null` and not empty even for anonymous classes.
|
||||
* */
|
||||
val isAnonymous: Boolean,
|
||||
|
||||
/** Whether this class is a synthetic class. */
|
||||
val isSynthetic: Boolean
|
||||
) : NestedClass(name) {
|
||||
|
||||
init {
|
||||
check(name.startsWith("$outerName\$"))
|
||||
}
|
||||
|
||||
/**
|
||||
* The simple name of this class (e.g., the simple name of "com/example/OuterClass$NestedClass" is "NestedClass", the simple name of
|
||||
* class "com/example/OuterClassWith$Sign$NestedClassWith$Sign" is "NestedClassWith$Sign").
|
||||
*
|
||||
* Note: [simpleName] is not `null` and not empty even for anonymous classes (see [isAnonymous]).
|
||||
*/
|
||||
val simpleName: String
|
||||
get() = name.substring("$outerName\$".length)
|
||||
}
|
||||
|
||||
/** See [JavaClassName]. */
|
||||
class LocalClass(name: String) : NestedClass(name)
|
||||
|
||||
/**
|
||||
* Computes [ClassId]s of the given Java classes.
|
||||
*
|
||||
* Note that creating a [ClassId] for a nested class will require accessing the outer class for 2 reasons:
|
||||
* - To disambiguate any '$' characters in the class name (e.g., "com/example/OuterClassWith$Sign$NestedClassWith$Sign").
|
||||
* - To determine whether a class is a local class (a nested class of a local class is also considered local, see [ClassId]'s kdoc).
|
||||
*
|
||||
* Therefore, outer classes and nested classes must be passed together in one invocation of this method.
|
||||
*/
|
||||
fun computeJavaClassIds(javaClassNames: List<JavaClassName>): List<ClassId> {
|
||||
val nameToJavaClassName: Map<String, JavaClassName> = javaClassNames.associateBy { it.name }
|
||||
val nameToClassId: MutableMap<String, ClassId?> = nameToJavaClassName.mapValues { null }.toMutableMap()
|
||||
|
||||
fun getOrCreateClassId(className: String): ClassId {
|
||||
val classInfo = nameToJavaClassName[className] ?: error("Class name not found: $className")
|
||||
val computedClassId = nameToClassId[className]
|
||||
if (computedClassId != null) {
|
||||
return computedClassId
|
||||
}
|
||||
|
||||
val packageName = FqName(classInfo.packageName.replace('/', '.'))
|
||||
val classId = when (classInfo) {
|
||||
is TopLevelClass -> {
|
||||
ClassId(packageName, FqName(classInfo.simpleName), /* local */ false)
|
||||
}
|
||||
is NestedNonLocalClass -> {
|
||||
val outerClassId = getOrCreateClassId(classInfo.outerName)
|
||||
val relativeClassName = FqName(outerClassId.relativeClassName.asString() + "." + classInfo.simpleName)
|
||||
// For ClassId, a nested non-local class of a local class is also considered local (see ClassId's kdoc).
|
||||
val isLocal = outerClassId.isLocal
|
||||
|
||||
ClassId(packageName, relativeClassName, /* local */ isLocal)
|
||||
}
|
||||
is LocalClass -> {
|
||||
// Note: The following computation for the relative class name of a local class does not exactly match the description given
|
||||
// in ClassId's kdoc, which currently says "In the case of a local class, relativeClassName consists of a single name
|
||||
// including all callables' and class' names all the way up to the package, separated by dollar signs."
|
||||
//
|
||||
// For example, consider this Java source:
|
||||
// package com.example;
|
||||
// class Foo {
|
||||
// void someMethod() {
|
||||
// class Bar {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// The above class will compile into class "com/example/Foo" and "com/example/Foo$1Bar" (or
|
||||
// "com/example/Foo$SomeOtherArbitraryUniqueName which need not contain the string "Bar").
|
||||
//
|
||||
// Given that class, the difference between the computed ClassId and the expected ClassId is as follows:
|
||||
// Value computed below Expected value as defined in ClassId's kdoc
|
||||
// relativeClassName Foo$1Bar Foo$someMethod$Bar
|
||||
// classId com.example.Foo$1Bar com.example.Foo$someMethod$Bar
|
||||
//
|
||||
// Note: While they don't match, the ClassId computed below is still unique, so it can still be used as an identifier.
|
||||
//
|
||||
// TODO: Compute ClassID to match the expected value. It will require collecting information about the enclosing class,
|
||||
// enclosing method, and simple class name (as written in source code). There will still be a challenge if the class is an
|
||||
// anonymous class: The simple class name is not available, and it's not clear what the expected ClassID is.
|
||||
//
|
||||
// Alternatively, check if we can safely adjust the definition of ClassId for a local class and update any related code
|
||||
// accordingly.
|
||||
val relativeClassName = FqName(classInfo.name.substringAfterLast('/'))
|
||||
ClassId(packageName, relativeClassName, /* local */ true)
|
||||
}
|
||||
}
|
||||
|
||||
return classId.also {
|
||||
nameToClassId[className] = it
|
||||
}
|
||||
}
|
||||
|
||||
return javaClassNames.map { getOrCreateClassId(it.name) }
|
||||
}
|
||||
@@ -187,7 +187,6 @@ object StringExternalizer : DataExternalizer<String> {
|
||||
override fun read(input: DataInput): String = IOUtil.readString(input)
|
||||
}
|
||||
|
||||
|
||||
// Should be consistent with org.jetbrains.jps.incremental.storage.PathStringDescriptor for correct work of portable caches
|
||||
object PathStringDescriptor : EnumeratorStringDescriptor() {
|
||||
private const val PORTABLE_CACHES_PROPERTY = "org.jetbrains.jps.portable.caches"
|
||||
@@ -239,6 +238,37 @@ fun DataOutput.writeString(value: String) = StringExternalizer.save(this, value)
|
||||
|
||||
fun DataInput.readString(): String = StringExternalizer.read(this)
|
||||
|
||||
class NullableValueExternalizer<T>(private val valueExternalizer: DataExternalizer<T>) : DataExternalizer<T> {
|
||||
|
||||
override fun save(output: DataOutput, value: T?) {
|
||||
output.writeBoolean(value != null)
|
||||
value?.let {
|
||||
valueExternalizer.save(output, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): T? {
|
||||
return if (input.readBoolean()) {
|
||||
valueExternalizer.read(input)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
object ByteArrayExternalizer : DataExternalizer<ByteArray> {
|
||||
|
||||
override fun save(output: DataOutput, bytes: ByteArray) {
|
||||
output.writeInt(bytes.size)
|
||||
output.write(bytes)
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): ByteArray {
|
||||
val size = input.readInt()
|
||||
return ByteArray(size).also {
|
||||
input.readFully(it, 0, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ListExternalizer<T>(
|
||||
private val elementExternalizer: DataExternalizer<T>
|
||||
) : DataExternalizer<List<T>> {
|
||||
@@ -284,19 +314,3 @@ class LinkedHashMapExternalizer<K, V>(
|
||||
return map
|
||||
}
|
||||
}
|
||||
|
||||
class NullableValueExternalizer<T>(private val valueExternalizer: DataExternalizer<T>) : DataExternalizer<T> {
|
||||
|
||||
override fun save(output: DataOutput, value: T?) {
|
||||
output.writeBoolean(value != null)
|
||||
value?.let {
|
||||
valueExternalizer.save(output, it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(input: DataInput): T? {
|
||||
return if (input.readBoolean()) {
|
||||
valueExternalizer.read(input)
|
||||
} else null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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.incremental
|
||||
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import javax.tools.ToolProvider
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class JavaClassNameTest {
|
||||
|
||||
@get:Rule
|
||||
val tmpDir = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `test compute JavaClassName`() {
|
||||
val compiledClasses = compileJava(className, sourceCode)
|
||||
val classNames = compiledClasses.map { JavaClassName.compute(it) }
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"TopLevelClass: com/example/JavaClassWithNestedClasses",
|
||||
"LocalClass: com/example/JavaClassWithNestedClasses\$1",
|
||||
"LocalClass: com/example/JavaClassWithNestedClasses\$1LocalClass",
|
||||
"NestedNonLocalClass: com/example/JavaClassWithNestedClasses\$1LocalClass\$InnerClassWithinLocalClass",
|
||||
"LocalClass: com/example/JavaClassWithNestedClasses\$2",
|
||||
"NestedNonLocalClass: com/example/JavaClassWithNestedClasses\$InnerClass",
|
||||
"LocalClass: com/example/JavaClassWithNestedClasses\$InnerClass\$1LocalClassWithinInnerClass",
|
||||
"NestedNonLocalClass: com/example/JavaClassWithNestedClasses\$InnerClass\$InnerClassWithinInnerClass",
|
||||
"NestedNonLocalClass: com/example/JavaClassWithNestedClasses\$InnerClassWith\$Sign",
|
||||
"NestedNonLocalClass: com/example/JavaClassWithNestedClasses\$StaticNestedClass"
|
||||
),
|
||||
classNames.map { "${it.javaClass.simpleName}: ${it.name}" }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test computeJavaClassIds`() {
|
||||
val compiledClasses = compileJava(className, sourceCode)
|
||||
val classNames = compiledClasses.map { JavaClassName.compute(it) }
|
||||
val classIds = computeJavaClassIds(classNames)
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
classId("com.example", "JavaClassWithNestedClasses", local = false),
|
||||
classId("com.example", "JavaClassWithNestedClasses$1", local = true),
|
||||
classId("com.example", "JavaClassWithNestedClasses$1LocalClass", local = true),
|
||||
classId("com.example", "JavaClassWithNestedClasses$1LocalClass.InnerClassWithinLocalClass", local = true),
|
||||
classId("com.example", "JavaClassWithNestedClasses$2", local = true),
|
||||
classId("com.example", "JavaClassWithNestedClasses.InnerClass", local = false),
|
||||
classId("com.example", "JavaClassWithNestedClasses\$InnerClass\$1LocalClassWithinInnerClass", local = true),
|
||||
classId("com.example", "JavaClassWithNestedClasses.InnerClass.InnerClassWithinInnerClass", local = false),
|
||||
classId("com.example", "JavaClassWithNestedClasses.InnerClassWith\$Sign", local = false),
|
||||
classId("com.example", "JavaClassWithNestedClasses.StaticNestedClass", local = false)
|
||||
),
|
||||
classIds
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun compileJava(className: String, sourceCode: String): List<ByteArray> {
|
||||
val sourceFile = File(tmpDir.newFolder(), "$className.java").apply {
|
||||
parentFile.mkdirs()
|
||||
writeText(sourceCode)
|
||||
}
|
||||
val classesDir = tmpDir.newFolder()
|
||||
|
||||
val compiler = ToolProvider.getSystemJavaCompiler()
|
||||
compiler.getStandardFileManager(null, null, null).use { fileManager ->
|
||||
val compilationTask = compiler.getTask(
|
||||
null, fileManager, null,
|
||||
listOf("-d", classesDir.path), null,
|
||||
fileManager.getJavaFileObjectsFromFiles(listOf(sourceFile))
|
||||
)
|
||||
assertTrue(compilationTask.call(), "Failed to compile '$className'")
|
||||
}
|
||||
|
||||
return classesDir.walk().filter { it.isFile }
|
||||
.sortedBy { it.path.substringBefore(".class") }
|
||||
.map { it.readBytes() }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun classId(@Suppress("SameParameterValue") packageFqName: String, relativeClassName: String, local: Boolean) =
|
||||
ClassId(FqName(packageFqName), FqName(relativeClassName), local)
|
||||
}
|
||||
|
||||
private const val className = "com/example/JavaClassWithNestedClasses"
|
||||
private val sourceCode = """
|
||||
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 {
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
+19
-9
@@ -32,13 +32,15 @@ import java.text.CharacterIterator
|
||||
import java.text.StringCharacterIterator
|
||||
|
||||
class BinaryJavaClass(
|
||||
override val virtualFile: VirtualFile,
|
||||
/** If virtualFile is not available, provide classContent & innerClassFinder below. */
|
||||
override val virtualFile: VirtualFile? = null,
|
||||
override val fqName: FqName,
|
||||
internal val context: ClassifierResolutionContext,
|
||||
private val signatureParser: BinaryClassSignatureParser,
|
||||
override var access: Int = 0,
|
||||
override val outerClass: JavaClass?,
|
||||
classContent: ByteArray? = null
|
||||
classContent: ByteArray? = null,
|
||||
private val innerClassFinder: ((Name) -> JavaClass?)? = null
|
||||
) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), VirtualFileBoundJavaClass, BinaryJavaModifierListOwner, MutableJavaAnnotationOwner {
|
||||
override val annotations: MutableCollection<JavaAnnotation> = SmartList()
|
||||
|
||||
@@ -111,13 +113,17 @@ class BinaryJavaClass(
|
||||
}
|
||||
|
||||
init {
|
||||
if (virtualFile == null) {
|
||||
checkNotNull(classContent) { "classContent must be provided when virtualFile is not available" }
|
||||
checkNotNull(innerClassFinder) { "innerClassFinder must be provided when virtualFile is not available" }
|
||||
}
|
||||
try {
|
||||
ClassReader(classContent ?: virtualFile.contentsToByteArray()).accept(
|
||||
ClassReader(classContent ?: virtualFile!!.contentsToByteArray()).accept(
|
||||
this,
|
||||
ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
throw IllegalStateException("Could not read class: $virtualFile", e)
|
||||
throw IllegalStateException("Could not read class " + (virtualFile ?: ""), e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,11 +253,15 @@ class BinaryJavaClass(
|
||||
fun findInnerClass(name: Name, classFileContent: ByteArray?): JavaClass? {
|
||||
val access = ownInnerClassNameToAccess[name] ?: return null
|
||||
|
||||
return virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
|
||||
BinaryJavaClass(
|
||||
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
|
||||
classFileContent
|
||||
)
|
||||
return if (virtualFile != null) {
|
||||
virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let {
|
||||
BinaryJavaClass(
|
||||
it, fqName.child(name), context.copyForMember(), signatureParser, access, this,
|
||||
classFileContent
|
||||
)
|
||||
}
|
||||
} else {
|
||||
innerClassFinder!!(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ class DeserializationComponentsForJava(
|
||||
errorReporter: ErrorReporter,
|
||||
javaSourceElementFactory: JavaSourceElementFactory
|
||||
): ModuleData {
|
||||
val storageManager = LockBasedStorageManager("RuntimeModuleData")
|
||||
val storageManager = LockBasedStorageManager("DeserializationComponentsForJava.ModuleData")
|
||||
val builtIns = JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FROM_DEPENDENCIES)
|
||||
val module = ModuleDescriptorImpl(Name.special("<$moduleName>"), storageManager, builtIns)
|
||||
builtIns.builtInsModule = module
|
||||
|
||||
+36
@@ -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
|
||||
)
|
||||
+61
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+23
-4
@@ -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()
|
||||
|
||||
+40
-6
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+119
-63
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+11
-12
@@ -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)
|
||||
}
|
||||
|
||||
+29
-2
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+101
-41
@@ -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!")
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
-12
@@ -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")
|
||||
}
|
||||
}
|
||||
-12
@@ -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")
|
||||
}
|
||||
}
|
||||
+1119
File diff suppressed because it is too large
Load Diff
+51
@@ -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 {
|
||||
}
|
||||
}
|
||||
+697
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -9,4 +9,4 @@ class SimpleKotlinClass {
|
||||
private fun privateMethod() {
|
||||
println("I'm in a private method")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user