Speed up finding kotlin binaries and java files in CLI

Change VirtualFileFinder api to accept ClassId and adjust usages
Introduce KotlinCoreProjectEnvironment to create special KotlinCliJavaFileManager which shares cache with CliVirtualFileFinder
Truly distinguish between java source roots and binary roots: don't search for source files in classpath and vica versa
Implement JvmDependenciesIndex
This commit is contained in:
Pavel V. Talanov
2015-03-17 15:46:04 +03:00
parent 94cc847c48
commit d76f293dc9
20 changed files with 755 additions and 198 deletions
@@ -44,6 +44,7 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage;
import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.serialization.ProtoBuf;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor;
import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
@@ -59,6 +60,7 @@ import java.io.StringWriter;
import java.util.Arrays;
import java.util.ListIterator;
import static kotlin.KotlinPackage.substringAfterLast;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait;
@@ -150,7 +152,7 @@ public class InlineCodegenUtil {
@NotNull
public static VirtualFile getVirtualFileForCallable(@NotNull ClassId containerClassId, @NotNull GenerationState state) {
VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(state.getProject());
VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId.asSingleFqName());
VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId);
if (file == null) {
throw new IllegalStateException("Couldn't find declaration file for " + containerClassId);
}
@@ -177,9 +179,13 @@ public class InlineCodegenUtil {
}
@Nullable
public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) {
public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalClassName) {
FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
String classNameWithDollars = substringAfterLast(internalClassName, "/", internalClassName);
VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project);
return fileFinder.findVirtualFileWithHeader(new FqName(internalName.replace('/', '.')));
//TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
// we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE
return fileFinder.findVirtualFileWithHeader(new ClassId(packageFqName, Name.identifier(classNameWithDollars)));
}
//TODO: navigate to inner classes
@@ -25,11 +25,14 @@ import org.jetbrains.kotlin.codegen.StackValue;
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
import org.jetbrains.kotlin.load.java.JvmAnnotationNames.KotlinSyntheticClass;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache;
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass;
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.MethodVisitor;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import org.jetbrains.org.objectweb.asm.commons.Method;
import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter;
@@ -1,79 +0,0 @@
/*
* 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.cli.jvm.compiler;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder;
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClassFinder;
import org.jetbrains.kotlin.name.FqName;
public class CliVirtualFileFinder extends VirtualFileKotlinClassFinder implements VirtualFileFinder {
@NotNull
private final ClassPath classPath;
public CliVirtualFileFinder(@NotNull ClassPath path) {
classPath = path;
}
@Nullable
@Override
public VirtualFile findVirtualFileWithHeader(@NotNull FqName className) {
for (VirtualFile root : classPath) {
VirtualFile fileInRoot = findFileInRoot(className.asString(), root, '.');
//NOTE: currently we use VirtualFileFinder to find Kotlin binaries only
if (fileInRoot != null && KotlinBinaryClassCache.getKotlinBinaryClass(fileInRoot) != null) {
return fileInRoot;
}
}
return null;
}
//NOTE: copied with some changes from CoreJavaFileManager
@Nullable
private static VirtualFile findFileInRoot(@NotNull String qName, @NotNull VirtualFile root, char separator) {
String pathRest = qName;
VirtualFile cur = root;
while (true) {
int dot = pathRest.indexOf(separator);
if (dot < 0) break;
String pathComponent = pathRest.substring(0, dot);
VirtualFile child = cur.findChild(pathComponent);
if (child == null) break;
pathRest = pathRest.substring(dot + 1);
cur = child;
}
String className = pathRest.replace('.', '$');
VirtualFile vFile = cur.findChild(className + ".class");
if (vFile != null) {
if (!vFile.isValid()) {
//TODO: log
return null;
}
return vFile;
}
return null;
}
}
@@ -0,0 +1,34 @@
/*
* 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.cli.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClassFinder
import org.jetbrains.kotlin.name.ClassId
public class CliVirtualFileFinder(private val index: JvmDependenciesIndex) : VirtualFileKotlinClassFinder(), VirtualFileFinder {
override fun findVirtualFileWithHeader(classId: ClassId): VirtualFile? {
val classFileName = classId.getRelativeClassName().asString().replace('.', '$')
return index.findClass(classId, acceptedRootTypes = JavaRoot.OnlyBinary) { dir, _ ->
dir.findChild("$classFileName.class")?.let {
if (it.isValid()) it else null
}
}
}
}
@@ -14,23 +14,14 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler;
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory;
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
public final class CliVirtualFileFinderFactory implements VirtualFileFinderFactory {
private final ClassPath classPath;
public CliVirtualFileFinderFactory(@NotNull ClassPath classpath) {
this.classPath = classpath;
}
@NotNull
@Override
public VirtualFileFinder create(@NotNull GlobalSearchScope scope) {
return new CliVirtualFileFinder(classPath);
public class CliVirtualFileFinderFactory(private val index: JvmDependenciesIndex) : VirtualFileFinderFactory {
override fun create(scope: GlobalSearchScope): VirtualFileFinder {
return CliVirtualFileFinder(index)
}
}
@@ -0,0 +1,251 @@
/*
* 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.cli.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.IntArrayList
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import java.util.ArrayList
import java.util.EnumSet
import java.util.HashMap
import kotlin.properties.Delegates
public data class JavaRoot(public val file: VirtualFile, public val type: JavaRoot.RootType) {
public enum class RootType {
SOURCE
BINARY
}
companion object RootTypes {
public val OnlyBinary: Set<RootType> = EnumSet.of(RootType.BINARY)
public val SourceAndBinary: Set<RootType> = EnumSet.of(RootType.BINARY, RootType.SOURCE)
}
}
// speeds up finding files/classes in classpath/java source roots
// NOT THREADSAFE, needs to be adapted/removed if we want compiler to be multithreaded
// the main idea of this class is for each package to store roots which contains it to avoid excessive file system traversal
public class JvmDependenciesIndex(_roots: List<JavaRoot>) {
//these fields are computed based on _roots passed to constructor which are filled in later
private val roots: List<JavaRoot> by Delegates.lazy { _roots.toList() }
private val maxIndex: Int
get() = roots.size()
// each "Cache" object corresponds to a package
private class Cache {
private val innerPackageCaches = HashMap<String, Cache>()
fun get(name: String) = innerPackageCaches.getOrPut(name) { Cache() }
// indices of roots that are known to contain this package
// if this list contains [1, 3, 5] then roots with indices 1, 3 and 5 are known to contain this package, 2 and 4 are known not to (no information about roots 6 or higher)
// if this list contains maxIndex that means that all roots containing this package are known
val rootIndices = IntArrayList()
}
// root "Cache" object corresponds to DefaultPackage which exists in every root
private val rootCache: Cache by Delegates.lazy {
with(Cache()) {
roots.indices.forEach {
rootIndices.add(it)
}
rootIndices.add(maxIndex)
rootIndices.trimToSize()
this
}
}
// holds the request and the result last time we searched for class
// helps improve several scenarios, LazyJavaResolverContext.findClassInJava being the most important
private var lastClassSearch: Pair<FindClassRequest, SearchResult>? = null
// findClassGivenDirectory MUST check whether the class with this classId exists in given package
public fun <T : Any> findClass(
classId: ClassId,
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
findClassGivenDirectory: (VirtualFile, JavaRoot.RootType) -> T?
): T? {
return search(FindClassRequest(classId, acceptedRootTypes)) { dir, rootType ->
val found = findClassGivenDirectory(dir, rootType)
HandleResult(found, continueSearch = found == null)
}
}
public fun traverseDirectoriesInPackage(
packageFqName: FqName,
acceptedRootTypes: Set<JavaRoot.RootType> = JavaRoot.SourceAndBinary,
continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean
) {
search(TraverseRequest(packageFqName, acceptedRootTypes)) { dir, rootType ->
HandleResult(Unit, continueSearch(dir, rootType))
}
}
private data class HandleResult<T : Any>(val result: T?, val continueSearch: Boolean)
private fun <T : Any> search(
request: SearchRequest,
handler: (VirtualFile, JavaRoot.RootType) -> HandleResult<T>
): T? {
// default to searching with given parameters
fun doSearch() = doSearch(request, handler)
// make a decision based on information saved from last class search
if (request !is FindClassRequest || lastClassSearch == null) {
return doSearch()
}
val (cachedRequest, cachedResult) = lastClassSearch!!
if (cachedRequest.classId != request.classId) {
return doSearch()
}
when (cachedResult) {
is SearchResult.NotFound -> {
val limitedRootTypes = request.acceptedRootTypes.toHashSet()
limitedRootTypes.removeAll(cachedRequest.acceptedRootTypes)
if (limitedRootTypes.isEmpty()) {
return null
}
else {
return doSearch(FindClassRequest(request.classId, limitedRootTypes), handler)
}
}
is SearchResult.Found -> {
if (cachedRequest.acceptedRootTypes == request.acceptedRootTypes) {
return handler(cachedResult.packageDirectory, cachedResult.root.type).result
}
}
}
return doSearch()
}
private fun <T : Any> doSearch(request: SearchRequest, handler: (VirtualFile, JavaRoot.RootType) -> HandleResult<T>): T? {
val findClassRequest = request as? FindClassRequest
fun <T : Any> found(packageDirectory: VirtualFile, root: JavaRoot, result: T): T {
if (findClassRequest != null) {
lastClassSearch = Pair(findClassRequest, SearchResult.Found(packageDirectory, root))
}
return result
}
fun <T : Any> notFound(): T? {
if (findClassRequest != null) {
lastClassSearch = Pair(findClassRequest, SearchResult.NotFound)
}
return null
}
fun handle(root: JavaRoot, targetDirInRoot: VirtualFile): T? {
if (root.type in request.acceptedRootTypes) {
val (result, shouldContinue) = handler(targetDirInRoot, root.type)
if (!shouldContinue) {
return result
}
}
return null
}
// a list of package sub names, ["org", "jb", "kotlin"]
val packagesPath = request.packageFqName.pathSegments().map { it.getIdentifier() }
// a list of caches corresponding to packages, [default, "org", "org.jb", "org.jb.kotlin"]
val caches = cachesPath(packagesPath)
var processedRootsUpTo = -1
// traverse caches starting from last, which contains most specific information
for (cacheIndex in caches.indices.reversed()) {
val cache = caches[cacheIndex]
for (i in cache.rootIndices.size().indices) {
val rootIndex = cache.rootIndices[i]
if (rootIndex <= processedRootsUpTo) continue // roots with those indices have been processed by now
val directoryInRoot = travelPath(rootIndex, packagesPath, cacheIndex, caches) ?: continue
val root = roots[rootIndex]
val result = handle(root, directoryInRoot)
if (result != null) {
return found(directoryInRoot, root, result)
}
}
processedRootsUpTo = cache.rootIndices.lastOrNull() ?: processedRootsUpTo
}
return notFound()
}
// try to find a target directory corresponding to package represented by packagesPath in a given root reprenting by index
// possibly filling "Cache" objects with new information
private fun travelPath(rootIndex: Int, packagesPath: List<String>, fillCachesAfter: Int, cachesPath: List<Cache>): VirtualFile? {
if (rootIndex >= maxIndex) {
for (i in (fillCachesAfter + 1)..cachesPath.size() - 1) {
// we all know roots that contain this package by now
cachesPath[i].rootIndices.add(maxIndex)
cachesPath[i].rootIndices.trimToSize()
}
return null
}
var currentFile = roots[rootIndex].file
for (pathIndex in packagesPath.indices) {
val subPackageName = packagesPath[pathIndex]
currentFile = currentFile.findChild(subPackageName) ?: return null
val correspondingCacheIndex = pathIndex + 1
if (correspondingCacheIndex > fillCachesAfter) {
// subPackageName exists in this root
cachesPath[correspondingCacheIndex].rootIndices.add(rootIndex)
}
}
return currentFile
}
private fun cachesPath(path: List<String>): List<Cache> {
val caches = ArrayList<Cache>()
caches.add(rootCache)
var currentCache = rootCache
for (subPackageName in path) {
currentCache = currentCache[subPackageName]
caches.add(currentCache)
}
return caches
}
private data class FindClassRequest(val classId: ClassId, override val acceptedRootTypes: Set<JavaRoot.RootType>) : SearchRequest {
override val packageFqName: FqName
get() = classId.getPackageFqName()
}
private data class TraverseRequest(
override val packageFqName: FqName,
override val acceptedRootTypes: Set<JavaRoot.RootType>
) : SearchRequest
private trait SearchRequest {
val packageFqName: FqName
val acceptedRootTypes: Set<JavaRoot.RootType>
}
private trait SearchResult {
class Found(val packageDirectory: VirtualFile, val root: JavaRoot) : SearchResult
object NotFound : SearchResult
}
}
private fun IntArrayList.lastOrNull() = if (isEmpty()) null else get(size() - 1)
@@ -0,0 +1,160 @@
/*
* 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.cli.jvm.compiler
import com.intellij.core.CoreJavaFileManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiClassOwner
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiPackage
import com.intellij.psi.impl.file.PsiPackageImpl
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager
import java.util.ArrayList
import kotlin.properties.Delegates
public class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager)
: CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager {
private var index: JvmDependenciesIndex by Delegates.notNull()
public fun initIndex(packagesCache: JvmDependenciesIndex) {
this.index = packagesCache
}
public override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? {
val classNameWithInnerClasses = classId.getRelativeClassName().asString()
return index.findClass(classId) { dir, type ->
findClassGivenPackage(searchScope, dir, classNameWithInnerClasses, type)
}
}
override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? {
// this method is called from IDEA to resolve dependencies in Java code
// which supposedly shouldn't have errors so the dependencies exist in general
// Most classes are top level classes so we will try to find them fast
// but we must sometimes fallback to support finding inner/nested classes
return qName.toSafeTopLevelClassId()?.let { classId -> findClass(classId, scope) }
?: super<CoreJavaFileManager>.findClass(qName, scope)
}
override fun findClasses(qName: String, scope: GlobalSearchScope): Array<PsiClass> {
val classIdAsTopLevelClass = qName.toSafeTopLevelClassId() ?: return super<CoreJavaFileManager>.findClasses(qName, scope)
val result = ArrayList<PsiClass>()
val classNameWithInnerClasses = classIdAsTopLevelClass.getRelativeClassName().asString()
index.traverseDirectoriesInPackage(classIdAsTopLevelClass.getPackageFqName()) { dir, rootType ->
val psiClass = findClassGivenPackage(scope, dir, classNameWithInnerClasses, rootType)
if (psiClass != null) {
result.add(psiClass)
}
// traverse all
true
}
if (result.isEmpty()) {
return super<CoreJavaFileManager>.findClasses(qName, scope)
}
return result.toArray<PsiClass>(arrayOfNulls<PsiClass>(result.size()))
}
override fun findPackage(packageName: String): PsiPackage? {
var found = false
index.traverseDirectoriesInPackage(FqName(packageName)) { _, __ ->
found = true
//abort on first found
false
}
if (found) {
return PsiPackageImpl(myPsiManager, packageName)
}
return null
}
private fun findClassGivenPackage(
scope: GlobalSearchScope, packageDir: VirtualFile,
classNameWithInnerClasses: String, rootType: JavaRoot.RootType
): PsiClass? {
val topLevelClassName = classNameWithInnerClasses.substringBefore('.')
val vFile = when (rootType) {
JavaRoot.RootType.BINARY -> packageDir.findChild("$topLevelClassName.class")
JavaRoot.RootType.SOURCE -> packageDir.findChild("$topLevelClassName.java")
} ?: return null
if (!vFile.isValid()) {
LOG.error("Invalid child of valid parent: ${vFile.getPath()}; ${packageDir.isValid()} path=${packageDir.getPath()}")
return null
}
if (vFile !in scope) {
return null
}
val file = myPsiManager.findFile(vFile) as? PsiClassOwner ?: return null
return findClassInPsiFile(classNameWithInnerClasses, file)
}
companion object {
private val LOG = Logger.getInstance(javaClass<KotlinCliJavaFileManagerImpl>())
private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? {
for (topLevelClass in file.getClasses()) {
val candidate = findClassByTopLevelClass(classNameWithInnerClassesDotSeparated, topLevelClass)
if (candidate != null) {
return candidate
}
}
return null
}
private fun findClassByTopLevelClass(className: String, topLevelClass: PsiClass): PsiClass? {
if (className.indexOf('.') < 0) {
return if (className == topLevelClass.getName()) topLevelClass else null
}
val segments = StringUtil.split(className, ".").iterator()
if (!segments.hasNext() || segments.next() != topLevelClass.getName()) {
return null
}
var curClass = topLevelClass
while (segments.hasNext()) {
val innerClassName = segments.next()
val innerClass = curClass.findInnerClassByName(innerClassName, false)
if (innerClass == null) {
return null
}
curClass = innerClass
}
return curClass
}
}
}
// a sad workaround to avoid throwing exception when called from inside IDEA code
private fun String.toSafeTopLevelClassId(): ClassId? = try {
ClassId.topLevel(FqName(this))
}
catch (e: IllegalArgumentException) {
null
}
catch (e: AssertionError) {
null
}
@@ -59,12 +59,14 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.cli.jvm.config.JVMConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.config.JavaSourceRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRoot
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.ContentRoot
import org.jetbrains.kotlin.config.KotlinSourceRoot
import org.jetbrains.kotlin.extensions.ExternalDeclarationsProvider
import org.jetbrains.kotlin.idea.JetFileType
@@ -89,13 +91,13 @@ public class KotlinCoreEnvironment private(
configuration: CompilerConfiguration
) {
private val projectEnvironment: JavaCoreProjectEnvironment = object : JavaCoreProjectEnvironment(parentDisposable, applicationEnvironment) {
private val projectEnvironment: JavaCoreProjectEnvironment = object : KotlinCoreProjectEnvironment(parentDisposable, applicationEnvironment) {
override fun preregisterServices() {
registerProjectExtensionPoints(Extensions.getArea(getProject()))
}
}
private val sourceFiles = ArrayList<JetFile>()
private val classPath = ClassPath()
private val javaRoots = ArrayList<JavaRoot>()
private val annotationsManager: CoreExternalAnnotationsManager
@@ -114,6 +116,9 @@ public class KotlinCoreEnvironment private(
registerProjectServices(projectEnvironment)
fillClasspath(configuration)
val fileManager = ServiceManager.getService(project, javaClass<CoreJavaFileManager>())
val index = JvmDependenciesIndex(javaRoots)
(fileManager as KotlinCliJavaFileManagerImpl).initIndex(index)
for (path in configuration.getList(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY)) {
addExternalAnnotationsRoot(path)
@@ -130,7 +135,7 @@ public class KotlinCoreEnvironment private(
JetScriptDefinitionProvider.getInstance(project).addScriptDefinitions(configuration.getList(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY))
project.registerService(javaClass<VirtualFileFinderFactory>(), CliVirtualFileFinderFactory(classPath))
project.registerService(javaClass<VirtualFileFinderFactory>(), CliVirtualFileFinderFactory(index))
ExternalDeclarationsProvider.registerExtensionPoint(project)
ExpressionCodegenExtension.registerExtensionPoint(project)
@@ -163,7 +168,12 @@ public class KotlinCoreEnvironment private(
val virtualFile = contentRootToVirtualFile(javaRoot) ?: continue
projectEnvironment.addSourcesToClasspath(virtualFile)
classPath.add(virtualFile)
val rootType = when (javaRoot) {
is JavaSourceRoot -> JavaRoot.RootType.SOURCE
is JvmClasspathRoot -> JavaRoot.RootType.BINARY
else -> throw IllegalStateException()
}
javaRoots.add(JavaRoot(virtualFile, rootType))
}
}
@@ -339,7 +349,6 @@ public class KotlinCoreEnvironment private(
platformStatic public fun registerApplicationServices(applicationEnvironment: JavaCoreApplicationEnvironment) {
with(applicationEnvironment) {
registerFileType(JetFileType.INSTANCE, "kt")
registerFileType(JetFileType.INSTANCE, "ktm")
registerFileType(JetFileType.INSTANCE, JetParserDefinition.STD_SCRIPT_SUFFIX)
registerParserDefinition(JetParserDefinition())
getApplication().registerService(javaClass<KotlinBinaryClassCache>(), KotlinBinaryClassCache())
@@ -14,27 +14,16 @@
* limitations under the License.
*/
package org.jetbrains.kotlin.cli.jvm.compiler;
package org.jetbrains.kotlin.cli.jvm.compiler
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import com.intellij.core.JavaCoreApplicationEnvironment
import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.openapi.Disposable
import com.intellij.psi.PsiManager
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public final class ClassPath implements Iterable<VirtualFile> {
@NotNull
private final List<VirtualFile> roots = new ArrayList<VirtualFile>();
@NotNull
@Override
public Iterator<VirtualFile> iterator() {
return roots.iterator();
}
public void add(@NotNull VirtualFile root) {
roots.add(root);
}
}
open class KotlinCoreProjectEnvironment(
disposable: Disposable,
applicationEnvironment: JavaCoreApplicationEnvironment
) : JavaCoreProjectEnvironment(disposable, applicationEnvironment) {
override fun createCoreFileManager() = KotlinCliJavaFileManagerImpl(PsiManager.getInstance(getProject()))
}
@@ -86,13 +86,11 @@ public class JavaClassFinderImpl implements JavaClassFinder {
@Nullable
@Override
public JavaClass findClass(@NotNull ClassId classId) {
FqName fqName = classId.asSingleFqName();
PsiClass psiClass = javaFacade.findClass(fqName.asString(), javaSearchScope);
PsiClass psiClass = javaFacade.findClass(classId, javaSearchScope);
if (psiClass == null) return null;
JavaClassImpl javaClass = new JavaClassImpl(psiClass);
FqName fqName = classId.asSingleFqName();
if (!fqName.equals(javaClass.getFqName())) {
throw new IllegalStateException("Requested " + fqName + ", got " + javaClass.getFqName());
}
@@ -22,7 +22,7 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.ClassId;
public interface VirtualFileFinder extends KotlinClassFinder {
class SERVICE {
@@ -33,5 +33,5 @@ public interface VirtualFileFinder extends KotlinClassFinder {
}
@Nullable
VirtualFile findVirtualFileWithHeader(@NotNull FqName className);
VirtualFile findVirtualFileWithHeader(@NotNull ClassId className);
}
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.utils.sure
public abstract class VirtualFileKotlinClassFinder : VirtualFileFinder {
override fun findKotlinClass(classId: ClassId): KotlinJvmBinaryClass? {
val file = findVirtualFileWithHeader(classId.asSingleFqName()) ?: return null
val file = findVirtualFileWithHeader(classId) ?: return null
return KotlinBinaryClassCache.getKotlinBinaryClass(file)
}
@@ -0,0 +1,26 @@
/*
* 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.resolve.jvm
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.file.impl.JavaFileManager
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.name.ClassId
public trait KotlinCliJavaFileManager : JavaFileManager {
public fun findClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass?
}
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.resolve.jvm;
import com.intellij.core.CoreJavaFileManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.DumbAware;
@@ -44,6 +43,7 @@ import com.intellij.util.messages.MessageBus;
import kotlin.Function1;
import kotlin.KotlinPackage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.ClassId;
import java.util.ArrayList;
import java.util.Arrays;
@@ -87,9 +87,11 @@ public class KotlinJavaPsiFacade {
});
}
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) {
ProgressIndicatorProvider.checkCanceled(); // We hope this method is being called often enough to cancel daemon processes smoothly
String qualifiedName = classId.asSingleFqName().asString();
if (shouldUseSlowResolve()) {
PsiClass[] classes = findClassesInDumbMode(qualifiedName, scope);
if (classes.length != 0) {
@@ -99,8 +101,14 @@ public class KotlinJavaPsiFacade {
}
for (KotlinPsiElementFinderWrapper finder : finders()) {
PsiClass aClass = finder.findClass(qualifiedName, scope);
if (aClass != null) return aClass;
if (finder instanceof KotlinPsiElementFinderImpl) {
PsiClass aClass = ((KotlinPsiElementFinderImpl) finder).findClass(classId, scope);
if (aClass != null) return aClass;
}
else {
PsiClass aClass = finder.findClass(qualifiedName, scope);
if (aClass != null) return aClass;
}
}
return null;
@@ -286,14 +294,14 @@ public class KotlinJavaPsiFacade {
static class KotlinPsiElementFinderImpl implements KotlinPsiElementFinderWrapper, DumbAware {
private final JavaFileManager javaFileManager;
private final boolean isCoreJavaFileManager;
private final boolean isCliFileManager;
private final PsiManager psiManager;
private final PackageIndex packageIndex;
public KotlinPsiElementFinderImpl(Project project) {
this.javaFileManager = findJavaFileManager(project);
this.isCoreJavaFileManager = javaFileManager instanceof CoreJavaFileManager;
this.isCliFileManager = javaFileManager instanceof KotlinCliJavaFileManager;
this.packageIndex = PackageIndex.getInstance(project);
this.psiManager = PsiManager.getInstance(project);
@@ -312,20 +320,19 @@ public class KotlinJavaPsiFacade {
@Override
public PsiClass findClass(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
PsiClass aClass = javaFileManager.findClass(qualifiedName, scope);
if (aClass != null) {
//TODO: (module refactoring) CoreJavaFileManager should check scope
if (!isCoreJavaFileManager || scope.contains(aClass.getContainingFile().getOriginalFile().getVirtualFile())) {
return aClass;
}
}
return javaFileManager.findClass(qualifiedName, scope);
}
return null;
public PsiClass findClass(@NotNull ClassId classId, @NotNull GlobalSearchScope scope) {
if (isCliFileManager) {
return ((KotlinCliJavaFileManager) javaFileManager).findClass(classId, scope);
}
return findClass(classId.asSingleFqName().asString(), scope);
}
@Override
public PsiPackage findPackage(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) {
if (isCoreJavaFileManager) {
if (isCliFileManager) {
return javaFileManager.findPackage(qualifiedName);
}
@@ -0,0 +1,176 @@
/*
* Copyright 2000-2013 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.cli.jvm
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.PlatformTestCase
import com.intellij.testFramework.PsiTestCase
import com.intellij.testFramework.PsiTestUtil
import junit.framework.TestCase
import org.intellij.lang.annotations.Language
import org.jetbrains.kotlin.cli.jvm.compiler.JavaRoot
import org.jetbrains.kotlin.cli.jvm.compiler.JvmDependenciesIndex
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCliJavaFileManagerImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
//Partial copy of CoreJavaFileManagerTest
public class KotlinCliJavaFileManagerTest : PsiTestCase() {
public fun testCommon() {
val manager = configureManager("package foo;\n\n" + "public class TopLevel {\n" + "public class Inner {\n" + " public class Inner {}\n" + "}\n" + "\n" + "}", "TopLevel")
assertCanFind(manager, "foo", "TopLevel")
assertCanFind(manager, "foo", "TopLevel.Inner")
assertCanFind(manager, "foo", "TopLevel.Inner.Inner")
assertCannotFind(manager, "foo", "TopLevel\$Inner.Inner")
assertCannotFind(manager, "foo", "TopLevel.Inner\$Inner")
assertCannotFind(manager, "foo", "TopLevel.Inner.Inner.Inner")
}
public fun testInnerClassesWithDollars() {
val manager = configureManager("package foo;\n\n" + "public class TopLevel {\n" +
"public class I\$nner {" + " public class I\$nner{}" + " public class \$Inner{}" + " public class In\$ne\$r\${}" + " public class Inner\$\${}" + " public class \$\$\$\$\${}" + "}\n" + "public class Inner\$ {" + " public class I\$nner{}" + " public class \$Inner{}" + " public class In\$ne\$r\${}" + " public class Inner\$\${}" + " public class \$\$\$\$\${}" + "}\n" + "public class In\$ner\$\$ {" + " public class I\$nner{}" + " public class \$Inner{}" + " public class In\$ne\$r\${}" + " public class Inner\$\${}" + " public class \$\$\$\$\${}" + "}\n" + "\n" + "}", "TopLevel")
assertCanFind(manager, "foo", "TopLevel")
assertCanFind(manager, "foo", "TopLevel.I\$nner")
assertCanFind(manager, "foo", "TopLevel.I\$nner.I\$nner")
assertCanFind(manager, "foo", "TopLevel.I\$nner.\$Inner")
assertCanFind(manager, "foo", "TopLevel.I\$nner.In\$ne\$r\$")
assertCanFind(manager, "foo", "TopLevel.I\$nner.Inner\$\$")
assertCanFind(manager, "foo", "TopLevel.I\$nner.\$\$\$\$\$")
assertCannotFind(manager, "foo", "TopLevel.I.nner.\$\$\$\$\$")
assertCanFind(manager, "foo", "TopLevel.Inner\$")
assertCanFind(manager, "foo", "TopLevel.Inner\$.I\$nner")
assertCanFind(manager, "foo", "TopLevel.Inner\$.\$Inner")
assertCanFind(manager, "foo", "TopLevel.Inner\$.In\$ne\$r\$")
assertCanFind(manager, "foo", "TopLevel.Inner\$.Inner\$\$")
assertCanFind(manager, "foo", "TopLevel.Inner\$.\$\$\$\$\$")
assertCannotFind(manager, "foo", "TopLevel.Inner..\$\$\$\$\$")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$.I\$nner")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$.\$Inner")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$.In\$ne\$r\$")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$.Inner\$\$")
assertCanFind(manager, "foo", "TopLevel.In\$ner\$\$.\$\$\$\$\$")
assertCannotFind(manager, "foo", "TopLevel.In.ner\$\$.\$\$\$\$\$")
}
public fun testTopLevelClassesWithDollars() {
val inTheMiddle = configureManager("package foo;\n\n public class Top\$Level {}", "Top\$Level")
assertCanFind(inTheMiddle, "foo", "Top\$Level")
val doubleAtTheEnd = configureManager("package foo;\n\n public class TopLevel\$\$ {}", "TopLevel\$\$")
assertCanFind(doubleAtTheEnd, "foo", "TopLevel\$\$")
val multiple = configureManager("package foo;\n\n public class Top\$Lev\$el\$ {}", "Top\$Lev\$el\$")
assertCanFind(multiple, "foo", "Top\$Lev\$el\$")
assertCannotFind(multiple, "foo", "Top.Lev\$el\$")
val twoBucks = configureManager("package foo;\n\n public class \$\$ {}", "\$\$")
assertCanFind(twoBucks, "foo", "\$\$")
}
throws(javaClass<Exception>())
public fun testTopLevelClassWithDollarsAndInners() {
val manager = configureManager("package foo;\n\n" + "public class Top\$Level\$\$ {\n" +
"public class I\$nner {" + " public class I\$nner{}" + " public class In\$ne\$r\${}" + " public class Inner\$\$\$\$\${}" + " public class \$Inner{}" + " public class \${}" + " public class \$\$\$\$\${}" + "}\n" + "public class Inner {" + " public class Inner{}" + "}\n" + "\n" + "}", "Top\$Level\$\$")
assertCanFind(manager, "foo", "Top\$Level\$\$")
assertCanFind(manager, "foo", "Top\$Level\$\$.Inner")
assertCanFind(manager, "foo", "Top\$Level\$\$.Inner.Inner")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.I\$nner")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.In\$ne\$r\$")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.Inner\$\$\$\$\$")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.\$Inner")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.\$")
assertCanFind(manager, "foo", "Top\$Level\$\$.I\$nner.\$\$\$\$\$")
assertCannotFind(manager, "foo", "Top.Level\$\$.I\$nner.\$\$\$\$\$")
}
public fun testDoNotThrowOnMalformedInput() {
val fileWithEmptyName = configureManager("package foo;\n\n public class Top\$Level {}", "")
val allScope = GlobalSearchScope.allScope(getProject())
fileWithEmptyName.findClass("foo.", allScope)
fileWithEmptyName.findClass(".", allScope)
fileWithEmptyName.findClass("..", allScope)
fileWithEmptyName.findClass(".foo", allScope)
}
public fun testSeveralClassesInOneFile() {
val manager = configureManager("package foo;\n\n" + "public class One {}\n" + "class Two {}\n" + "class Three {}", "One")
assertCanFind(manager, "foo", "One")
//NOTE: this is unsupported
assertCannotFind(manager, "foo", "Two")
assertCannotFind(manager, "foo", "Three")
}
public fun testScopeCheck() {
val manager = configureManager("package foo;\n\n" + "public class Test {}\n", "Test")
TestCase.assertNotNull("Should find class in all scope", manager.findClass("foo.Test", GlobalSearchScope.allScope(getProject())))
TestCase.assertNull("Should not find class in empty scope", manager.findClass("foo.Test", GlobalSearchScope.EMPTY_SCOPE))
}
private fun configureManager(Language("JAVA") text: String, className: String): KotlinCliJavaFileManagerImpl {
val root = PsiTestUtil.createTestProjectStructure(myProject, myModule, PlatformTestCase.myFilesToDelete)
val pkg = root.createChildDirectory(this, "foo")
val dir = myPsiManager.findDirectory(pkg)
TestCase.assertNotNull(dir)
dir.add(PsiFileFactory.getInstance(getProject()).createFileFromText(className + ".java", JavaFileType.INSTANCE, text))
val coreJavaFileManagerExt = KotlinCliJavaFileManagerImpl(myPsiManager)
coreJavaFileManagerExt.initIndex(JvmDependenciesIndex(listOf(JavaRoot(root, JavaRoot.RootType.SOURCE))))
coreJavaFileManagerExt.addToClasspath(root)
return coreJavaFileManagerExt
}
private fun assertCanFind(manager: KotlinCliJavaFileManagerImpl, packageFQName: String, classFqName: String) {
val allScope = GlobalSearchScope.allScope(getProject())
val classId = ClassId(FqName(packageFQName), FqName(classFqName), false)
val stringRequest = classId.asSingleFqName().asString()
val foundByClassId = manager.findClass(classId, allScope)
val foundByString = manager.findClass(stringRequest, allScope)
TestCase.assertNotNull("Could not find: $classId", foundByClassId)
TestCase.assertNotNull("Could not find: $stringRequest", foundByString)
TestCase.assertEquals(foundByClassId, foundByString)
TestCase.assertEquals("Found ${foundByClassId!!.getQualifiedName()} instead of $packageFQName", packageFQName + "." + classFqName,
foundByClassId.getQualifiedName())
}
private fun assertCannotFind(manager: KotlinCliJavaFileManagerImpl, packageFQName: String, classFqName: String) {
val classId = ClassId(FqName(packageFQName), FqName(classFqName), false)
val foundClass = manager.findClass(classId, GlobalSearchScope.allScope(getProject()))
TestCase.assertNull("Found, but shouldn't have: $classId", foundClass)
}
}
@@ -17,27 +17,30 @@
package org.jetbrains.kotlin.idea.decompiler.navigation;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.ClassOrPackageFragmentDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor;
import org.jetbrains.kotlin.idea.caches.resolve.JsProjectDetector;
import org.jetbrains.kotlin.idea.decompiler.DecompilerPackage;
import org.jetbrains.kotlin.idea.decompiler.KotlinClsFileBase;
import org.jetbrains.kotlin.idea.stubindex.JetSourceFilterScope;
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.ClassId;
import org.jetbrains.kotlin.psi.JetDeclaration;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.types.ErrorUtils;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
import static org.jetbrains.kotlin.load.kotlin.PackageClassUtils.getPackageClassId;
import static org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage.getClassId;
public final class DecompiledNavigationUtils {
@@ -76,34 +79,30 @@ public final class DecompiledNavigationUtils {
@NotNull Project project,
@NotNull DeclarationDescriptor referencedDescriptor
) {
FqName containerFqName = getContainerFqName(referencedDescriptor);
if (containerFqName == null) {
return null;
}
if (ErrorUtils.isError(referencedDescriptor)) return null;
ClassId containerClassId = getContainerClassId(referencedDescriptor);
if (containerClassId == null) return null;
GlobalSearchScope scopeToSearchIn = JetSourceFilterScope.kotlinSourceAndClassFiles(GlobalSearchScope.allScope(project), project);
VirtualFileFinder fileFinder = VirtualFileFinderFactory.SERVICE.getInstance(project).create(scopeToSearchIn);
VirtualFile virtualFile = fileFinder.findVirtualFileWithHeader(containerFqName);
if (virtualFile == null) {
return null;
}
return virtualFile;
return fileFinder.findVirtualFileWithHeader(containerClassId);
}
//TODO: navigate to inner classes
@Nullable
private static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) {
private static ClassId getContainerClassId(@NotNull DeclarationDescriptor referencedDescriptor) {
ClassOrPackageFragmentDescriptor
containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
if (containerDescriptor instanceof PackageFragmentDescriptor) {
return PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) containerDescriptor).getFqName());
return getPackageClassId(((PackageFragmentDescriptor) containerDescriptor).getFqName());
}
if (containerDescriptor instanceof ClassDescriptor) {
if (containerDescriptor.getContainingDeclaration() instanceof ClassDescriptor
|| ExpressionTypingUtils.isLocal(containerDescriptor.getContainingDeclaration(), containerDescriptor)) {
return getContainerFqName(containerDescriptor.getContainingDeclaration());
return getContainerClassId(containerDescriptor.getContainingDeclaration());
}
FqNameUnsafe fqNameUnsafe = getFqName(containerDescriptor);
return fqNameUnsafe.isSafe() ? fqNameUnsafe.toSafe() : null;
return getClassId((ClassDescriptor) containerDescriptor);
}
return null;
}
@@ -17,7 +17,6 @@
package org.jetbrains.kotlin.idea.vfilefinder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.util.indexing.FileBasedIndex;
@@ -25,7 +24,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder;
import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClassFinder;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.ClassId;
import java.util.Collection;
@@ -33,11 +32,9 @@ public final class IDEVirtualFileFinder extends VirtualFileKotlinClassFinder imp
private static final Logger LOG = Logger.getInstance(IDEVirtualFileFinder.class);
@NotNull private final Project project;
@NotNull private final GlobalSearchScope scope;
public IDEVirtualFileFinder(@NotNull Project project, @NotNull GlobalSearchScope scope) {
this.project = project;
public IDEVirtualFileFinder(@NotNull GlobalSearchScope scope) {
this.scope = scope;
if (scope != GlobalSearchScope.EMPTY_SCOPE && scope.getProject() == null) {
@@ -47,13 +44,13 @@ public final class IDEVirtualFileFinder extends VirtualFileKotlinClassFinder imp
@Nullable
@Override
public VirtualFile findVirtualFileWithHeader(@NotNull FqName className) {
Collection<VirtualFile> files = FileBasedIndex.getInstance().getContainingFiles(KotlinClassFileIndex.KEY, className, scope);
public VirtualFile findVirtualFileWithHeader(@NotNull ClassId classId) {
Collection<VirtualFile> files = FileBasedIndex.getInstance().getContainingFiles(KotlinClassFileIndex.KEY, classId.asSingleFqName(), scope);
if (files.isEmpty()) {
return null;
}
if (files.size() > 1) {
LOG.warn("There are " + files.size() + " classes with same fqName: " + className + " found.");
LOG.warn("There are " + files.size() + " classes with same fqName: " + classId + " found.");
}
return files.iterator().next();
}
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.vfilefinder;
import com.intellij.openapi.project.Project;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder;
@@ -24,16 +23,9 @@ import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory;
public class IDEVirtualFileFinderFactory implements VirtualFileFinderFactory {
@NotNull
private final Project project;
public IDEVirtualFileFinderFactory(@NotNull Project project) {
this.project = project;
}
@NotNull
@Override
public VirtualFileFinder create(@NotNull GlobalSearchScope scope) {
return new IDEVirtualFileFinder(project, scope);
return new IDEVirtualFileFinder(scope);
}
}
@@ -16,14 +16,14 @@
package org.jetbrains.kotlin.idea.decompiler.stubBuilder
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.FileContentImpl
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.load.kotlin.PackageClassUtils
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.decompiler.textBuilder.buildDecompiledText
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import com.intellij.util.indexing.FileContentImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.stubs.elements.JetFileStubBuilder
import org.junit.Assert
@@ -34,9 +34,8 @@ public class ClsStubConsistencyTest : JetLightCodeInsightFixtureTestCase() {
public fun testConsistencyForKotlinPackage() {
val project = getProject()
val packageClassFqName = PackageClassUtils.getPackageClassFqName(STANDARD_LIBRARY_FQNAME)
val virtualFileFinder = VirtualFileFinderFactory.SERVICE.getInstance(project).create(GlobalSearchScope.allScope(project))
val kotlinPackageFile = virtualFileFinder.findVirtualFileWithHeader(packageClassFqName)!!
val kotlinPackageFile = virtualFileFinder.findVirtualFileWithHeader(PackageClassUtils.getPackageClassId(STANDARD_LIBRARY_FQNAME))!!
val decompiledText = buildDecompiledText(kotlinPackageFile).text
val clsStub = KotlinClsStubBuilder().buildFileStub(FileContentImpl.createByFile(kotlinPackageFile))!!
@@ -44,9 +44,8 @@ public class DecompiledTextConsistencyTest : JetLightCodeInsightFixtureTestCase(
public fun testConsistencyWithJavaDescriptorResolver() {
val project = getProject()
val packageClassFqName = PackageClassUtils.getPackageClassFqName(STANDARD_LIBRARY_FQNAME)
val virtualFileFinder = VirtualFileFinderFactory.SERVICE.getInstance(project).create(GlobalSearchScope.allScope(project))
val kotlinPackageFile = virtualFileFinder.findVirtualFileWithHeader(packageClassFqName)!!
val kotlinPackageFile = virtualFileFinder.findVirtualFileWithHeader(PackageClassUtils.getPackageClassId(STANDARD_LIBRARY_FQNAME))!!
val projectBasedText = buildDecompiledText(kotlinPackageFile, ProjectBasedResolverForDecompiler(project)).text
val deserializedText = buildDecompiledText(kotlinPackageFile).text
Assert.assertEquals(projectBasedText, deserializedText)