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 package org.jetbrains.kotlin.idea.caches.resolve
import com.intellij.ide.highlighter.JavaClassFileType
import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryUtil 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.openapi.vfs.VirtualFile
import com.intellij.psi.ClassFileViewProvider import com.intellij.psi.ClassFileViewProvider
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
@@ -29,12 +26,9 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager import com.intellij.psi.PsiManager
import com.intellij.psi.impl.compiled.ClsClassImpl import com.intellij.psi.impl.compiled.ClsClassImpl
import com.intellij.psi.impl.compiled.ClsFileImpl 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.search.GlobalSearchScope
import com.intellij.psi.stubs.PsiClassHolderFileStub import com.intellij.psi.stubs.PsiClassHolderFileStub
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.cls.ClsFormatException
import org.jetbrains.kotlin.asJava.* import org.jetbrains.kotlin.asJava.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.fileClasses.javaFileFacadeFqName 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.lazy.*
import org.jetbrains.kotlin.resolve.scopes.JetScope import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.utils.sure import org.jetbrains.kotlin.utils.sure
import java.io.IOException
import java.util.* import java.util.*
public class IDELightClassGenerationSupport(private val project: Project) : LightClassGenerationSupport() { public class IDELightClassGenerationSupport(private val project: Project) : LightClassGenerationSupport() {
@@ -166,7 +159,6 @@ public class IDELightClassGenerationSupport(private val project: Project) : Ligh
val clsClassFromPackageClass = createClsJavaClassFromVirtualFile( val clsClassFromPackageClass = createClsJavaClassFromVirtualFile(
mirrorFile = files.first(), mirrorFile = files.first(),
classFile = virtualFileForPackageClass, classFile = virtualFileForPackageClass,
cacheHolder = virtualFileForPackageClass,
correspondingClassOrObject = null correspondingClassOrObject = null
) ?: return emptyList() ) ?: return emptyList()
return listOf(KotlinLightClassForDecompiledDeclaration(clsClassFromPackageClass, null)) 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) { // make sure we create a package descriptor
for (file in files) { val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName)
// SCRIPT: not supported if (packageDescriptor.isEmpty()) {
if (file.isScript) continue LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text)
session.forceResolveAll()
continue
}
val packageFqName = file.packageFqName for (declaration in file.declarations) {
if (declaration is JetFunction) {
// make sure we create a package descriptor val name = declaration.nameAsSafeName
val packageDescriptor = session.moduleDescriptor.getPackage(packageFqName) val functions = packageDescriptor.memberScope.getFunctions(name, NoLookupLocation.FROM_IDE)
if (packageDescriptor.isEmpty()) { for (descriptor in functions) {
LOG.warn("No descriptor found for package " + packageFqName + " in file " + file.name + "\n" + file.text) ForceResolveUtil.forceResolveAllContents(descriptor)
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)
} }
} }
} else if (declaration is JetProperty) {
} val name = declaration.nameAsSafeName
val properties = packageDescriptor.memberScope.getProperties(name, NoLookupLocation.FROM_IDE)
private fun getLightClassForDecompiledClassOrObject(decompiledClassOrObject: JetClassOrObject): PsiClass? { for (descriptor in properties) {
if (decompiledClassOrObject is JetEnumEntry) { ForceResolveUtil.forceResolveAllContents(descriptor)
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
} }
return super.getNavigationElement()
} }
else if (declaration is JetClassOrObject) {
override fun getStub(): PsiClassHolderFileStub<*> { // Do nothing: we are not interested in classes
return javaFileStub
} }
else {
override fun getMirror(): PsiElement { LOG.error("Unsupported declaration kind: " + declaration + " in file " + file.name + "\n" + file.text)
return mirrorFile
} }
} }
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" <projectService serviceInterface="org.jetbrains.kotlin.idea.references.BuiltInsReferenceResolver"
serviceImplementation="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"/> <errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
<internalFileTemplate name="Kotlin File"/> <internalFileTemplate name="Kotlin File"/>