diff --git a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/kotlin/KotlinBinaryClassCache.java b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/kotlin/KotlinBinaryClassCache.java index ad4f7b3f862..e4f69ba299b 100644 --- a/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/kotlin/KotlinBinaryClassCache.java +++ b/compiler/frontend.java/src/org/jetbrains/jet/lang/resolve/kotlin/KotlinBinaryClassCache.java @@ -18,30 +18,32 @@ package org.jetbrains.jet.lang.resolve.kotlin; import com.intellij.ide.highlighter.JavaClassFileType; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.containers.SLRUCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public final class KotlinBinaryClassCache implements Disposable { + private static class RequestCache { + VirtualFile virtualFile; + long modificationStamp; + VirtualFileKotlinClass virtualFileKotlinClass; - // This cache must be small: we only query the same file a few times in a row (from different places) - // It's local to each thread: we don't want a single instance to synchronize access on, because VirtualFileKotlinClass.create involves - // reading files from disk and may take some time - private final ThreadLocal>> cache = - new ThreadLocal>>() { + public VirtualFileKotlinClass cache(VirtualFile file, VirtualFileKotlinClass aClass) { + virtualFile = file; + virtualFileKotlinClass = aClass; + modificationStamp = file.getModificationStamp(); + + return aClass; + } + } + + private final ThreadLocal cache = + new ThreadLocal() { @Override - protected SLRUCache> initialValue() { - return new SLRUCache>(2, 2) { - @NotNull - @Override - @SuppressWarnings("deprecation") - public Ref createValue(VirtualFile virtualFile) { - return Ref.create(VirtualFileKotlinClass.OBJECT$.create(virtualFile)); - } - }; + protected RequestCache initialValue() { + return new RequestCache(); } }; @@ -50,7 +52,18 @@ public final class KotlinBinaryClassCache implements Disposable { if (file.getFileType() != JavaClassFileType.INSTANCE) return null; KotlinBinaryClassCache service = ServiceManager.getService(KotlinBinaryClassCache.class); - return service.cache.get().get(file).get(); + RequestCache requestCache = service.cache.get(); + + if (file.getModificationStamp() == requestCache.modificationStamp && file.equals(requestCache.virtualFile)) { + return requestCache.virtualFileKotlinClass; + } + else { + ApplicationManager.getApplication().assertReadAccessAllowed(); + + //noinspection deprecation + VirtualFileKotlinClass aClass = VirtualFileKotlinClass.OBJECT$.create(file); + return requestCache.cache(file, aClass); + } } @Override diff --git a/compiler/util/src/org/jetbrains/jet/utils/concurrent.kt b/compiler/util/src/org/jetbrains/jet/utils/concurrent.kt new file mode 100644 index 00000000000..dfcd14aca18 --- /dev/null +++ b/compiler/util/src/org/jetbrains/jet/utils/concurrent.kt @@ -0,0 +1,49 @@ +/* + * 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.utils.concurrent.block + +public class LockedClearableLazyValue(val lock: Any, val init: () -> T) { + private volatile var value: T? = null + + public fun get(): T { + val _v1 = value + if (_v1 != null) { + return _v1 + } + + return synchronized(lock) { + val _v2 = value + // Suppress because of https://youtrack.jetbrains.com/issue/KT-6176 + [suppress("UNNECESSARY_NOT_NULL_ASSERTION")] + if (_v2 != null) { + _v2!! + } + else { + val _v3 = init() + this.value = _v3 + _v3 + } + } + } + + fun drop() { + synchronized (lock) { + value = null + } + } +} + diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/DecompiledUtils.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/DecompiledUtils.kt index ccdffff1ff2..02fb042a82c 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/DecompiledUtils.kt +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/DecompiledUtils.kt @@ -24,26 +24,26 @@ import org.jetbrains.jet.lang.resolve.java.JvmAnnotationNames.KotlinSyntheticCla import com.intellij.ide.highlighter.JavaClassFileType public fun isKotlinCompiledFile(file: VirtualFile): Boolean { - if (isKotlinCompiledFileWithIncompatibleAbiVersion(file)) { + if (file.getExtension() != JavaClassFileType.INSTANCE!!.getDefaultExtension()) { return false } - + val header = KotlinBinaryClassCache.getKotlinBinaryClass(file)?.getClassHeader() return header != null && header.syntheticClassKind != KotlinSyntheticClass.Kind.TRAIT_IMPL } -public fun isKotlinCompiledFileWithIncompatibleAbiVersion(file: VirtualFile): Boolean { - if (file.getExtension() != JavaClassFileType.INSTANCE!!.getDefaultExtension()) { - return false - } +public fun isKotlinWithCompatibleAbiVersion(file: VirtualFile): Boolean { + if (!isKotlinCompiledFile(file)) return false + val header = KotlinBinaryClassCache.getKotlinBinaryClass(file)?.getClassHeader() - return header != null && !header.isCompatibleAbiVersion + return header != null && header.isCompatibleAbiVersion } public fun isKotlinInternalCompiledFile(file: VirtualFile): Boolean { if (!isKotlinCompiledFile(file)) { return false } + if (ClassFileViewProvider.isInnerClass(file)) { return true } diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClassFileViewProvider.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClassFileViewProvider.kt index a65641c5719..10ce0875916 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClassFileViewProvider.kt +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClassFileViewProvider.kt @@ -24,30 +24,25 @@ import com.intellij.psi.PsiManager import com.intellij.psi.SingleRootFileViewProvider import org.jetbrains.jet.plugin.JetLanguage import kotlin.properties.Delegates -import org.jetbrains.jet.plugin.decompiler.textBuilder.buildDecompiledText public class JetClassFileViewProvider( manager: PsiManager, - file: VirtualFile, + val file: VirtualFile, physical: Boolean, - val isInternal: Boolean -) : SingleRootFileViewProvider(manager, file, physical, JetLanguage.INSTANCE) { - val decompiledText by Delegates.blockingLazy(this) { - buildDecompiledText(getVirtualFile()) + val isInternal: Boolean) : SingleRootFileViewProvider(manager, file, physical, JetLanguage.INSTANCE) { + + val jetClsFile by Delegates.blockingLazy(this) { + //TODO: check index that file is library file, as in ClassFileViewProvider + if (!isInternal) JetClsFile(this) else null } override fun getContents(): CharSequence { - return if (isInternal) "" else decompiledText.text + return if (!isInternal) jetClsFile!!.getText() else "" } - override fun createFile(project: Project, file: VirtualFile, fileType: FileType): PsiFile? { - //TODO: check index that file is library file, as in ClassFileViewProvider - if (isInternal) return null - - return JetClsFile(this) - } + override fun createFile(project: Project, file: VirtualFile, fileType: FileType): PsiFile? = jetClsFile override fun createCopy(copy: VirtualFile): SingleRootFileViewProvider { return JetClassFileViewProvider(getManager(), copy, false, isInternal) } -} +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClsFile.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClsFile.kt index 214c014bfab..b7401e52006 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClsFile.kt +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetClsFile.kt @@ -22,31 +22,53 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.TestOnly import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor import org.jetbrains.jet.lang.psi.JetDeclaration -import kotlin.properties.Delegates +import com.intellij.psi.PsiElement +import org.jetbrains.jet.plugin.decompiler.textBuilder.buildDecompiledText import org.jetbrains.jet.plugin.decompiler.textBuilder.descriptorToKey +import org.jetbrains.jet.utils.concurrent.block.LockedClearableLazyValue public class JetClsFile(val provider: JetClassFileViewProvider) : ClsFileImpl(provider) { + private val mirrorLock = Any() - private val decompiledFile by Delegates.blockingLazy(this) { - JetDummyClassFileViewProvider.createJetFile(provider.getManager(), getVirtualFile(), provider.decompiledText.text) + private val mirrorFile = LockedClearableLazyValue(mirrorLock) { + JetDummyClassFileViewProvider.createJetFile( + provider.getManager(), + getVirtualFile(), + decompiledText.get().text)!! } - override fun getDecompiledPsiFile() = decompiledFile + private val decompiledText = LockedClearableLazyValue(mirrorLock) { + buildDecompiledText(getVirtualFile()) + } + + override fun getMirror(): PsiElement? = mirrorFile.get() public fun getDeclarationForDescriptor(descriptor: DeclarationDescriptor): JetDeclaration? { val key = descriptorToKey(descriptor.getOriginal()) - val range = provider.decompiledText.renderedDescriptorsToRange[key] - if (range == null) { - return null + + return synchronized(mirrorLock) { + val range = decompiledText.get().renderedDescriptorsToRange[key] + if (range != null) { + PsiTreeUtil.findElementOfClassAtRange( + mirrorFile.get(), range.getStartOffset(), range.getEndOffset(), javaClass()) + } + else { + null + } + } + } + + override fun onContentReload() { + super.onContentReload() + + synchronized (mirrorLock) { + decompiledText.drop() + mirrorFile.drop() } - return PsiTreeUtil.findElementOfClassAtRange(decompiledFile, range.getStartOffset(), range.getEndOffset(), javaClass()) } TestOnly fun getRenderedDescriptorsToRange(): Map { - return provider.decompiledText.renderedDescriptorsToRange + return decompiledText.get().renderedDescriptorsToRange } - - override fun getTextLength() = decompiledFile.getTextLength() - override fun getText() = decompiledFile.getText() } diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetDecompilerForWrongAbiVersion.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetDecompilerForWrongAbiVersion.kt deleted file mode 100644 index 6ca2526ddc5..00000000000 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/JetDecompilerForWrongAbiVersion.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.decompiler - -import com.intellij.psi.compiled.ClassFileDecompilers -import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.impl.compiled.ClsFileImpl - -public class JetDecompilerForWrongAbiVersion : ClassFileDecompilers.Light() { - override fun accepts(file: VirtualFile) = isKotlinCompiledFileWithIncompatibleAbiVersion(file) - - override fun getText(file: VirtualFile) = "$INCOMPATIBLE_ABI_VERSION_COMMENT\n${ClsFileImpl.decompile(file)}" -} - -public val INCOMPATIBLE_ABI_VERSION_COMMENT: String = " // This Kotlin file has an incompatible ABI version and is displayed as Java class" diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextFactory.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextFactory.kt index b2ecf9ecc08..3be40ea0508 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextFactory.kt +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextFactory.kt @@ -32,6 +32,19 @@ import org.jetbrains.jet.lang.types.error.MissingDependencyErrorClass import org.jetbrains.jet.lang.resolve.dataClassUtils.isComponentLike import org.jetbrains.jet.plugin.util.IdeDescriptorRenderers import org.jetbrains.jet.lang.types.isFlexible +import org.jetbrains.jet.lang.resolve.java.JvmAbi +import org.jetbrains.jet.lang.resolve.kotlin.header.isCompatiblePackageFacadeKind +import org.jetbrains.jet.lang.resolve.kotlin.header.isCompatibleClassKind + +private val FILE_ABI_VERSION_MARKER: String = "FILE_ABI" +private val CURRENT_ABI_VERSION_MARKER: String = "CURRENT_ABI" + +public val INCOMPATIBLE_ABI_VERSION_GENERAL_COMMENT: String = "// This class file was compiled with different version of Kotlin compiler and can't be decompiled." +public val INCOMPATIBLE_ABI_VERSION_COMMENT: String = + "$INCOMPATIBLE_ABI_VERSION_GENERAL_COMMENT\n" + + "//\n" + + "// Current compiler ABI version is $CURRENT_ABI_VERSION_MARKER\n" + + "// File ABI version is $FILE_ABI_VERSION_MARKER" public fun buildDecompiledText( classFile: VirtualFile, @@ -41,21 +54,22 @@ public fun buildDecompiledText( assert(kotlinClass != null) { "Decompiled data factory shouldn't be called on an unsupported file: " + classFile } val classId = kotlinClass!!.getClassId() val classHeader = kotlinClass.getClassHeader() - val kind = kotlinClass.getClassHeader().kind val packageFqName = classId.getPackageFqName() - - if (!classHeader.isCompatibleAbiVersion) { - throw UnsupportedOperationException("Illegal operation for incompatibel header") - } - return if (kind == KotlinClassHeader.Kind.PACKAGE_FACADE) { - buildDecompiledText(packageFqName, ArrayList(resolver.resolveDeclarationsInPackage(packageFqName))) - } - else if (kind == KotlinClassHeader.Kind.CLASS) { - buildDecompiledText(packageFqName, listOf(resolver.resolveTopLevelClass(classId)).filterNotNull()) - } - else { - throw UnsupportedOperationException("Unknown header kind: " + kind) + return when { + !classHeader.isCompatibleAbiVersion -> { + DecompiledText( + INCOMPATIBLE_ABI_VERSION_COMMENT + .replaceAll(CURRENT_ABI_VERSION_MARKER, JvmAbi.VERSION.toString()) + .replaceAll(FILE_ABI_VERSION_MARKER, classHeader.version.toString()), + mapOf()) + } + classHeader.isCompatiblePackageFacadeKind() -> + buildDecompiledText(packageFqName, ArrayList(resolver.resolveDeclarationsInPackage(packageFqName))) + classHeader.isCompatibleClassKind() -> + buildDecompiledText(packageFqName, listOf(resolver.resolveTopLevelClass(classId)).filterNotNull()) + else -> + throw UnsupportedOperationException("Unknown header kind: ${classHeader.kind} ${classHeader.isCompatibleAbiVersion}") } } diff --git a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DeserializerForDecompiler.kt b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DeserializerForDecompiler.kt index d3184815624..4b61977d861 100644 --- a/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DeserializerForDecompiler.kt +++ b/idea/idea-analysis/src/org/jetbrains/jet/plugin/decompiler/textBuilder/DeserializerForDecompiler.kt @@ -37,7 +37,7 @@ import org.jetbrains.jet.lang.PlatformToKotlinClassMap import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.descriptors.serialization.ClassData import org.jetbrains.jet.lang.descriptors.impl.ModuleDescriptorImpl -import org.jetbrains.jet.plugin.decompiler.isKotlinCompiledFile +import org.jetbrains.jet.plugin.decompiler.isKotlinWithCompatibleAbiVersion public fun DeserializerForDecompiler(classFile: VirtualFile): DeserializerForDecompiler { val kotlinClass = KotlinBinaryClassCache.getKotlinBinaryClass(classFile) @@ -81,7 +81,7 @@ public class DeserializerForDecompiler(val packageDirectory: VirtualFile, val di val segments = DeserializedResolverUtils.kotlinFqNameToJavaFqName(classId.getRelativeClassName()).pathSegments() val targetName = segments.joinToString("$", postfix = ".class") val virtualFile = packageDirectory.findChild(targetName) - if (virtualFile != null && isKotlinCompiledFile(virtualFile)) { + if (virtualFile != null && isKotlinWithCompatibleAbiVersion(virtualFile)) { return KotlinBinaryClassCache.getKotlinBinaryClass(virtualFile) } return null diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 3a7ec8586e5..d6630beaa94 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -397,8 +397,7 @@ - - + diff --git a/idea/tests/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextForWrongAbiVersionTest.kt b/idea/tests/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextForWrongAbiVersionTest.kt index c7fff149d4d..6786a3cfd7e 100644 --- a/idea/tests/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextForWrongAbiVersionTest.kt +++ b/idea/tests/org/jetbrains/jet/plugin/decompiler/textBuilder/DecompiledTextForWrongAbiVersionTest.kt @@ -25,10 +25,9 @@ import com.intellij.psi.PsiCompiledFile import org.jetbrains.jet.plugin.JetJdkAndLibraryProjectDescriptor import java.io.File import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiJavaFile import com.intellij.psi.impl.compiled.ClsFileImpl import org.jetbrains.jet.plugin.decompiler.navigation.NavigateToDecompiledLibraryTest -import org.jetbrains.jet.plugin.decompiler.INCOMPATIBLE_ABI_VERSION_COMMENT +import org.jetbrains.jet.lang.psi.JetFile public class DecompiledTextForWrongAbiVersionTest : JetLightCodeInsightFixtureTestCase() { @@ -50,9 +49,8 @@ public class DecompiledTextForWrongAbiVersionTest : JetLightCodeInsightFixtureTe val psiFile = PsiManager.getInstance(getProject()!!).findFile(file) Assert.assertTrue(psiFile is ClsFileImpl) val decompiledPsiFile = (psiFile as PsiCompiledFile).getDecompiledPsiFile() - Assert.assertTrue(decompiledPsiFile is PsiJavaFile) + Assert.assertTrue(decompiledPsiFile is JetFile) val decompiledText = decompiledPsiFile!!.getText()!! - Assert.assertTrue(decompiledText.contains(INCOMPATIBLE_ABI_VERSION_COMMENT)) - Assert.assertTrue((decompiledPsiFile as PsiJavaFile).getClasses().size == 1) + Assert.assertTrue(decompiledText.contains(INCOMPATIBLE_ABI_VERSION_GENERAL_COMMENT)) } } \ No newline at end of file