blob: eeb0d63b7602706ec38c827677f8eebc5a932b4e [file] [log] [blame]
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.incremental
import com.intellij.psi.PsiJavaFile
import com.intellij.util.io.DataExternalizer
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
import org.jetbrains.kotlin.load.java.JavaClassesTracker
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.metadata.builtins.BuiltInsProtoBuf
import org.jetbrains.kotlin.metadata.deserialization.NameResolverImpl
import org.jetbrains.kotlin.metadata.java.JavaClassProtoBuf
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.protobuf.ExtensionRegistryLite
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.serialization.DescriptorSerializer
import org.jetbrains.kotlin.serialization.deserialization.getClassId
import org.jetbrains.kotlin.util.PerformanceCounter
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.sure
import java.io.DataInput
import java.io.DataOutput
import java.io.File
val CONVERTING_JAVA_CLASSES_TO_PROTO = PerformanceCounter.create("Converting Java sources to proto")
class JavaClassesTrackerImpl(
private val cache: IncrementalJvmCache,
private val untrackedJavaClasses: Set<ClassId>
) : JavaClassesTracker {
private val classToSourceSerialized: MutableMap<ClassId, SerializedJavaClassWithSource> = hashMapOf()
val javaClassesUpdates: Collection<SerializedJavaClassWithSource>
get() = classToSourceSerialized.values
private val classDescriptors: MutableList<JavaClassDescriptor> = mutableListOf()
override fun reportClass(classDescriptor: JavaClassDescriptor) {
val classId = classDescriptor.classId!!
if (!cache.isJavaClassToTrack(classId) || classDescriptor.javaSourceFile == null) return
classDescriptors.add(classDescriptor)
}
override fun onCompletedAnalysis(module: ModuleDescriptor) {
for (classId in cache.getObsoleteJavaClasses() + untrackedJavaClasses) {
// Just force the loading obsolete classes
// We assume here that whenever an LazyJavaClassDescriptor instances is created
// it's being passed to JavaClassesTracker::reportClass
module.findClassAcrossModuleDependencies(classId)
}
for (classDescriptor in classDescriptors.toList()) {
val classId = classDescriptor.classId!!
if (cache.isJavaClassAlreadyInCache(classId) || classId in untrackedJavaClasses || classDescriptor.wasContentRequested()) {
assert(classId !in classToSourceSerialized) {
"Duplicated JavaClassDescriptor $classId reported to IC"
}
classToSourceSerialized[classId] = CONVERTING_JAVA_CLASSES_TO_PROTO.time {
classDescriptor.convertToProto()
}
}
}
}
private fun JavaClassDescriptor.wasContentRequested() =
this.safeAs<LazyJavaClassDescriptor>()?.wasScopeContentRequested() != false
}
private val JavaClassDescriptor.javaSourceFile: File?
get() = source.safeAs<PsiSourceElement>()
?.psi?.containingFile?.takeIf { it is PsiJavaFile }
?.virtualFile?.path?.let(::File)
fun JavaClassDescriptor.convertToProto(): SerializedJavaClassWithSource {
val file = javaSourceFile.sure { "convertToProto should only be called for source based classes" }
val extension = JavaClassesSerializerExtension()
val serializer = DescriptorSerializer.createTopLevel(extension)
val classProto = serializer.classProto(this).build()
val (stringTable, qualifiedNameTable) = extension.stringTable.buildProto()
return SerializedJavaClassWithSource(file, SerializedJavaClass(classProto, stringTable, qualifiedNameTable))
}
class SerializedJavaClass(
val proto: ProtoBuf.Class,
val stringTable: ProtoBuf.StringTable,
val qualifiedNameTable: ProtoBuf.QualifiedNameTable
) {
val classId: ClassId
get() = NameResolverImpl(stringTable, qualifiedNameTable).getClassId(proto.fqName)
}
data class SerializedJavaClassWithSource(
val source: File,
val proto: SerializedJavaClass
)
fun SerializedJavaClass.toProtoData() = ClassProtoData(proto, NameResolverImpl(stringTable, qualifiedNameTable))
val JAVA_CLASS_PROTOBUF_REGISTRY =
ExtensionRegistryLite.newInstance()
.also(JavaClassProtoBuf::registerAllExtensions)
// Built-ins extensions are used for annotations' serialization
.also(BuiltInsProtoBuf::registerAllExtensions)
object JavaClassProtoMapValueExternalizer : DataExternalizer<SerializedJavaClass> {
override fun save(output: DataOutput, value: SerializedJavaClass) {
output.writeBytesWithSize(value.proto.toByteArray())
output.writeBytesWithSize(value.stringTable.toByteArray())
output.writeBytesWithSize(value.qualifiedNameTable.toByteArray())
}
private fun DataOutput.writeBytesWithSize(bytes: ByteArray) {
writeInt(bytes.size)
write(bytes)
}
private fun DataInput.readBytesWithSize(): ByteArray {
val bytesLength = readInt()
return ByteArray(bytesLength).also {
readFully(it, 0, bytesLength)
}
}
override fun read(input: DataInput): SerializedJavaClass {
val proto = ProtoBuf.Class.parseFrom(input.readBytesWithSize(), JAVA_CLASS_PROTOBUF_REGISTRY)
val stringTable = ProtoBuf.StringTable.parseFrom(input.readBytesWithSize(), JAVA_CLASS_PROTOBUF_REGISTRY)
val qualifiedNameTable = ProtoBuf.QualifiedNameTable.parseFrom(input.readBytesWithSize(), JAVA_CLASS_PROTOBUF_REGISTRY)
return SerializedJavaClass(proto, stringTable, qualifiedNameTable)
}
}