diff --git a/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/ClassId.java b/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/ClassId.java index 88eac1346c0..c0efac5d2cd 100644 --- a/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/ClassId.java +++ b/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/ClassId.java @@ -22,6 +22,12 @@ import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe; import org.jetbrains.jet.lang.resolve.name.Name; public final class ClassId { + + @NotNull + public static ClassId topLevel(@NotNull FqName topLevelFqName) { + return new ClassId(topLevelFqName.parent(), FqNameUnsafe.topLevel(topLevelFqName.shortName())); + } + private final FqName packageFqName; private final FqNameUnsafe relativeClassName; @@ -55,6 +61,7 @@ public final class ClassId { return relativeClassName.parent().isRoot(); } + @NotNull public FqNameUnsafe asSingleFqName() { if (packageFqName.isRoot()) return relativeClassName; return new FqNameUnsafe(packageFqName.asString() + "." + relativeClassName.asString()); diff --git a/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/DescriptorFinder.java b/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/DescriptorFinder.java index d56637c306e..6ed1cb6e5b0 100644 --- a/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/DescriptorFinder.java +++ b/compiler/frontend/serialization/src/org/jetbrains/jet/descriptors/serialization/DescriptorFinder.java @@ -18,6 +18,7 @@ package org.jetbrains.jet.descriptors.serialization; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.ReadOnly; import org.jetbrains.jet.lang.descriptors.ClassDescriptor; import org.jetbrains.jet.lang.resolve.name.FqName; import org.jetbrains.jet.lang.resolve.name.Name; @@ -43,6 +44,7 @@ public interface DescriptorFinder { @Nullable ClassDescriptor findClass(@NotNull ClassId classId); + @ReadOnly @NotNull Collection getClassNames(@NotNull FqName packageName); } diff --git a/idea/src/org/jetbrains/jet/plugin/libraries/DeserializerForDecompiler.kt b/idea/src/org/jetbrains/jet/plugin/libraries/DeserializerForDecompiler.kt new file mode 100644 index 00000000000..44331f5b8c7 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/libraries/DeserializerForDecompiler.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2010-2014 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.jet.plugin.libraries + +import org.jetbrains.jet.descriptors.serialization.ClassId +import org.jetbrains.jet.descriptors.serialization.DescriptorFinder +import org.jetbrains.jet.descriptors.serialization.JavaProtoBufUtil +import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedClassDescriptor +import org.jetbrains.jet.lang.descriptors.* +import org.jetbrains.jet.lang.descriptors.impl.MutablePackageFragmentDescriptor +import org.jetbrains.jet.lang.resolve.kotlin.KotlinJvmBinaryClass +import org.jetbrains.jet.lang.resolve.name.FqName +import org.jetbrains.jet.lang.resolve.name.Name +import org.jetbrains.jet.lang.types.ErrorUtils +import org.jetbrains.jet.storage.LockBasedStorageManager +import java.util.Collections +import org.jetbrains.jet.lang.resolve.java.resolver.DescriptorResolverUtils +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.jet.lang.resolve.kotlin.KotlinBinaryClassCache +import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils +import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedPackageMemberScope +import org.jetbrains.jet.lang.resolve.kotlin.AnnotationDescriptorDeserializer +import org.jetbrains.jet.lang.resolve.java.resolver.ErrorReporter +import org.jetbrains.jet.lang.types.ErrorUtils.getErrorModule +import org.jetbrains.jet.lang.types.error.MissingDependencyErrorClassDescriptor +import org.jetbrains.jet.lang.resolve.java.PackageClassUtils +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.jet.lang.resolve.kotlin.KotlinClassFinder +import org.jetbrains.jet.lang.resolve.name.FqNameUnsafe + +public fun DeserializerForDecompiler(classFile: VirtualFile): DeserializerForDecompiler { + val kotlinClass = KotlinBinaryClassCache.getKotlinBinaryClass(classFile) + val classFqName = kotlinClass.getClassName().getFqNameForClassNameWithoutDollars() + val packageFqName = classFqName.parent() + return DeserializerForDecompiler(classFile.getParent()!!, packageFqName) +} + +public class DeserializerForDecompiler(val packageDirectory: VirtualFile, val directoryPackageFqName: FqName) : ResolverForDecompiler { + + override fun resolveClass(classFqName: FqName) = classes(classFqName.toClassId()) + + override fun resolveDeclarationsInPackage(packageFqName: FqName): Collection { + assert(packageFqName == directoryPackageFqName, "Was called for $packageFqName but only $directoryPackageFqName is expected.") + val packageClassFqName = PackageClassUtils.getPackageClassFqName(packageFqName) + val binaryClassForPackageClass = localClassFinder.findKotlinClass(packageClassFqName) + val annotationData = binaryClassForPackageClass?.getClassHeader()?.getAnnotationData() + if (annotationData == null) { + LOG.error("Could not read annotation data for $packageFqName from ${binaryClassForPackageClass?.getClassName()}") + return Collections.emptyList() + } + val membersScope = DeserializedPackageMemberScope( + storageManager, + createDummyPackageFragment(packageFqName), + annotationDeserializer, + descriptorFinder, + JavaProtoBufUtil.readPackageDataFrom(annotationData) + ) + return membersScope.getAllDescriptors() + } + + private val localClassFinder = object: KotlinClassFinder { + override fun findKotlinClass(fqName: FqName) = findKotlinClass(fqName.toClassId()) + + fun findKotlinClass(classId: ClassId): KotlinJvmBinaryClass? { + if (classId.getPackageFqName() != directoryPackageFqName) { + return null + } + val segments = DeserializedResolverUtils.kotlinFqNameToJavaFqName(classId.getRelativeClassName()).pathSegments() + val targetName = segments.makeString("$", postfix = ".class") + val virtualFile = packageDirectory.findChild(targetName) + if (virtualFile != null && DecompiledUtils.isKotlinCompiledFile(virtualFile)) { + return KotlinBinaryClassCache.getKotlinBinaryClass(virtualFile) + } + return null + } + } + private val storageManager = LockBasedStorageManager.NO_LOCKS + private val classes = storageManager.createMemoizedFunctionWithNullableValues { + (classId: ClassId) -> + resolveClassByClassId(classId) + } + + private val annotationDeserializer = AnnotationDescriptorDeserializer(storageManager); + { + annotationDeserializer.setClassResolver { + fqName -> + classes(fqName.toClassId()) + } + annotationDeserializer.setKotlinClassFinder(localClassFinder) + annotationDeserializer.setErrorReporter(LOGGING_REPORTER) + } + + private val descriptorFinder = object : DescriptorFinder { + override fun findClass(classId: ClassId): ClassDescriptor? { + return classes(classId) + } + + override fun getClassNames(packageName: FqName): Collection { + return Collections.emptyList() + } + } + + private val packageFragmentProvider = object : PackageFragmentProvider { + override fun getPackageFragments(fqName: FqName): List { + return listOf(createDummyPackageFragment(fqName)) + } + + override fun getSubPackagesOf(fqName: FqName): Collection { + throw UnsupportedOperationException("This method is not supposed to be called.") + } + } + + private fun createDummyPackageFragment(fqName: FqName): MutablePackageFragmentDescriptor { + return MutablePackageFragmentDescriptor(ErrorUtils.getErrorModule(), fqName) + } + + private fun resolveClassByClassId(classId: ClassId): ClassDescriptor? { + val fullFqName = classId.asSingleFqName() + if (fullFqName.isSafe()) { + val fromBuiltIns = DescriptorResolverUtils.getKotlinBuiltinClassDescriptor(fullFqName.toSafe()) + if (fromBuiltIns != null) { + return fromBuiltIns + } + } + val binaryClass = localClassFinder.findKotlinClass(classId) + if (binaryClass != null) { + return deserializeBinaryClass(binaryClass) + } + assert(fullFqName.isSafe(), "Safe fq name expected here, got $fullFqName instead") + return MissingDependencyErrorClassDescriptor(fullFqName.toSafe()) + } + + private fun deserializeBinaryClass(kotlinClass: KotlinJvmBinaryClass): ClassDescriptor { + val data = kotlinClass.getClassHeader()?.getAnnotationData() + if (data == null) { + LOG.error("Annotation data missing for ${kotlinClass.getClassName()}") + } + val classData = JavaProtoBufUtil.readClassDataFrom(data!!) + return DeserializedClassDescriptor(storageManager, annotationDeserializer, descriptorFinder, packageFragmentProvider, + classData.getNameResolver(), classData.getClassProto()) + } + + // we need a "magic" way to obtain ClassId from FqName + // the idea behind this function is that we need accurate class ids only for "neighbouring" classes (inner classes, class object, etc) + // for all others we can build any ClassId since it will resolve to MissingDependencyErrorClassDescriptor which only stores fqName + private fun FqName.toClassId(): ClassId { + val segments = pathSegments() + val packageSegmentsCount = directoryPackageFqName.pathSegments().size + if (segments.size <= packageSegmentsCount) { + return ClassId.topLevel(this) + } + val packageFqName = FqName.fromSegments(segments.subList(0, packageSegmentsCount) map { it.asString() }) + if (packageFqName == directoryPackageFqName) { + return ClassId(packageFqName, FqNameUnsafe.fromSegments(segments.subList(packageSegmentsCount, segments.size))) + } + return ClassId.topLevel(this) + } + + class object { + private val LOG = Logger.getInstance(javaClass()) + + private object LOGGING_REPORTER: ErrorReporter { + override fun reportAnnotationLoadingError(message: String, exception: Exception?) { + LOG.error(message, exception) + } + override fun reportCannotInferVisibility(descriptor: CallableMemberDescriptor) { + LOG.error("Could not infer visibility for $descriptor") + } + override fun reportIncompatibleAbiVersion(kotlinClass: KotlinJvmBinaryClass, actualVersion: Int) { + LOG.error("Incompatible ABI version for class ${kotlinClass.getClassName()}, actual version: $actualVersion") + } + } + } +}