diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt.as31 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt.as31 new file mode 100644 index 00000000000..49cdaa5b9e5 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/ClasspathRootsResolver.kt.as31 @@ -0,0 +1,329 @@ +/* + * Copyright 2010-2017 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.StandardFileSystems +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiJavaModule +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiManager +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.* +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageUtil +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.cli.jvm.config.JvmModulePathRoot +import org.jetbrains.kotlin.cli.jvm.index.JavaRoot +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder +import org.jetbrains.kotlin.cli.jvm.modules.JavaModuleGraph +import org.jetbrains.kotlin.config.ContentRoot +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.isValidJavaFqName +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleInfo +import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME +import java.io.IOException +import java.util.jar.Attributes +import java.util.jar.Manifest +import kotlin.LazyThreadSafetyMode.NONE + +class ClasspathRootsResolver( + private val psiManager: PsiManager, + private val messageCollector: MessageCollector?, + private val additionalModules: List, + private val contentRootToVirtualFile: (JvmContentRoot) -> VirtualFile?, + private val javaModuleFinder: CliJavaModuleFinder, + private val requireStdlibModule: Boolean, + private val outputDirectory: VirtualFile? +) { + val javaModuleGraph = JavaModuleGraph(javaModuleFinder) + + data class RootsAndModules(val roots: List, val modules: List) + + private data class RootWithPrefix(val root: VirtualFile, val packagePrefix: String?) + + fun convertClasspathRoots(contentRoots: List): RootsAndModules { + val javaSourceRoots = mutableListOf() + val jvmClasspathRoots = mutableListOf() + val jvmModulePathRoots = mutableListOf() + + for (contentRoot in contentRoots) { + if (contentRoot !is JvmContentRoot) continue + val root = contentRootToVirtualFile(contentRoot) ?: continue + when (contentRoot) { + is JavaSourceRoot -> javaSourceRoots += RootWithPrefix(root, contentRoot.packagePrefix) + is JvmClasspathRoot -> jvmClasspathRoots += root + is JvmModulePathRoot -> jvmModulePathRoots += root + else -> error("Unknown root type: $contentRoot") + } + } + + return computeRoots(javaSourceRoots, jvmClasspathRoots, jvmModulePathRoots) + } + + private fun computeRoots( + javaSourceRoots: List, + jvmClasspathRoots: List, + jvmModulePathRoots: List + ): RootsAndModules { + val result = mutableListOf() + val modules = mutableListOf() + + val hasOutputDirectoryInClasspath = outputDirectory in jvmClasspathRoots || outputDirectory in jvmModulePathRoots + + for ((root, packagePrefix) in javaSourceRoots) { + val modularRoot = modularSourceRoot(root, hasOutputDirectoryInClasspath) + if (modularRoot != null) { + modules += modularRoot + } + else { + result += JavaRoot(root, JavaRoot.RootType.SOURCE, packagePrefix?.let { prefix -> + if (isValidJavaFqName(prefix)) FqName(prefix) + else null.also { + report(STRONG_WARNING, "Invalid package prefix name is ignored: $prefix") + } + }) + } + } + + for (root in jvmClasspathRoots) { + result += JavaRoot(root, JavaRoot.RootType.BINARY) + } + + val outputDirectoryAddedAsPartOfModule = modules.any { module -> module.moduleRoots.any { it.file == outputDirectory } } + + for (root in jvmModulePathRoots) { + // Do not add output directory as a separate module if we're compiling an explicit named module. + // It's going to be included as a root of our module in modularSourceRoot. + if (outputDirectoryAddedAsPartOfModule && root == outputDirectory) continue + + val module = modularBinaryRoot(root) + if (module != null) { + modules += module + } + } + + addModularRoots(modules, result) + + return RootsAndModules(result, modules) + } + +/* + private fun findSourceModuleInfo(root: VirtualFile): Pair? { + val moduleInfoFile = + when { + root.isDirectory -> root.findChild(PsiJavaModule.MODULE_INFO_FILE) + root.name == PsiJavaModule.MODULE_INFO_FILE -> root + else -> null + } ?: return null + + val psiFile = psiManager.findFile(moduleInfoFile) ?: return null + val psiJavaModule = psiFile.children.singleOrNull { it is PsiJavaModule } as? PsiJavaModule ?: return null + + return moduleInfoFile to psiJavaModule + } + + private fun modularSourceRoot(root: VirtualFile, hasOutputDirectoryInClasspath: Boolean): JavaModule.Explicit? { + val (moduleInfoFile, psiJavaModule) = findSourceModuleInfo(root) ?: return null + val sourceRoot = JavaModule.Root(root, isBinary = false) + val roots = + if (hasOutputDirectoryInClasspath) + listOf(sourceRoot, JavaModule.Root(outputDirectory!!, isBinary = true)) + else listOf(sourceRoot) + return JavaModule.Explicit(JavaModuleInfo.create(psiJavaModule), roots, moduleInfoFile) + } + + private fun modularBinaryRoot(root: VirtualFile): JavaModule? { + val isJar = root.fileSystem.protocol == StandardFileSystems.JAR_PROTOCOL + val manifest: Attributes? by lazy(NONE) { readManifestAttributes(root) } + + val moduleInfoFile = + root.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) + ?: root.takeIf { isJar }?.findFileByRelativePath(MULTI_RELEASE_MODULE_INFO_CLS_FILE)?.takeIf { + manifest?.getValue(IS_MULTI_RELEASE)?.equals("true", ignoreCase = true) == true + } + + if (moduleInfoFile != null) { + val moduleInfo = JavaModuleInfo.read(moduleInfoFile) ?: return null + return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(root, isBinary = true)), moduleInfoFile) + } + + // Only .jar files can be automatic modules + if (isJar) { + val moduleRoot = listOf(JavaModule.Root(root, isBinary = true)) + + val automaticModuleName = manifest?.getValue(AUTOMATIC_MODULE_NAME) + if (automaticModuleName != null) { + return JavaModule.Automatic(automaticModuleName, moduleRoot) + } + + val originalFile = VfsUtilCore.virtualToIoFile(root) + val moduleName = LightJavaModule.moduleName(originalFile.nameWithoutExtension) + if (moduleName.isEmpty()) { + report(ERROR, "Cannot infer automatic module name for the file", VfsUtilCore.getVirtualFileForJar(root) ?: root) + return null + } + return JavaModule.Automatic(moduleName, moduleRoot) + } + + return null + } +*/ + + private fun readManifestAttributes(jarRoot: VirtualFile): Attributes? { + val manifestFile = jarRoot.findChild("META-INF")?.findChild("MANIFEST.MF") + return try { + manifestFile?.inputStream?.let(::Manifest)?.mainAttributes + } + catch (e: IOException) { + null + } + } + + private fun addModularRoots(modules: List, result: MutableList) { + // In current implementation, at most one source module is supported. This can be relaxed in the future if we support another + // compilation mode, similar to java's --module-source-path + val sourceModules = modules.filterIsInstance().filter(JavaModule::isSourceModule) + if (sourceModules.size > 1) { + for (module in sourceModules) { + report(ERROR, "Too many source module declarations found", module.moduleInfoFile) + } + return + } + + for (module in modules) { + val existing = javaModuleFinder.findModule(module.name) + if (existing == null) { + javaModuleFinder.addUserModule(module) + } + else if (module.moduleRoots != existing.moduleRoots) { + fun JavaModule.getRootFile() = + moduleRoots.firstOrNull()?.file?.let { VfsUtilCore.getVirtualFileForJar(it) ?: it } + + val thisFile = module.getRootFile() + val existingFile = existing.getRootFile() + val atExistingPath = if (existingFile == null) "" else " at: ${existingFile.path}" + report(STRONG_WARNING, "The root is ignored because a module with the same name '${module.name}' " + + "has been found earlier on the module path$atExistingPath", thisFile) + } + } + + if (javaModuleFinder.allObservableModules.none()) return + + val sourceModule = sourceModules.singleOrNull() + val addAllModulePathToRoots = "ALL-MODULE-PATH" in additionalModules + if (addAllModulePathToRoots && sourceModule != null) { + report(ERROR, "-Xadd-modules=ALL-MODULE-PATH can only be used when compiling the unnamed module") + return + } + + val rootModules = when { + sourceModule != null -> listOf(sourceModule.name) + additionalModules + addAllModulePathToRoots -> modules.map(JavaModule::name) + else -> computeDefaultRootModules() + additionalModules + } + + val allDependencies = javaModuleGraph.getAllDependencies(rootModules) + if (allDependencies.any { moduleName -> javaModuleFinder.findModule(moduleName) is JavaModule.Automatic }) { + // According to java.lang.module javadoc, if at least one automatic module is added to the module graph, + // all observable automatic modules should be added. + // There are no automatic modules in the JDK, so we select all automatic modules out of user modules + for (module in modules) { + if (module is JavaModule.Automatic) { + allDependencies += module.name + } + } + } + + report(LOGGING, "Loading modules: $allDependencies") + + for (moduleName in allDependencies) { + val module = javaModuleFinder.findModule(moduleName) + if (module == null) { + report(ERROR, "Module $moduleName cannot be found in the module graph") + } + else { + for ((root, isBinary) in module.moduleRoots) { + result.add(JavaRoot(root, if (isBinary) JavaRoot.RootType.BINARY else JavaRoot.RootType.SOURCE)) + } + } + } + + if (requireStdlibModule && sourceModule != null && !javaModuleGraph.reads(sourceModule.name, KOTLIN_STDLIB_MODULE_NAME)) { + report( + ERROR, + "The Kotlin standard library is not found in the module graph. " + + "Please ensure you have the 'requires $KOTLIN_STDLIB_MODULE_NAME' clause in your module definition", + sourceModule.moduleInfoFile + ) + } + } + + // See http://openjdk.java.net/jeps/261 + private fun computeDefaultRootModules(): List { + val result = arrayListOf() + + val systemModules = javaModuleFinder.systemModules.associateBy(JavaModule::name) + val javaSeExists = "java.se" in systemModules + if (javaSeExists) { + // The java.se module is a root, if it exists. + result.add("java.se") + } + + fun JavaModule.Explicit.exportsAtLeastOnePackageUnqualified(): Boolean = moduleInfo.exports.any { it.toModules.isEmpty() } + + if (!javaSeExists) { + // If it does not exist then every java.* module on the upgrade module path or among the system modules + // that exports at least one package, without qualification, is a root. + for ((name, module) in systemModules) { + if (name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) { + result.add(name) + } + } + } + + for ((name, module) in systemModules) { + // Every non-java.* module on the upgrade module path or among the system modules that exports at least one package, + // without qualification, is also a root. + if (!name.startsWith("java.") && module.exportsAtLeastOnePackageUnqualified()) { + result.add(name) + } + } + + return result + } + + private fun report(severity: CompilerMessageSeverity, message: String, file: VirtualFile? = null) { + if (messageCollector == null) { + throw IllegalStateException("${if (file != null) file.path + ":" else ""}$severity: $message (no MessageCollector configured)") + } + messageCollector.report( + severity, message, + if (file == null) null else CompilerMessageLocation.create(MessageUtil.virtualFileToPath(file)) + ) + } + + private companion object { + //const val MULTI_RELEASE_MODULE_INFO_CLS_FILE = "META-INF/versions/9/${PsiJavaModule.MODULE_INFO_CLS_FILE}" + const val AUTOMATIC_MODULE_NAME = "Automatic-Module-Name" + const val IS_MULTI_RELEASE = "Multi-Release" + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt.as31 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt.as31 new file mode 100644 index 00000000000..d6c8a1b03ec --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCliJavaFileManagerImpl.kt.as31 @@ -0,0 +1,302 @@ +/* + * 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.* +import com.intellij.psi.impl.file.PsiPackageImpl +import com.intellij.psi.search.GlobalSearchScope +import gnu.trove.THashMap +import org.jetbrains.kotlin.cli.jvm.index.JavaRoot +import org.jetbrains.kotlin.cli.jvm.index.JvmDependenciesIndex +import org.jetbrains.kotlin.cli.jvm.index.SingleJavaFileRootsIndex +import org.jetbrains.kotlin.load.java.structure.JavaClass +import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryClassSignatureParser +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.BinaryJavaClass +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.ClassifierResolutionContext +import org.jetbrains.kotlin.load.java.structure.impl.classFiles.isNotTopLevelClass +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.resolve.jvm.KotlinCliJavaFileManager +import org.jetbrains.kotlin.util.PerformanceCounter +import org.jetbrains.kotlin.utils.addIfNotNull +import java.util.* + +// TODO: do not inherit from CoreJavaFileManager to avoid accidental usage of its methods which do not use caches/indices +// Currently, the only relevant usage of this class as CoreJavaFileManager is at CoreJavaDirectoryService.getPackage, +// which is indirectly invoked from PsiPackage.getSubPackages +class KotlinCliJavaFileManagerImpl(private val myPsiManager: PsiManager) : CoreJavaFileManager(myPsiManager), KotlinCliJavaFileManager { + private val perfCounter = PerformanceCounter.create("Find Java class") + private lateinit var index: JvmDependenciesIndex + private lateinit var singleJavaFileRootsIndex: SingleJavaFileRootsIndex + private lateinit var packagePartProviders: List + private val topLevelClassesCache: MutableMap = THashMap() + private val allScope = GlobalSearchScope.allScope(myPsiManager.project) + private var useFastClassFilesReading = false + + fun initialize( + index: JvmDependenciesIndex, + packagePartProviders: List, + singleJavaFileRootsIndex: SingleJavaFileRootsIndex, + useFastClassFilesReading: Boolean + ) { + this.index = index + this.packagePartProviders = packagePartProviders + this.singleJavaFileRootsIndex = singleJavaFileRootsIndex + this.useFastClassFilesReading = useFastClassFilesReading + } + + private fun findPsiClass(classId: ClassId, searchScope: GlobalSearchScope): PsiClass? = perfCounter.time { + findVirtualFileForTopLevelClass(classId, searchScope)?.findPsiClassInVirtualFile(classId.relativeClassName.asString()) + } + + private fun findVirtualFileForTopLevelClass(classId: ClassId, searchScope: GlobalSearchScope): VirtualFile? { + val relativeClassName = classId.relativeClassName.asString() + return topLevelClassesCache.getOrPut(classId.packageFqName.child(classId.relativeClassName.pathSegments().first())) { + index.findClass(classId) { dir, type -> + findVirtualFileGivenPackage(dir, relativeClassName, type) + } + ?: singleJavaFileRootsIndex.findJavaSourceClass(classId) + }?.takeIf { it in searchScope } + } + + private val binaryCache: MutableMap = THashMap() + private val signatureParsingComponent = + BinaryClassSignatureParser() + + override fun findClass(classId: ClassId, searchScope: GlobalSearchScope): JavaClass? { + val virtualFile = findVirtualFileForTopLevelClass(classId, searchScope) ?: return null + + if (useFastClassFilesReading && virtualFile.extension == "class") { + // We return all class files' names in the directory in knownClassNamesInPackage method, so one may request an inner class + return binaryCache.getOrPut(classId) { + // Note that currently we implicitly suppose that searchScope for binary classes is constant and we do not use it + // as a key in cache + // This is a true assumption by now since there are two search scopes in compiler: one for sources and another one for binary + // When it become wrong because we introduce the modules into CLI, it's worth to consider + // having different KotlinCliJavaFileManagerImpl's for different modules + val classContent = virtualFile.contentsToByteArray() + if (virtualFile.nameWithoutExtension.contains("$") && isNotTopLevelClass(classContent)) return@getOrPut null + classId.outerClassId?.let { outerClassId -> + val outerClass = findClass(outerClassId, searchScope) + return@getOrPut outerClass?.findInnerClass(classId.shortClassName) + } + + val resolver = ClassifierResolutionContext { findClass(it, allScope) } + + BinaryJavaClass( + virtualFile, + classId.asSingleFqName(), + resolver, + signatureParsingComponent, + outerClass = null, + classContent = classContent + ) + } + } + + return virtualFile.findPsiClassInVirtualFile(classId.relativeClassName.asString())?.let(::JavaClassImpl) + } + + // this method is called from IDEA to resolve dependencies in Java code + // which supposedly shouldn't have errors so the dependencies exist in general + override fun findClass(qName: String, scope: GlobalSearchScope): PsiClass? { + // String cannot be reliably converted to ClassId because we don't know where the package name ends and class names begin. + // For example, if qName is "a.b.c.d.e", we should either look for a top level class "e" in the package "a.b.c.d", + // or, for example, for a nested class with the relative qualified name "c.d.e" in the package "a.b". + // Below, we start by looking for the top level class "e" in the package "a.b.c.d" first, then for the class "d.e" in the package + // "a.b.c", and so on, until we find something. Most classes are top level, so most of the times the search ends quickly + + forEachClassId(qName) { classId -> + findPsiClass(classId, scope)?.let { return it } + } + + return null + } + + private inline fun forEachClassId(fqName: String, block: (ClassId) -> Unit) { + var classId = fqName.toSafeTopLevelClassId() ?: return + + while (true) { + block(classId) + + val packageFqName = classId.packageFqName + if (packageFqName.isRoot) break + + classId = ClassId( + packageFqName.parent(), + FqName(packageFqName.shortName().asString() + "." + classId.relativeClassName.asString()), + false + ) + } + } + + override fun findClasses(qName: String, scope: GlobalSearchScope): Array = perfCounter.time { + val result = ArrayList(1) + forEachClassId(qName) { classId -> + val relativeClassName = classId.relativeClassName.asString() + index.traverseDirectoriesInPackage(classId.packageFqName) { dir, rootType -> + val psiClass = + findVirtualFileGivenPackage(dir, relativeClassName, rootType) + ?.takeIf { it in scope } + ?.findPsiClassInVirtualFile(relativeClassName) + if (psiClass != null) { + result.add(psiClass) + } + // traverse all + true + } + + result.addIfNotNull( + singleJavaFileRootsIndex.findJavaSourceClass(classId) + ?.takeIf { it in scope } + ?.findPsiClassInVirtualFile(relativeClassName) + ) + + if (result.isNotEmpty()) { + return@time result.toTypedArray() + } + } + + PsiClass.EMPTY_ARRAY + } + + override fun findPackage(packageName: String): PsiPackage? { + var found = false + val packageFqName = packageName.toSafeFqName() ?: return null + index.traverseDirectoriesInPackage(packageFqName) { _, _ -> + found = true + //abort on first found + false + } + if (!found) { + found = packagePartProviders.any { it.findPackageParts(packageName).isNotEmpty() } + } + if (!found) { + found = singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName).isNotEmpty() + } + return if (found) PsiPackageImpl(myPsiManager, packageName) else null + } + + private fun findVirtualFileGivenPackage( + packageDir: VirtualFile, + classNameWithInnerClasses: String, + rootType: JavaRoot.RootType + ): VirtualFile? { + 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.path}; ${packageDir.isValid} path=${packageDir.path}") + return null + } + + return vFile + } + + private fun VirtualFile.findPsiClassInVirtualFile( + classNameWithInnerClasses: String + ): PsiClass? { + val file = myPsiManager.findFile(this) as? PsiClassOwner ?: return null + return findClassInPsiFile(classNameWithInnerClasses, file) + } + + override fun knownClassNamesInPackage(packageFqName: FqName): Set { + val result = hashSetOf() + index.traverseDirectoriesInPackage(packageFqName, continueSearch = { + dir, _ -> + + for (child in dir.children) { + if (child.extension == "class" || child.extension == "java") { + result.add(child.nameWithoutExtension) + } + } + + true + }) + + for (classId in singleJavaFileRootsIndex.findJavaSourceClasses(packageFqName)) { + assert(!classId.isNestedClass) { "ClassId of a single .java source class should not be nested: $classId" } + result.add(classId.shortClassName.asString()) + } + + return result + } + +/* + override fun findModules(moduleName: String, scope: GlobalSearchScope): Collection { + // TODO + return emptySet() + } +*/ + + override fun getNonTrivialPackagePrefixes(): Collection = emptyList() + + companion object { + private val LOG = Logger.getInstance(KotlinCliJavaFileManagerImpl::class.java) + + private fun findClassInPsiFile(classNameWithInnerClassesDotSeparated: String, file: PsiClassOwner): PsiClass? { + for (topLevelClass in file.classes) { + 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.name) topLevelClass else null + } + + val segments = StringUtil.split(className, ".").iterator() + if (!segments.hasNext() || segments.next() != topLevelClass.name) { + return null + } + var curClass = topLevelClass + while (segments.hasNext()) { + val innerClassName = segments.next() + val innerClass = curClass.findInnerClassByName(innerClassName, false) ?: return null + curClass = innerClass + } + return curClass + } + } +} + +// a sad workaround to avoid throwing exception when called from inside IDEA code +private fun safely(compute: () -> T): T? = try { + compute() +} +catch (e: IllegalArgumentException) { + null +} +catch (e: AssertionError) { + null +} + +private fun String.toSafeFqName(): FqName? = safely { FqName(this) } +private fun String.toSafeTopLevelClassId(): ClassId? = safely { ClassId.topLevel(FqName(this)) } diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt.as31 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt.as31 new file mode 100644 index 00000000000..243a22cd8f6 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt.as31 @@ -0,0 +1,484 @@ +/* + * Copyright 2010-2017 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.application.ApplicationManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiManager +import com.intellij.psi.impl.PsiModificationTrackerImpl +import com.intellij.psi.search.DelegatingGlobalSearchScope +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.asJava.FilteredJvmDiagnostics +import org.jetbrains.kotlin.backend.common.output.OutputFileCollection +import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection +import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.ExitCode +import org.jetbrains.kotlin.cli.common.checkKotlinPackageUsage +import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.OUTPUT +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.WARNING +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil +import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAll +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import org.jetbrains.kotlin.cli.jvm.config.* +import org.jetbrains.kotlin.codegen.* +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.codegen.state.GenerationStateEventCallback +import org.jetbrains.kotlin.config.* +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil +import org.jetbrains.kotlin.idea.MainFunctionDetector +import org.jetbrains.kotlin.javac.JavacWrapper +import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager +import org.jetbrains.kotlin.modules.Module +import org.jetbrains.kotlin.modules.TargetId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.script.tryConstructClassFromStringArgs +import org.jetbrains.kotlin.util.PerformanceCounter +import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize +import java.io.File +import java.lang.reflect.InvocationTargetException +import java.net.URLClassLoader +import java.util.concurrent.TimeUnit + +object KotlinToJVMBytecodeCompiler { + + private fun getAbsolutePaths(buildFile: File, module: Module): List { + return module.getSourceFiles().map { sourceFile -> + val source = File(sourceFile) + if (!source.isAbsolute) { + File(buildFile.absoluteFile.parentFile, sourceFile).absolutePath + } + else { + source.absolutePath + } + } + } + + private fun writeOutput( + configuration: CompilerConfiguration, + outputFiles: OutputFileCollection, + mainClass: FqName? + ) { + val reportOutputFiles = configuration.getBoolean(CommonConfigurationKeys.REPORT_OUTPUT_FILES) + val jarPath = configuration.get(JVMConfigurationKeys.OUTPUT_JAR) + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE) + if (jarPath != null) { + val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false) + CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, mainClass, outputFiles) + if (reportOutputFiles) { + val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath) + messageCollector.report(OUTPUT, message) + } + return + } + + val outputDir = configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) ?: File(".") + outputFiles.writeAll(outputDir, messageCollector, reportOutputFiles) + } + + private fun createOutputFilesFlushingCallbackIfPossible(configuration: CompilerConfiguration): GenerationStateEventCallback { + if (configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY) == null) { + return GenerationStateEventCallback.DO_NOTHING + } + return GenerationStateEventCallback { state -> + val currentOutput = SimpleOutputFileCollection(state.factory.currentOutput) + writeOutput(configuration, currentOutput, mainClass = null) + if (!configuration.get(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false)) { + state.factory.releaseGeneratedOutput() + } + } + } + + internal fun compileModules(environment: KotlinCoreEnvironment, buildFile: File, chunk: List): Boolean { + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + + val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project) + + val projectConfiguration = environment.configuration + for (module in chunk) { + moduleVisibilityManager.addModule(module) + } + + val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS) + for (path in friendPaths) { + moduleVisibilityManager.addFriendPath(path) + } + + val targetDescription = "in targets [" + chunk.joinToString { input -> input.getModuleName() + "-" + input.getModuleType() } + "]" + + val result = repeatAnalysisIfNeeded(analyze(environment, targetDescription), environment, targetDescription) + if (result == null || !result.shouldGenerateCode) return false + + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + + result.throwIfError() + + val outputs = newLinkedHashMapWithExpectedSize(chunk.size) + + for (module in chunk) { + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + val ktFiles = CompileEnvironmentUtil.getKtFiles( + environment.project, getAbsolutePaths(buildFile, module), projectConfiguration + ) { path -> throw IllegalStateException("Should have been checked before: $path") } + if (!checkKotlinPackageUsage(environment, ktFiles)) return false + + val moduleConfiguration = projectConfiguration.copy().apply { + put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(module.getOutputDirectory())) + } + + outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module) + } + + try { + for ((_, state) in outputs) { + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + writeOutput(state.configuration, state.factory, null) + } + + if (projectConfiguration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)) { + val singleModule = chunk.singleOrNull() + if (singleModule != null) { + return JavacWrapper.getInstance(environment.project).use { + it.compile(File(singleModule.getOutputDirectory())) + } + } + else { + projectConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY).let { + it.report(WARNING, "A chunk contains multiple modules (${chunk.joinToString { it.getModuleName() }}). -Xuse-javac option couldn't be used to compile java files") + } + JavacWrapper.getInstance(environment.project).close() + } + } + + return true + } + finally { + outputs.values.forEach(GenerationState::destroy) + } + } + + internal fun configureSourceRoots(configuration: CompilerConfiguration, chunk: List, buildFile: File) { + for (module in chunk) { + configuration.addKotlinSourceRoots(getAbsolutePaths(buildFile, module)) + } + + for (module in chunk) { + for ((path, packagePrefix) in module.getJavaSourceRoots()) { + configuration.addJavaSourceRoot(File(path), packagePrefix) + } + } + + val isJava9Module = false /*chunk.any { module -> + module.getJavaSourceRoots().any { (path, packagePrefix) -> + val file = File(path) + packagePrefix == null && + (file.name == PsiJavaModule.MODULE_INFO_FILE || + (file.isDirectory && file.listFiles().any { it.name == PsiJavaModule.MODULE_INFO_FILE })) + } + }*/ + + for (module in chunk) { + for (classpathRoot in module.getClasspathRoots()) { + configuration.add( + JVMConfigurationKeys.CONTENT_ROOTS, + if (isJava9Module) JvmModulePathRoot(File(classpathRoot)) else JvmClasspathRoot(File(classpathRoot)) + ) + } + } + + for (module in chunk) { + val modularJdkRoot = module.modularJdkRoot + if (modularJdkRoot != null) { + // We use the SDK of the first module in the chunk, which is not always correct because some other module in the chunk + // might depend on a different SDK + configuration.put(JVMConfigurationKeys.JDK_HOME, File(modularJdkRoot)) + break + } + } + + configuration.addAll(JVMConfigurationKeys.MODULES, chunk) + } + + private fun findMainClass(generationState: GenerationState, files: List): FqName? { + val mainFunctionDetector = MainFunctionDetector(generationState.bindingContext) + return files.asSequence() + .map { file -> + if (mainFunctionDetector.hasMain(file.declarations)) + JvmFileClassUtil.getFileClassInfoNoResolve(file).facadeClassFqName + else + null + } + .singleOrNull { it != null } + } + + fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean { + val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project) + + val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS) + for (path in friendPaths) { + moduleVisibilityManager.addFriendPath(path) + } + + if (!checkKotlinPackageUsage(environment, environment.getSourceFiles())) return false + + val generationState = analyzeAndGenerate(environment) ?: return false + + val mainClass = findMainClass(generationState, environment.getSourceFiles()) + + try { + writeOutput(environment.configuration, generationState.factory, mainClass) + return true + } + finally { + generationState.destroy() + } + } + + internal fun compileAndExecuteScript(environment: KotlinCoreEnvironment, scriptArgs: List): ExitCode { + val scriptClass = compileScript(environment) ?: return ExitCode.COMPILATION_ERROR + + try { + try { + tryConstructClassFromStringArgs(scriptClass, scriptArgs) + ?: throw RuntimeException("unable to find appropriate constructor for class ${scriptClass.name} accepting arguments $scriptArgs\n") + } + finally { + // NB: these lines are required (see KT-9546) but aren't covered by tests + System.out.flush() + System.err.flush() + } + } + catch (e: Throwable) { + reportExceptionFromScript(e) + return ExitCode.SCRIPT_EXECUTION_ERROR + } + + return ExitCode.OK + } + + private fun repeatAnalysisIfNeeded( + result: AnalysisResult?, + environment: KotlinCoreEnvironment, + targetDescription: String? + ): AnalysisResult? { + if (result is AnalysisResult.RetryWithAdditionalJavaRoots) { + val configuration = environment.configuration + + val oldReadOnlyValue = configuration.isReadOnly + configuration.isReadOnly = false + configuration.addJavaSourceRoots(result.additionalJavaRoots) + configuration.isReadOnly = oldReadOnlyValue + + if (result.addToEnvironment) { + environment.updateClasspath(result.additionalJavaRoots.map { JavaSourceRoot(it, null) }) + } + + // Clear package caches (see KotlinJavaPsiFacade) + ApplicationManager.getApplication().runWriteAction { + (PsiManager.getInstance(environment.project).modificationTracker as? PsiModificationTrackerImpl)?.incCounter() + } + + // Clear all diagnostic messages + configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]?.clear() + + // Repeat analysis with additional Java roots (kapt generated sources) + return analyze(environment, targetDescription) + } + + return result + } + + private fun reportExceptionFromScript(exception: Throwable) { + // expecting InvocationTargetException from constructor invocation with cause that describes the actual cause + val stream = System.err + val cause = exception.cause + if (exception !is InvocationTargetException || cause == null) { + exception.printStackTrace(stream) + return + } + stream.println(cause) + val fullTrace = cause.stackTrace + for (i in 0 until fullTrace.size - exception.stackTrace.size) { + stream.println("\tat " + fullTrace[i]) + } + } + + fun compileScript(environment: KotlinCoreEnvironment, parentClassLoader: ClassLoader? = null): Class<*>? { + val state = analyzeAndGenerate(environment) ?: return null + + try { + val urls = environment.configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS).mapNotNull { root -> + when (root) { + is JvmModulePathRoot -> root.file // TODO: only add required modules + is JvmClasspathRoot -> root.file + else -> null + } + }.map { it.toURI().toURL() } + + val classLoader = GeneratedClassLoader(state.factory, parentClassLoader ?: URLClassLoader(urls.toTypedArray(), null)) + + val script = environment.getSourceFiles()[0].script ?: error("Script must be parsed") + return classLoader.loadClass(script.fqName.asString()) + } + catch (e: Exception) { + throw RuntimeException("Failed to evaluate script: " + e, e) + } + } + + fun analyzeAndGenerate(environment: KotlinCoreEnvironment): GenerationState? { + val result = repeatAnalysisIfNeeded(analyze(environment, null), environment, null) ?: return null + + if (!result.shouldGenerateCode) return null + + result.throwIfError() + + return generate(environment, environment.configuration, result, environment.getSourceFiles(), null) + } + + private fun analyze(environment: KotlinCoreEnvironment, targetDescription: String?): AnalysisResult? { + val sourceFiles = environment.getSourceFiles() + val collector = environment.messageCollector + + val analysisStart = PerformanceCounter.currentTime() + val analyzerWithCompilerReport = AnalyzerWithCompilerReport(collector, environment.configuration.languageVersionSettings) + analyzerWithCompilerReport.analyzeAndReport(sourceFiles) { + val project = environment.project + val moduleOutputs = environment.configuration.get(JVMConfigurationKeys.MODULES)?.mapNotNullTo(hashSetOf()) { module -> + environment.findLocalFile(module.getOutputDirectory()) + }.orEmpty() + val sourcesOnly = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, sourceFiles) + // To support partial and incremental compilation, we add the scope which contains binaries from output directories + // of the compiled modules (.class) to the list of scopes of the source module + val scope = if (moduleOutputs.isEmpty()) sourcesOnly else sourcesOnly.uniteWith(DirectoriesScope(project, moduleOutputs)) + TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + project, + sourceFiles, + NoScopeRecordCliBindingTrace(), + environment.configuration, + environment::createPackagePartProvider, + sourceModuleSearchScope = scope + ) + } + + val analysisNanos = PerformanceCounter.currentTime() - analysisStart + + val sourceLinesOfCode = environment.countLinesOfCode(sourceFiles) + val time = TimeUnit.NANOSECONDS.toMillis(analysisNanos) + val speed = sourceLinesOfCode.toFloat() * 1000 / time + + val message = "ANALYZE: ${sourceFiles.size} files ($sourceLinesOfCode lines) ${targetDescription ?: ""}" + + "in $time ms - ${"%.3f".format(speed)} loc/s" + + K2JVMCompiler.reportPerf(environment.configuration, message) + + val analysisResult = analyzerWithCompilerReport.analysisResult + + return if (!analyzerWithCompilerReport.hasErrors() || analysisResult is AnalysisResult.RetryWithAdditionalJavaRoots) + analysisResult + else + null + } + + class DirectoriesScope( + project: Project, + private val directories: Set + ) : DelegatingGlobalSearchScope(GlobalSearchScope.allScope(project)) { + private val fileSystems = directories.mapTo(hashSetOf(), VirtualFile::getFileSystem) + + override fun contains(file: VirtualFile): Boolean { + if (file.fileSystem !in fileSystems) return false + + var parent: VirtualFile = file + while (true) { + if (parent in directories) return true + parent = parent.parent ?: return false + } + } + + override fun toString() = "All files under: $directories" + } + + private fun GenerationState.Builder.withModule(module: Module?) = + apply { + targetId(module?.let { TargetId(it) }) + moduleName(module?.getModuleName()) + outDirectory(module?.let { File(it.getOutputDirectory()) }) + } + + private fun generate( + environment: KotlinCoreEnvironment, + configuration: CompilerConfiguration, + result: AnalysisResult, + sourceFiles: List, + module: Module? + ): GenerationState { + val isKapt2Enabled = environment.project.getUserData(IS_KAPT2_ENABLED_KEY) ?: false + val generationState = GenerationState.Builder( + environment.project, + ClassBuilderFactories.binaries(isKapt2Enabled), + result.moduleDescriptor, + result.bindingContext, + sourceFiles, + configuration + ) + .codegenFactory(if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory else DefaultCodegenFactory) + .withModule(module) + .onIndependentPartCompilationEnd(createOutputFilesFlushingCallbackIfPossible(configuration)) + .build() + + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + + val generationStart = PerformanceCounter.currentTime() + + KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION) + + val generationNanos = PerformanceCounter.currentTime() - generationStart + val desc = if (module != null) "target " + module.getModuleName() + "-" + module.getModuleType() + " " else "" + val numberOfSourceFiles = sourceFiles.size + val numberOfLines = environment.countLinesOfCode(sourceFiles) + val time = TimeUnit.NANOSECONDS.toMillis(generationNanos) + val speed = numberOfLines.toFloat() * 1000 / time + val message = "GENERATE: $numberOfSourceFiles files ($numberOfLines lines) ${desc}in $time ms - ${"%.3f".format(speed)} loc/s" + + K2JVMCompiler.reportPerf(environment.configuration, message) + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + + AnalyzerWithCompilerReport.reportDiagnostics( + FilteredJvmDiagnostics( + generationState.collectedExtraJvmDiagnostics, + result.bindingContext.diagnostics + ), + environment.messageCollector + ) + + AnalyzerWithCompilerReport.reportBytecodeVersionErrors( + generationState.extraJvmDiagnosticsTrace.bindingContext, environment.messageCollector + ) + + ProgressIndicatorAndCompilationCanceledStatus.checkCanceled() + return generationState + } + + private val KotlinCoreEnvironment.messageCollector: MessageCollector + get() = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt.as31 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt.as31 new file mode 100644 index 00000000000..a52e80c4ef3 --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/modules/CliJavaModuleFinder.kt.as31 @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2017 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.modules + +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModule +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleFinder + +class CliJavaModuleFinder(jrtFileSystemRoot: VirtualFile?) : JavaModuleFinder { + private val modulesRoot = jrtFileSystemRoot?.findChild("modules") + private val userModules = linkedMapOf() + + fun addUserModule(module: JavaModule) { + userModules.putIfAbsent(module.name, module) + } + + val allObservableModules: Sequence + get() = systemModules + userModules.values + + val systemModules: Sequence + get() = modulesRoot?.children.orEmpty().asSequence().mapNotNull(this::findSystemModule) + + override fun findModule(name: String): JavaModule? = + modulesRoot?.findChild(name)?.let(this::findSystemModule) ?: userModules[name] + + private fun findSystemModule(moduleRoot: VirtualFile): JavaModule.Explicit? { + /* + val file = moduleRoot.findChild(PsiJavaModule.MODULE_INFO_CLS_FILE) ?: return null + val moduleInfo = JavaModuleInfo.read(file) ?: return null + return JavaModule.Explicit(moduleInfo, listOf(JavaModule.Root(moduleRoot, isBinary = true)), file) + */ + return null + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.as31 b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.as31 new file mode 100644 index 00000000000..716f1c7e740 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.as31 @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2017 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.load.java.structure.impl + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiTypeParameter +import com.intellij.psi.search.SearchScope +import org.jetbrains.kotlin.asJava.KtLightClassMarker +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtPsiUtil +import org.jetbrains.kotlin.psi.psiUtil.contains + +class JavaClassImpl(psiClass: PsiClass) : JavaClassifierImpl(psiClass), VirtualFileBoundJavaClass, JavaAnnotationOwnerImpl, JavaModifierListOwnerImpl { + init { + assert(psiClass !is PsiTypeParameter) { "PsiTypeParameter should be wrapped in JavaTypeParameter, not JavaClass: use JavaClassifier.create()" } + } + + override val innerClassNames: Collection + get() = psi.innerClasses.mapNotNull { it.name?.takeIf(Name::isValidIdentifier)?.let(Name::identifier) } + + override fun findInnerClass(name: Name): JavaClass? { + return psi.findInnerClassByName(name.asString(), false)?.let(::JavaClassImpl) + } + + override val fqName: FqName? + get() { + val qualifiedName = psi.qualifiedName + return if (qualifiedName == null) null else FqName(qualifiedName) + } + + override val name: Name + get() = KtPsiUtil.safeName(psi.name) + + override val isInterface: Boolean + get() = psi.isInterface + + override val isAnnotationType: Boolean + get() = psi.isAnnotationType + + override val isEnum: Boolean + get() = psi.isEnum + + override val outerClass: JavaClassImpl? + get() { + val outer = psi.containingClass + return if (outer == null) null else JavaClassImpl(outer) + } + + override val typeParameters: List + get() = typeParameters(psi.typeParameters) + + override val supertypes: Collection + get() = classifierTypes(psi.superTypes) + + override val methods: Collection + get() { + assertNotLightClass() + // We apply distinct here because PsiClass#getMethods() can return duplicate PSI methods, for example in Lombok (see KT-11778) + // Return type seems to be null for example for the 'clone' Groovy method generated by @AutoClone (see EA-73795) + return methods(psi.methods.filter { method -> !method.isConstructor && method.returnType != null }).distinct() + } + + override val fields: Collection + get() { + assertNotLightClass() + return fields(psi.fields.filter { field -> + val name = field.name + // ex. Android plugin generates LightFields for resources started from '.' (.DS_Store file etc) + name != null && Name.isValidIdentifier(name) + }) + } + + override val constructors: Collection + get() { + assertNotLightClass() + // See for example org.jetbrains.plugins.scala.lang.psi.light.ScFunctionWrapper, + // which is present in getConstructors(), but its isConstructor() returns false + return constructors(psi.constructors.filter { method -> method.isConstructor }) + } + + override val isAbstract: Boolean + get() = JavaElementUtil.isAbstract(this) + + override val isStatic: Boolean + get() = JavaElementUtil.isStatic(this) + + override val isFinal: Boolean + get() = JavaElementUtil.isFinal(this) + + override val visibility: Visibility + get() = JavaElementUtil.getVisibility(this) + + override val lightClassOriginKind: LightClassOriginKind? + get() = (psi as? KtLightClassMarker)?.originKind + + override val virtualFile: VirtualFile? + get() = psi.containingFile?.virtualFile + + override fun isFromSourceCodeInScope(scope: SearchScope): Boolean = psi.containingFile in scope + + override fun getAnnotationOwnerPsi() = psi.modifierList + + private fun assertNotLightClass() { + val psiClass = psi + if (psiClass !is KtLightClassMarker) return + + val message = "Querying members of JavaClass created for $psiClass of type ${psiClass::class.java} defined in file ${psiClass.containingFile?.virtualFile?.canonicalPath}" + LOGGER.error(message) + } + + companion object { + private val LOGGER = Logger.getInstance(JavaClassImpl::class.java) + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinBinaryClassCache.kt.as31 b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinBinaryClassCache.kt.as31 new file mode 100644 index 00000000000..3ed195c944c --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/load/kotlin/KotlinBinaryClassCache.kt.as31 @@ -0,0 +1,73 @@ +/* + * 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.load.kotlin + +import com.intellij.ide.highlighter.JavaClassFileType +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.util.Computable +import com.intellij.openapi.vfs.VirtualFile + +class KotlinBinaryClassCache : Disposable { + private class RequestCache { + internal var virtualFile: VirtualFile? = null + internal var modificationStamp: Long = 0 + internal var virtualFileKotlinClass: VirtualFileKotlinClass? = null + + fun cache(file: VirtualFile, aClass: VirtualFileKotlinClass?): VirtualFileKotlinClass? { + virtualFile = file + virtualFileKotlinClass = aClass + modificationStamp = file.modificationStamp + + return aClass + } + } + + private val cache = object : ThreadLocal() { + override fun initialValue(): RequestCache { + return RequestCache() + } + } + + override fun dispose() { + // This is only relevant for tests. We create a new instance of Application for each test, and so a new instance of this service is + // also created for each test. However all tests share the same event dispatch thread, which would collect all instances of this + // thread-local if they're not removed properly. Each instance would transitively retain VFS resulting in OutOfMemoryError + cache.remove() + } + + companion object { + fun getKotlinBinaryClass(file: VirtualFile, fileContent: ByteArray? = null): KotlinJvmBinaryClass? { + if (file.fileType !== JavaClassFileType.INSTANCE) return null + + val service = ServiceManager.getService(KotlinBinaryClassCache::class.java) + val requestCache = service.cache.get() + + if (file.modificationStamp == requestCache.modificationStamp && file == requestCache.virtualFile) { + return requestCache.virtualFileKotlinClass + } + + val aClass = ApplicationManager.getApplication().runReadAction(Computable { + @Suppress("DEPRECATION") + VirtualFileKotlinClass.create(file, fileContent) + }) + + return requestCache.cache(file, aClass) + } + } +} diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleInfo.kt.as31 b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleInfo.kt.as31 new file mode 100644 index 00000000000..1bd0dfffc2d --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/resolve/jvm/modules/JavaModuleInfo.kt.as31 @@ -0,0 +1,89 @@ +/* + * Copyright 2010-2017 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.modules + +import com.intellij.openapi.vfs.VirtualFile +//import com.intellij.psi.PsiJavaModule +import com.intellij.psi.PsiModifier +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.utils.compact +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassVisitor +//import org.jetbrains.org.objectweb.asm.ModuleVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +//import org.jetbrains.org.objectweb.asm.Opcodes.ACC_TRANSITIVE +import java.io.IOException + +class JavaModuleInfo( + val moduleName: String, + val requires: List, + val exports: List +) { + data class Requires(val moduleName: String, val isTransitive: Boolean) + + data class Exports(val packageFqName: FqName, val toModules: List) + + override fun toString(): String = + "Module $moduleName (${requires.size} requires, ${exports.size} exports)" + + companion object { + /*fun create(psiJavaModule: PsiJavaModule): JavaModuleInfo { + return JavaModuleInfo( + psiJavaModule.name, + psiJavaModule.requires.mapNotNull { statement -> + statement.moduleName?.let { moduleName -> + JavaModuleInfo.Requires(moduleName, statement.hasModifierProperty(PsiModifier.TRANSITIVE)) + } + }, + psiJavaModule.exports.mapNotNull { statement -> + statement.packageName?.let { packageName -> + JavaModuleInfo.Exports(FqName(packageName), statement.moduleNames) + } + } + ) + }*/ + + /*fun read(file: VirtualFile): JavaModuleInfo? { + val contents = try { file.contentsToByteArray() } catch (e: IOException) { return null } + + var moduleName: String? = null + val requires = arrayListOf() + val exports = arrayListOf() + + ClassReader(contents).accept(object : ClassVisitor(Opcodes.ASM6) { + override fun visitModule(name: String, access: Int, version: String?): ModuleVisitor { + moduleName = name + + return object : ModuleVisitor(Opcodes.ASM6) { + override fun visitRequire(module: String, access: Int, version: String?) { + requires.add(Requires(module, (access and ACC_TRANSITIVE) != 0)) + } + + override fun visitExport(packageFqName: String, access: Int, modules: Array?) { + // For some reason, '/' is the delimiter in packageFqName here + exports.add(Exports(FqName(packageFqName.replace('/', '.')), modules?.toList().orEmpty())) + } + } + } + }, ClassReader.SKIP_DEBUG or ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES) + + return if (moduleName != null) + JavaModuleInfo(moduleName!!, requires.compact(), exports.compact()) + else null + }*/ + } +} diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt.as31 new file mode 100644 index 00000000000..40c99f83900 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/debugger/AndroidDexerImpl.kt.as31 @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2017 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.android.debugger + +import com.intellij.openapi.module.ModuleManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootModificationTracker +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.sdk.AndroidSdkData +import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer +import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad +import java.io.File +import java.net.URLClassLoader +import java.security.ProtectionDomain + +class AndroidDexerImpl(val project: Project) : AndroidDexer { + private val cachedDexWrapper = CachedValuesManager.getManager(project).createCachedValue({ + val dexWrapper = doGetAndroidDexFile()?.let { dexJarFile -> + val androidDexWrapperName = AndroidDexWrapper::class.java.canonicalName + val classBytes = this.javaClass.classLoader.getResource( + androidDexWrapperName.replace('.', '/') + ".class").readBytes() + + val dexClassLoader = object : URLClassLoader(arrayOf(dexJarFile.toURI().toURL()), this::class.java.classLoader) { + init { + defineClass(androidDexWrapperName, classBytes, 0, classBytes.size, null as ProtectionDomain?) + } + } + + Class.forName(androidDexWrapperName, true, dexClassLoader).newInstance() + } + + CachedValueProvider.Result.createSingleDependency(dexWrapper, ProjectRootModificationTracker.getInstance(project)) + }, /* trackValue = */ false) + + override fun dex(classes: Collection): ByteArray? { + val dexWrapper = cachedDexWrapper.value + val dexMethod = dexWrapper::class.java.methods.firstOrNull { it.name == "dex" } ?: return null + return dexMethod.invoke(dexWrapper, classes) as? ByteArray ?: return null + } + + private fun doGetAndroidDexFile(): File? { + for (module in ModuleManager.getInstance(project).modules) { + val androidFacet = AndroidFacet.getInstance(module) ?: continue + val sdkData = AndroidSdkData.getSdkData(androidFacet) ?: continue + val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false) + ?: sdkData.getLatestBuildTool(/* allowPreview = */ true) + ?: continue + + val dxJar = File(latestBuildTool.location, "lib/dx.jar") + if (dxJar.exists()) { + return dxJar + } + } + + return null + } +} \ No newline at end of file