Introduce ClsJavaStubByVirtualFileCache

Avoid caching in user data of virtual file because it leads to project leaking
This commit is contained in:
Pavel V. Talanov
2015-09-14 17:17:27 +03:00
parent 708f7fc51f
commit 54bcbc7a02
3 changed files with 187 additions and 160 deletions
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2015 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.idea.caches.resolve
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.components.ServiceManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import com.intellij.util.cls.ClsFormatException
import com.intellij.util.containers.ContainerUtil
import java.io.IOException
public class ClsJavaStubByVirtualFileCache {
private class CachedJavaStub(val modificationStamp: Long, val javaFileStub: PsiJavaFileStubImpl)
private val cache = ContainerUtil.createConcurrentWeakKeySoftValueMap<VirtualFile, CachedJavaStub>()
public fun get(classFile: VirtualFile): PsiJavaFileStubImpl? {
val cached = cache.get(classFile)
val fileModificationStamp = classFile.modificationStamp
if (cached != null && cached.modificationStamp == fileModificationStamp) {
return cached.javaFileStub
}
val stub = createStub(classFile) as PsiJavaFileStubImpl? ?: return null
cache.put(classFile, CachedJavaStub(fileModificationStamp, stub))
return stub
}
private fun createStub(file: VirtualFile): PsiJavaFileStub? {
if (file.fileType !== JavaClassFileType.INSTANCE) return null
try {
return ClsFileImpl.buildFileStub(file, file.contentsToByteArray())
}
catch (e: ClsFormatException) {
LOG.debug(e)
}
catch (e: IOException) {
LOG.debug(e)
}
LOG.error("Failed to build java cls class for " + file.canonicalPath!!)
return null
}
companion object {
private val LOG = Logger.getInstance(ClsJavaStubByVirtualFileCache::class.java)
public fun getInstance(project: Project): ClsJavaStubByVirtualFileCache {
return ServiceManager.getService(project, ClsJavaStubByVirtualFileCache::class.java)
}
}
}
@@ -16,12 +16,9 @@
package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider
import com.intellij.psi.PsiClass
@@ -29,12 +26,9 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.impl.compiled.ClsClassImpl
import com.intellij.psi.impl.compiled.ClsFileImpl
import com.intellij.psi.impl.java.stubs.PsiJavaFileStub
import com.intellij.psi.impl.java.stubs.impl.PsiJavaFileStubImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.PsiClassHolderFileStub
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.cls.ClsFormatException
import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName
@@ -52,7 +46,6 @@ import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.*
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.utils.sure
import java.io.IOException
import java.util.*
public class IDELightClassGenerationSupport(private val project: Project) : LightClassGenerationSupport() {
@@ -166,7 +159,6 @@ public class IDELightClassGenerationSupport(private val project: Project) : Ligh
val clsClassFromPackageClass = createClsJavaClassFromVirtualFile(
mirrorFile = files.first(),
classFile = virtualFileForPackageClass,
cacheHolder = virtualFileForPackageClass,
correspondingClassOrObject = null
) ?: return emptyList()
return listOf(KotlinLightClassForDecompiledDeclaration(clsClassFromPackageClass, null))
@@ -245,169 +237,130 @@ public class IDELightClassGenerationSupport(private val project: Project) : Ligh
}
}
private class CachedJavaStub(public var modificationStamp: Long, public var javaFileStub: PsiJavaFileStubImpl)
companion object {
private fun forceResolvePackageDeclarations(files: Collection<JetFile>, session: KotlinCodeAnalyzer) {
for (file in files) {
// SCRIPT: not supported
if (file.isScript) continue
private val LOG = Logger.getInstance(IDELightClassGenerationSupport::class.java)
val packageFqName = file.packageFqName
private fun forceResolvePackageDeclarations(files: Collection<JetFile>, session: KotlinCodeAnalyzer) {
for (file in files) {
// SCRIPT: not supported
if (file.isScript) continue
// make sure we create a package descriptor
val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName)
if (packageDescriptor.isEmpty()) {
LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text)
session.forceResolveAll()
continue
}
val packageFqName = file.packageFqName
// make sure we create a package descriptor
val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName)
if (packageDescriptor.isEmpty()) {
LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text)
session.forceResolveAll()
continue
}
for (declaration in file.declarations) {
if (declaration is JetFunction) {
val name = declaration.nameAsSafeName
val functions = packageDescriptor.memberScope.getFunctions(name, NoLookupLocation.FROM_IDE)
for (descriptor in functions) {
ForceResolveUtil.forceResolveAllContents(descriptor)
}
}
else if (declaration is JetProperty) {
val name = declaration.nameAsSafeName
val properties = packageDescriptor.memberScope.getProperties(name, NoLookupLocation.FROM_IDE)
for (descriptor in properties) {
ForceResolveUtil.forceResolveAllContents(descriptor)
}
}
else if (declaration is JetClassOrObject) {
// Do nothing: we are not interested in classes
}
else {
LOG.error("Unsupported declaration kind: " + declaration + " in file " + file.name + "\n" + file.text)
for (declaration in file.declarations) {
if (declaration is JetFunction) {
val name = declaration.nameAsSafeName
val functions = packageDescriptor.memberScope.getFunctions(name, NoLookupLocation.FROM_IDE)
for (descriptor in functions) {
ForceResolveUtil.forceResolveAllContents(descriptor)
}
}
}
}
private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: JetClassOrObject): PsiClass? {
if (decompiledClassOrObject is JetEnumEntry) {
return null
}
val containingJetFile = decompiledClassOrObject.getContainingJetFile()
if (!containingJetFile.isCompiled) {
return null
}
val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingJetFile) ?: return null
return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile)
}
private fun findCorrespondingLightClass(
decompiledClassOrObject: JetClassOrObject,
rootLightClassForDecompiledFile: PsiClass): PsiClass {
val relativeFqName = getClassRelativeName(decompiledClassOrObject)
val iterator = relativeFqName.pathSegments().iterator()
val base = iterator.next()
assert(rootLightClassForDecompiledFile.name == base.asString()) { "Light class for file:\n" + decompiledClassOrObject.getContainingJetFile().virtualFile.canonicalPath + "\nwas expected to have name: " + base.asString() + "\n Actual: " + rootLightClassForDecompiledFile.name }
var current = rootLightClassForDecompiledFile
while (iterator.hasNext()) {
val name = iterator.next()
val innerClass = current.findInnerClassByName(name.asString(), false).sure {
"Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\n" + "File: " + decompiledClassOrObject.getContainingJetFile().virtualFile.name
}
current = innerClass
}
return current
}
private fun getClassRelativeName(decompiledClassOrObject: JetClassOrObject): FqName {
val name = decompiledClassOrObject.nameAsName!!
val parent = PsiTreeUtil.getParentOfType(decompiledClassOrObject, JetClassOrObject::class.java, true)
if (parent == null) {
assert(decompiledClassOrObject.isTopLevel())
return FqName.topLevel(name)
}
return getClassRelativeName(parent).child(name)
}
private fun createLightClassForDecompiledKotlinFile(file: JetFile): KotlinLightClassForDecompiledDeclaration? {
val virtualFile = file.virtualFile ?: return null
val classOrObject = file.declarations.filterIsInstance<JetClassOrObject>().singleOrNull()
val javaClsClass = createClsJavaClassFromVirtualFile(
file, virtualFile,
cacheHolder = file,
correspondingClassOrObject = classOrObject
) ?: return null
return KotlinLightClassForDecompiledDeclaration(javaClsClass, classOrObject)
}
private fun createClsJavaClassFromVirtualFile(
mirrorFile: JetFile,
classFile: VirtualFile,
cacheHolder: UserDataHolder,
correspondingClassOrObject: JetClassOrObject?
): ClsClassImpl? {
val javaFileStub = getOrCreateJavaFileStub(cacheHolder, classFile) ?: return null
val manager = PsiManager.getInstance(mirrorFile.project)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) {
override fun getNavigationElement(): PsiElement {
if (correspondingClassOrObject != null) {
return correspondingClassOrObject.navigationElement.containingFile
else if (declaration is JetProperty) {
val name = declaration.nameAsSafeName
val properties = packageDescriptor.memberScope.getProperties(name, NoLookupLocation.FROM_IDE)
for (descriptor in properties) {
ForceResolveUtil.forceResolveAllContents(descriptor)
}
return super.getNavigationElement()
}
override fun getStub(): PsiClassHolderFileStub<*> {
return javaFileStub
else if (declaration is JetClassOrObject) {
// Do nothing: we are not interested in classes
}
override fun getMirror(): PsiElement {
return mirrorFile
else {
LOG.error("Unsupported declaration kind: " + declaration + " in file " + file.name + "\n" + file.text)
}
}
fakeFile.isPhysical = false
javaFileStub.setPsi(fakeFile)
return fakeFile.classes.single() as ClsClassImpl
}
private val cachedJavaStubKey = Key.create<CachedJavaStub>("CACHED_JAVA_STUB")
private fun getOrCreateJavaFileStub(
userDataHolder: UserDataHolder,
virtualFile: VirtualFile
): PsiJavaFileStubImpl? {
val cachedJavaStub = userDataHolder.getUserData(cachedJavaStubKey)
val fileModificationStamp = virtualFile.modificationStamp
if (cachedJavaStub != null && cachedJavaStub.modificationStamp == fileModificationStamp) {
return cachedJavaStub.javaFileStub
}
val stub = createStub(virtualFile) as PsiJavaFileStubImpl?
if (stub != null) {
userDataHolder.putUserData(cachedJavaStubKey, CachedJavaStub(fileModificationStamp, stub))
}
return stub
}
private fun createStub(file: VirtualFile): PsiJavaFileStub? {
if (file.fileType !== JavaClassFileType.INSTANCE) return null
try {
return ClsFileImpl.buildFileStub(file, file.contentsToByteArray())
}
catch (e: ClsFormatException) {
LOG.debug(e)
}
catch (e: IOException) {
LOG.debug(e)
}
LOG.error("Failed to build java cls class for " + file.canonicalPath!!)
return null
}
}
private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: JetClassOrObject): PsiClass? {
if (decompiledClassOrObject is JetEnumEntry) {
return null
}
val containingJetFile = decompiledClassOrObject.getContainingJetFile()
if (!containingJetFile.isCompiled) {
return null
}
val rootLightClassForDecompiledFile = createLightClassForDecompiledKotlinFile(containingJetFile) ?: return null
return findCorrespondingLightClass(decompiledClassOrObject, rootLightClassForDecompiledFile)
}
private fun findCorrespondingLightClass(
decompiledClassOrObject: JetClassOrObject,
rootLightClassForDecompiledFile: PsiClass): PsiClass {
val relativeFqName = getClassRelativeName(decompiledClassOrObject)
val iterator = relativeFqName.pathSegments().iterator()
val base = iterator.next()
assert(rootLightClassForDecompiledFile.name == base.asString()) { "Light class for file:\n" + decompiledClassOrObject.getContainingJetFile().virtualFile.canonicalPath + "\nwas expected to have name: " + base.asString() + "\n Actual: " + rootLightClassForDecompiledFile.name }
var current = rootLightClassForDecompiledFile
while (iterator.hasNext()) {
val name = iterator.next()
val innerClass = current.findInnerClassByName(name.asString(), false).sure {
"Could not find corresponding inner/nested class " + relativeFqName + " in class " + decompiledClassOrObject.fqName + "\n" + "File: " + decompiledClassOrObject.getContainingJetFile().virtualFile.name
}
current = innerClass
}
return current
}
private fun getClassRelativeName(decompiledClassOrObject: JetClassOrObject): FqName {
val name = decompiledClassOrObject.nameAsName!!
val parent = PsiTreeUtil.getParentOfType(decompiledClassOrObject, JetClassOrObject::class.java, true)
if (parent == null) {
assert(decompiledClassOrObject.isTopLevel())
return FqName.topLevel(name)
}
return getClassRelativeName(parent).child(name)
}
private fun createLightClassForDecompiledKotlinFile(file: JetFile): KotlinLightClassForDecompiledDeclaration? {
val virtualFile = file.virtualFile ?: return null
val classOrObject = file.declarations.filterIsInstance<JetClassOrObject>().singleOrNull()
val javaClsClass = createClsJavaClassFromVirtualFile(
file, virtualFile,
correspondingClassOrObject = classOrObject
) ?: return null
return KotlinLightClassForDecompiledDeclaration(javaClsClass, classOrObject)
}
private fun createClsJavaClassFromVirtualFile(
mirrorFile: JetFile,
classFile: VirtualFile,
correspondingClassOrObject: JetClassOrObject?
): ClsClassImpl? {
val javaFileStub = ClsJavaStubByVirtualFileCache.getInstance(project).get(classFile) ?: return null
val manager = PsiManager.getInstance(mirrorFile.project)
val fakeFile = object : ClsFileImpl(ClassFileViewProvider(manager, classFile)) {
override fun getNavigationElement(): PsiElement {
if (correspondingClassOrObject != null) {
return correspondingClassOrObject.navigationElement.containingFile
}
return super.getNavigationElement()
}
override fun getStub(): PsiClassHolderFileStub<*> {
return javaFileStub
}
override fun getMirror(): PsiElement {
return mirrorFile
}
}
fakeFile.isPhysical = false
javaFileStub.psi = fakeFile
return fakeFile.classes.single() as ClsClassImpl
}
companion object {
private val LOG = Logger.getInstance(IDELightClassGenerationSupport::class.java)
}
}
+3
View File
@@ -235,6 +235,9 @@
<projectService serviceInterface="org.jetbrains.kotlin.idea.references.BuiltInsReferenceResolver"
serviceImplementation="org.jetbrains.kotlin.idea.references.BuiltInsReferenceResolver"/>
<projectService serviceInterface="org.jetbrains.kotlin.idea.caches.resolve.ClsJavaStubByVirtualFileCache"
serviceImplementation="org.jetbrains.kotlin.idea.caches.resolve.ClsJavaStubByVirtualFileCache"/>
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
<internalFileTemplate name="Kotlin File"/>