Introduce ClsJavaStubByVirtualFileCache
Avoid caching in user data of virtual file because it leads to project leaking
This commit is contained in:
+71
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+113
-160
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user