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
@@ -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()))
}