diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index b7bf3d6635e..156e0c236f3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -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 diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java index 33e26a11e2e..b70ea736e8e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java @@ -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; diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.java deleted file mode 100644 index 5033784e4e1..00000000000 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.java +++ /dev/null @@ -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; - } - -} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.kt new file mode 100644 index 00000000000..a1b89e1e560 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinder.kt @@ -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 + } + } + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.kt similarity index 53% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.java rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.kt index 747ebc0d41b..7f63250c0ea 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/CliVirtualFileFinderFactory.kt @@ -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) } } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmDependenciesIndex.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmDependenciesIndex.kt new file mode 100644 index 00000000000..a7930d7d9e0 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/JvmDependenciesIndex.kt @@ -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 = EnumSet.of(RootType.BINARY) + public val SourceAndBinary: Set = 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) { + + //these fields are computed based on _roots passed to constructor which are filled in later + private val roots: List 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() + + 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? = null + + + // findClassGivenDirectory MUST check whether the class with this classId exists in given package + public fun findClass( + classId: ClassId, + acceptedRootTypes: Set = 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.SourceAndBinary, + continueSearch: (VirtualFile, JavaRoot.RootType) -> Boolean + ) { + search(TraverseRequest(packageFqName, acceptedRootTypes)) { dir, rootType -> + HandleResult(Unit, continueSearch(dir, rootType)) + } + } + + private data class HandleResult(val result: T?, val continueSearch: Boolean) + + private fun search( + request: SearchRequest, + handler: (VirtualFile, JavaRoot.RootType) -> HandleResult + ): 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 doSearch(request: SearchRequest, handler: (VirtualFile, JavaRoot.RootType) -> HandleResult): T? { + val findClassRequest = request as? FindClassRequest + + fun found(packageDirectory: VirtualFile, root: JavaRoot, result: T): T { + if (findClassRequest != null) { + lastClassSearch = Pair(findClassRequest, SearchResult.Found(packageDirectory, root)) + } + return result + } + + fun 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, fillCachesAfter: Int, cachesPath: List): 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): List { + val caches = ArrayList() + 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) : SearchRequest { + override val packageFqName: FqName + get() = classId.getPackageFqName() + } + + private data class TraverseRequest( + override val packageFqName: FqName, + override val acceptedRootTypes: Set + ) : SearchRequest + + private trait SearchRequest { + val packageFqName: FqName + val acceptedRootTypes: Set + } + + 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) \ No newline at end of file diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt new file mode 100644 index 00000000000..b546aaf0f0d --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt @@ -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.findClass(qName, scope) + } + + override fun findClasses(qName: String, scope: GlobalSearchScope): Array { + val classIdAsTopLevelClass = qName.toSafeTopLevelClassId() ?: return super.findClasses(qName, scope) + + val result = ArrayList() + 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.findClasses(qName, scope) + } + return result.toArray(arrayOfNulls(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()) + + 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 +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index 975bcfcf555..765de41918e 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -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() - private val classPath = ClassPath() + private val javaRoots = ArrayList() private val annotationsManager: CoreExternalAnnotationsManager @@ -114,6 +116,9 @@ public class KotlinCoreEnvironment private( registerProjectServices(projectEnvironment) fillClasspath(configuration) + val fileManager = ServiceManager.getService(project, javaClass()) + 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(), CliVirtualFileFinderFactory(classPath)) + project.registerService(javaClass(), 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()) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClassPath.java b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreProjectEnvironment.kt similarity index 51% rename from compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClassPath.java rename to compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreProjectEnvironment.kt index 8dead5f9379..7243f21335e 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClassPath.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreProjectEnvironment.kt @@ -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 { - - @NotNull - private final List roots = new ArrayList(); - - @NotNull - @Override - public Iterator 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())) +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.java index 51bcaabda62..790862131af 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/JavaClassFinderImpl.java @@ -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()); } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileFinder.java b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileFinder.java index c75bbe47f11..ad88ad1bc14 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileFinder.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileFinder.java @@ -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); } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileKotlinClassFinder.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileKotlinClassFinder.kt index ba9c097c922..0fdecc74d35 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileKotlinClassFinder.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/VirtualFileKotlinClassFinder.kt @@ -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) } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinCliJavaFileManager.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinCliJavaFileManager.kt new file mode 100644 index 00000000000..cecce802338 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinCliJavaFileManager.kt @@ -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? +} \ No newline at end of file diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java index 2472da1a081..7c5b1dee811 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/KotlinJavaPsiFacade.java @@ -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); } diff --git a/compiler/tests/org/jetbrains/kotlin/cli/jvm/KotlinCliJavaFileManagerTest.kt b/compiler/tests/org/jetbrains/kotlin/cli/jvm/KotlinCliJavaFileManagerTest.kt new file mode 100644 index 00000000000..8d29e870505 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/cli/jvm/KotlinCliJavaFileManagerTest.kt @@ -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()) + 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) + } +} diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/DecompiledNavigationUtils.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/DecompiledNavigationUtils.java index 5e922d69196..7d060bb5f5e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/DecompiledNavigationUtils.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/decompiler/navigation/DecompiledNavigationUtils.java @@ -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; } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinder.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinder.java index 0076856bf37..8222aa4259e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinder.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinder.java @@ -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 files = FileBasedIndex.getInstance().getContainingFiles(KotlinClassFileIndex.KEY, className, scope); + public VirtualFile findVirtualFileWithHeader(@NotNull ClassId classId) { + Collection 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(); } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinderFactory.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinderFactory.java index 45c9fae276d..abf71dbf561 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinderFactory.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/vfilefinder/IDEVirtualFileFinderFactory.java @@ -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); } } diff --git a/idea/tests/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubConsistencyTest.kt b/idea/tests/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubConsistencyTest.kt index 5362b85660c..85ce88941d1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubConsistencyTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/decompiler/stubBuilder/ClsStubConsistencyTest.kt @@ -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))!! diff --git a/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/DecompiledTextConsistencyTest.kt b/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/DecompiledTextConsistencyTest.kt index c78bd073ef9..af1ba615022 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/DecompiledTextConsistencyTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/decompiler/textBuilder/DecompiledTextConsistencyTest.kt @@ -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)