diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/JavaElementFinder.java b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/JavaElementFinder.java index 0e15dd91337..fae86bf264c 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/JavaElementFinder.java +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/JavaElementFinder.java @@ -22,13 +22,12 @@ import com.google.common.collect.Sets; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.CachedValue; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.*; import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.SLRUCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,6 +40,7 @@ import org.jetbrains.kotlin.psi.JetFile; import org.jetbrains.kotlin.resolve.jvm.KotlinFinderMarker; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Set; @@ -121,7 +121,7 @@ public class JavaElementFinder extends PsiElementFinder implements KotlinFinderM answer.addAll(lightClassGenerationSupport.getPackageClasses(qualifiedName.parent(), scope)); } - return answer.toArray(new PsiClass[answer.size()]); + return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]); } // Finds explicitly declared classes and objects, not package classes @@ -209,7 +209,7 @@ public class JavaElementFinder extends PsiElementFinder implements KotlinFinderM } } - return answer.toArray(new PsiClass[answer.size()]); + return sortByClasspath(answer, scope).toArray(new PsiClass[answer.size()]); } @Override @@ -268,4 +268,27 @@ public class JavaElementFinder extends PsiElementFinder implements KotlinFinderM return fqName + " in " + scope; } } + + @NotNull + public static Comparator byClasspathComparator(@NotNull final GlobalSearchScope searchScope) { + return new Comparator() { + @Override + public int compare(@NotNull PsiElement o1, @NotNull PsiElement o2) { + VirtualFile f1 = PsiUtilCore.getVirtualFile(o1); + VirtualFile f2 = PsiUtilCore.getVirtualFile(o2); + if (f1 == f2) return 0; + if (f1 == null) return -1; + if (f2 == null) return 1; + return searchScope.compare(f2, f1); + } + }; + } + + private static Collection sortByClasspath(@NotNull List classes, @NotNull GlobalSearchScope searchScope) { + if (classes.size() > 1) { + ContainerUtil.quickSort(classes, byClasspathComparator(searchScope)); + } + + return classes; + } } diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java index 25e836d50d0..17c332a351d 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KotlinJavaFileStubProvider.java @@ -421,7 +421,7 @@ public class KotlinJavaFileStubProvider files) { JetFile firstFile = files.iterator().next(); - VirtualFile virtualFile = files.size() == 1 ? firstFile.getVirtualFile() : new LightVirtualFile(); + VirtualFile virtualFile = firstFile.getVirtualFile(); assert virtualFile != null : "No virtual file for " + firstFile; return virtualFile; } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.java index 89ab465a0f7..e59264b5188 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/resolve/IDELightClassGenerationSupport.java @@ -21,10 +21,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.libraries.LibraryUtil; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.ClassFileViewProvider; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiManager; +import com.intellij.psi.*; import com.intellij.psi.impl.PsiManagerImpl; import com.intellij.psi.impl.compiled.ClsClassImpl; import com.intellij.psi.impl.compiled.ClsFileImpl; @@ -71,12 +68,12 @@ public class IDELightClassGenerationSupport extends LightClassGenerationSupport private final Project project; - private final Comparator jetFileComparator; + private final Comparator scopeFileComparator; private final PsiManager psiManager; public IDELightClassGenerationSupport(@NotNull Project project) { this.project = project; - this.jetFileComparator = byScopeComparator(GlobalSearchScope.allScope(project)); + this.scopeFileComparator = JavaElementFinder.byClasspathComparator(GlobalSearchScope.allScope(project)); this.psiManager = PsiManager.getInstance(project); } @@ -87,7 +84,7 @@ public class IDELightClassGenerationSupport extends LightClassGenerationSupport assert !files.isEmpty() : "No files in package"; List sortedFiles = new ArrayList(files); - Collections.sort(sortedFiles, jetFileComparator); + Collections.sort(sortedFiles, scopeFileComparator); JetFile file = sortedFiles.get(0); ResolveSessionForBodies session = KotlinCacheService.getInstance(file.getProject()).getLazyResolveSession(file); @@ -319,38 +316,9 @@ public class IDELightClassGenerationSupport extends LightClassGenerationSupport for (Map.Entry> entry : filesByInfo.entrySet()) { result.add(new KotlinLightPackageClassInfo(entry.getValue(), entry.getKey())); } - sortByClasspath(wholeScope, result); return result; } - @NotNull - private static Comparator byScopeComparator(@NotNull final GlobalSearchScope searchScope) { - return new Comparator() { - @Override - public int compare(@NotNull JetFile o1, @NotNull JetFile o2) { - VirtualFile f1 = o1.getVirtualFile(); - VirtualFile f2 = o2.getVirtualFile(); - if (f1 == f2) return 0; - if (f1 == null) return -1; - if (f2 == null) return 1; - return searchScope.compare(f1, f2); - } - }; - } - - private static void sortByClasspath(@NotNull GlobalSearchScope wholeScope, @NotNull List result) { - final Comparator byScopeComparator = byScopeComparator(wholeScope); - Collections.sort(result, new Comparator() { - @Override - public int compare(@NotNull KotlinLightPackageClassInfo info1, @NotNull KotlinLightPackageClassInfo info2) { - JetFile file1 = info1.getFiles().iterator().next(); - JetFile file2 = info2.getFiles().iterator().next(); - //classes earlier that would appear earlier on classpath should go first - return -byScopeComparator.compare(file1, file2); - } - }); - } - private static final class KotlinLightPackageClassInfo { private final Collection files; private final IdeaModuleInfo moduleInfo; diff --git a/idea/testData/decompiler/lightClassesOrder/explicitClass/file.kt b/idea/testData/decompiler/lightClassesOrder/explicitClass/file.kt new file mode 100644 index 00000000000..b4978d41654 --- /dev/null +++ b/idea/testData/decompiler/lightClassesOrder/explicitClass/file.kt @@ -0,0 +1,3 @@ +package test1 + +class A diff --git a/idea/testData/decompiler/lightClassesOrder/packageClassOneFile/file.kt b/idea/testData/decompiler/lightClassesOrder/packageClassOneFile/file.kt new file mode 100644 index 00000000000..afee6b324a1 --- /dev/null +++ b/idea/testData/decompiler/lightClassesOrder/packageClassOneFile/file.kt @@ -0,0 +1,5 @@ +package test2 + +fun foo() { + +} diff --git a/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file1.kt b/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file1.kt new file mode 100644 index 00000000000..fb6a843ed73 --- /dev/null +++ b/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file1.kt @@ -0,0 +1,5 @@ +package test3 + +fun foo() { + +} diff --git a/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file2.kt b/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file2.kt new file mode 100644 index 00000000000..d0033f8d5db --- /dev/null +++ b/idea/testData/decompiler/lightClassesOrder/packageClassTwoFiles/file2.kt @@ -0,0 +1,5 @@ +package test3 + +fun bar() { + +} diff --git a/idea/tests/org/jetbrains/kotlin/asJava/LightClassesClasspathSortingTest.kt b/idea/tests/org/jetbrains/kotlin/asJava/LightClassesClasspathSortingTest.kt new file mode 100644 index 00000000000..63aeddfd2d5 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/asJava/LightClassesClasspathSortingTest.kt @@ -0,0 +1,72 @@ +/* + * 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.asJava + +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.impl.ResolveScopeManager +import org.jetbrains.kotlin.idea.caches.resolve.KotlinLightClassForDecompiledDeclaration +import org.jetbrains.kotlin.idea.test.JdkAndMockLibraryProjectDescriptor +import org.jetbrains.kotlin.idea.test.KotlinCodeInsightTestCase +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.idea.test.configureAs +import java.io.File +import kotlin.test.assertNotNull + +class LightClassesClasspathSortingTest : KotlinCodeInsightTestCase() { + fun testExplicitClass() { + doTest("test1.A") + } + + fun testPackageClassOneFile() { + doTest("test2.Test2Package") + } + + fun testPackageClassTwoFiles() { + doTest("test3.Test3Package") + } + + private fun doTest(fqName: String) { + // Same classes are in sources and in compiled Kotlin library. Test that light classes from sources have a priority. + + // Configure library first to make classes be indexed before correspondent classes from sources + val dirName = getTestName(true) + getModule().configureAs(getProjectDescriptor(dirName)) + + val testDirRoot = File(getTestDataPath()) + val filePaths = File(testDirRoot, dirName).listFiles().map { it.relativeTo(testDirRoot) }.toArrayList().toTypedArray() + configureByFiles(null, *filePaths) + + checkLightClassBeforeDecompiled(fqName) + } + + private fun checkLightClassBeforeDecompiled(fqName: String) { + val psiClass = JavaPsiFacade.getInstance(getProject()).findClass(fqName, ResolveScopeManager.getElementResolveScope(getFile())) + + assertNotNull(psiClass, "Can't find class for $fqName") + assert(psiClass is KotlinLightClassForExplicitDeclaration || psiClass is KotlinLightClassForPackage, + "Should be an explicit light class, but was $fqName ${psiClass.javaClass}") + assert(psiClass !is KotlinLightClassForDecompiledDeclaration, + "Should not be decompiled light class: $fqName ${psiClass.javaClass}") + } + + private fun getProjectDescriptor(dir: String) = + JdkAndMockLibraryProjectDescriptor(PluginTestCaseBase.getTestDataPathBase() + "/decompiler/lightClassesOrder/$dir", true) + + override fun getTestDataPath(): String? { + return PluginTestCaseBase.getTestDataPathBase() + "/decompiler/lightClassesOrder/" + } +} \ No newline at end of file