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:
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-16
@@ -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)
|
||||
+160
@@ -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())
|
||||
|
||||
+11
-22
@@ -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()))
|
||||
}
|
||||
Reference in New Issue
Block a user