diff options
author | Alexey Sedunov <alexey.sedunov@jetbrains.com> | 2018-07-27 16:30:39 +0300 |
---|---|---|
committer | Alexey Sedunov <alexey.sedunov@jetbrains.com> | 2018-08-03 15:28:14 +0300 |
commit | 375c785694cf0fc5cc4c63a0911d1651931a1d0e (patch) | |
tree | 0d3c8052239a4bd337cea1fa8d67e008b29d4c40 | |
parent | 4c9eb5ef1c3859b986ebb72e6b526cb16a96d676 (diff) | |
download | kotlin-375c785694cf0fc5cc4c63a0911d1651931a1d0e.tar.gz |
Misc: Add bunch files to fix compatibility with AS3.3C4
3 files changed, 913 insertions, 0 deletions
diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as33c4 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as33c4 new file mode 100644 index 00000000000..e9c91492b50 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as33c4 @@ -0,0 +1,279 @@ +/* + * 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.idea.configuration + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.LibraryData +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.isQualifiedModuleNamesEnabled +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.OrderRootType +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.impl.libraries.LibraryImpl +import com.intellij.openapi.roots.libraries.PersistentLibraryKind +import com.intellij.openapi.util.Key +import com.intellij.util.PathUtil +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.config.CoroutineSupport +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor +import org.jetbrains.kotlin.gradle.ArgsInfo +import org.jetbrains.kotlin.gradle.CompilerArgumentsBySourceSet +import org.jetbrains.kotlin.idea.facet.* +import org.jetbrains.kotlin.idea.framework.CommonLibraryKind +import org.jetbrains.kotlin.idea.framework.JSLibraryKind +import org.jetbrains.kotlin.idea.framework.detectLibraryKind +import org.jetbrains.kotlin.idea.inspections.gradle.findAll +import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion +import org.jetbrains.kotlin.idea.inspections.gradle.getResolvedVersionByModuleData +import org.jetbrains.kotlin.psi.UserDataProperty +import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import java.io.File +import java.util.* + +var Module.compilerArgumentsBySourceSet + by UserDataProperty(Key.create<CompilerArgumentsBySourceSet>("CURRENT_COMPILER_ARGUMENTS")) + +var Module.sourceSetName + by UserDataProperty(Key.create<String>("SOURCE_SET_NAME")) + +interface GradleProjectImportHandler { + companion object : ProjectExtensionDescriptor<GradleProjectImportHandler>( + "org.jetbrains.kotlin.gradleProjectImportHandler", + GradleProjectImportHandler::class.java + ) + + fun importBySourceSet(facet: KotlinFacet, sourceSetNode: DataNode<GradleSourceSetData>) + fun importByModule(facet: KotlinFacet, moduleNode: DataNode<ModuleData>) +} + +class KotlinGradleSourceSetDataService : AbstractProjectDataService<GradleSourceSetData, Void>() { + override fun getTargetDataKey() = GradleSourceSetData.KEY + + override fun postProcess( + toImport: Collection<DataNode<GradleSourceSetData>>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (sourceSetNode in toImport) { + val sourceSetData = sourceSetNode.data + val ideModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + + val moduleNode = ExternalSystemApiUtil.findParent(sourceSetNode, ProjectKeys.MODULE) ?: continue + val sourceSetName = sourceSetNode.data.id.let { it.substring(it.lastIndexOf(':') + 1) } + val kotlinFacet = configureFacetByGradleModule(moduleNode, sourceSetName, ideModule, modelsProvider) ?: continue + GradleProjectImportHandler.getInstances(project).forEach { it.importBySourceSet(kotlinFacet, sourceSetNode) } + } + } +} + +class KotlinGradleProjectDataService : AbstractProjectDataService<ModuleData, Void>() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection<DataNode<ModuleData>>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (moduleNode in toImport) { + // If source sets are present, configure facets in the their modules + if (ExternalSystemApiUtil.getChildren(moduleNode, GradleSourceSetData.KEY).isNotEmpty()) continue + + val moduleData = moduleNode.data + val ideModule = modelsProvider.findIdeModule(moduleData) ?: continue + val kotlinFacet = configureFacetByGradleModule(moduleNode, null, ideModule, modelsProvider) ?: continue + GradleProjectImportHandler.getInstances(project).forEach { it.importByModule(kotlinFacet, moduleNode) } + } + } +} + +class KotlinGradleLibraryDataService : AbstractProjectDataService<LibraryData, Void>() { + override fun getTargetDataKey() = ProjectKeys.LIBRARY + + override fun postProcess( + toImport: MutableCollection<DataNode<LibraryData>>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + if (toImport.isEmpty()) return + val projectDataNode = toImport.first().parent!! + @Suppress("UNCHECKED_CAST") + val moduleDataNodes = projectDataNode.children.filter { it.data is ModuleData } as List<DataNode<ModuleData>> + val anyNonJvmModules = moduleDataNodes.any { detectPlatformByPlugin(it)?.takeIf { it !is TargetPlatformKind.Jvm } != null } + for (libraryDataNode in toImport) { + val ideLibrary = modelsProvider.findIdeLibrary(libraryDataNode.data) ?: continue + + val modifiableModel = modelsProvider.getModifiableLibraryModel(ideLibrary) as LibraryEx.ModifiableModelEx + if (anyNonJvmModules || ideLibrary.name?.looksAsNonJvmLibraryName() == true) { + detectLibraryKind(modifiableModel.getFiles(OrderRootType.CLASSES))?.let { modifiableModel.kind = it } + } else if (ideLibrary is LibraryImpl && (ideLibrary.kind === JSLibraryKind || ideLibrary.kind === CommonLibraryKind)) { + resetLibraryKind(modifiableModel) + } + } + } + + private fun String.looksAsNonJvmLibraryName() = nonJvmSuffixes.any { it in this } + + private fun resetLibraryKind(modifiableModel: LibraryEx.ModifiableModelEx) { + try { + val cls = LibraryImpl::class.java + // Don't use name-based lookup because field names are scrambled in IDEA Ultimate + for (field in cls.declaredFields) { + if (field.type == PersistentLibraryKind::class.java) { + field.isAccessible = true + field.set(modifiableModel, null) + return + } + } + LOG.info("Could not find field of type PersistentLibraryKind in LibraryImpl.class") + } catch (e: Exception) { + LOG.info("Failed to reset library kind", e) + } + } + + companion object { + val LOG = Logger.getInstance(KotlinGradleLibraryDataService::class.java) + + val nonJvmSuffixes = listOf("-common", "-js", "-native", "-kjsm") + } +} + +fun detectPlatformByPlugin(moduleNode: DataNode<ModuleData>): TargetPlatformKind<*>? { + return when (moduleNode.platformPluginId) { + "kotlin-platform-jvm" -> TargetPlatformKind.Jvm[JvmTarget.JVM_1_6] + "kotlin-platform-js" -> TargetPlatformKind.JavaScript + "kotlin-platform-common" -> TargetPlatformKind.Common + else -> null + } +} + +private fun detectPlatformByLibrary(moduleNode: DataNode<ModuleData>): TargetPlatformKind<*>? { + val detectedPlatforms = + mavenLibraryIdToPlatform.entries + .filter { moduleNode.getResolvedVersionByModuleData(KOTLIN_GROUP_ID, listOf(it.key)) != null } + .map { it.value }.distinct() + return detectedPlatforms.singleOrNull() ?: detectedPlatforms.firstOrNull { it != TargetPlatformKind.Common } +} + +fun configureFacetByGradleModule( + moduleNode: DataNode<ModuleData>, + sourceSetName: String?, + ideModule: Module, + modelsProvider: IdeModifiableModelsProvider +): KotlinFacet? { + if (!moduleNode.isResolved) return null + + if (!moduleNode.hasKotlinPlugin) { + val facetModel = modelsProvider.getModifiableFacetModel(ideModule) + val facet = facetModel.getFacetByType(KotlinFacetType.TYPE_ID) + if (facet != null) { + facetModel.removeFacet(facet) + } + return null + } + + val compilerVersion = moduleNode.findAll(BuildScriptClasspathData.KEY).firstOrNull()?.data?.let(::findKotlinPluginVersion) + ?: return null + val platformKind = detectPlatformByPlugin(moduleNode) ?: detectPlatformByLibrary(moduleNode) + + val coroutinesProperty = CoroutineSupport.byCompilerArgument( + moduleNode.coroutines ?: findKotlinCoroutinesProperty(ideModule.project) + ) + + val kotlinFacet = ideModule.getOrCreateFacet(modelsProvider, false) + kotlinFacet.configureFacet(compilerVersion, coroutinesProperty, platformKind, modelsProvider) + + ideModule.compilerArgumentsBySourceSet = moduleNode.compilerArgumentsBySourceSet + ideModule.sourceSetName = sourceSetName + + val argsInfo = moduleNode.compilerArgumentsBySourceSet?.get(sourceSetName ?: "main") + if (argsInfo != null) { + configureFacetByCompilerArguments(kotlinFacet, argsInfo, modelsProvider) + } + + with(kotlinFacet.configuration.settings) { + val sourceSetNameForImplementedModules = if (ideModule.getBuildSystemType() == AndroidGradle) null else sourceSetName + implementedModuleNames = getImplementedModuleNames(moduleNode, sourceSetNameForImplementedModules, ideModule.project) + + productionOutputPath = getExplicitOutputPath(moduleNode, platformKind, "main") + testOutputPath = getExplicitOutputPath(moduleNode, platformKind, "test") + } + + kotlinFacet.noVersionAutoAdvance() + + return kotlinFacet +} + +fun configureFacetByCompilerArguments(kotlinFacet: KotlinFacet, argsInfo: ArgsInfo, modelsProvider: IdeModifiableModelsProvider?) { + val currentCompilerArguments = argsInfo.currentArguments + val defaultCompilerArguments = argsInfo.defaultArguments + val dependencyClasspath = argsInfo.dependencyClasspath.map { PathUtil.toSystemIndependentName(it) } + if (modelsProvider != null && currentCompilerArguments.isNotEmpty()) { + parseCompilerArgumentsToFacet(currentCompilerArguments, defaultCompilerArguments, kotlinFacet, modelsProvider) + } + adjustClasspath(kotlinFacet, dependencyClasspath) +} + +private fun getImplementedModuleNames(moduleNode: DataNode<ModuleData>, sourceSetName: String?, project: Project): List<String> { + val baseModuleNames = moduleNode.implementedModuleNames + if (baseModuleNames.isEmpty() || sourceSetName == null) return baseModuleNames + val delimiter = if(isQualifiedModuleNamesEnabled(project)) "." else "_" + return baseModuleNames.map { "$it$delimiter$sourceSetName" } +} + +private fun getExplicitOutputPath(moduleNode: DataNode<ModuleData>, platformKind: TargetPlatformKind<*>?, sourceSet: String): String? { + if (platformKind !== TargetPlatformKind.JavaScript) return null + val k2jsArgumentList = moduleNode.compilerArgumentsBySourceSet?.get(sourceSet)?.currentArguments ?: return null + return K2JSCompilerArguments().apply { parseCommandLineArguments(k2jsArgumentList, this) }.outputFile +} + +private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List<String>) { + if (dependencyClasspath.isEmpty()) return + val arguments = kotlinFacet.configuration.settings.compilerArguments as? K2JVMCompilerArguments ?: return + val fullClasspath = arguments.classpath?.split(File.pathSeparator) ?: emptyList() + if (fullClasspath.isEmpty()) return + val newClasspath = fullClasspath - dependencyClasspath + arguments.classpath = if (newClasspath.isNotEmpty()) newClasspath.joinToString(File.pathSeparator) else null +} + +private val gradlePropertyFiles = listOf("local.properties", "gradle.properties") + +private fun findKotlinCoroutinesProperty(project: Project): String { + for (propertyFileName in gradlePropertyFiles) { + val propertyFile = project.baseDir.findChild(propertyFileName) ?: continue + val properties = Properties() + properties.load(propertyFile.inputStream) + properties.getProperty("kotlin.coroutines")?.let { return it } + } + + return CoroutineSupport.getCompilerArgument(LanguageFeature.Coroutines.defaultState) +} diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt.as33c4 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt.as33c4 new file mode 100644 index 00000000000..f7f671811ef --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinWithGradleConfigurator.kt.as33c4 @@ -0,0 +1,403 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.codeInsight.CodeInsightUtilCore +import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix +import com.intellij.ide.actions.OpenFileAction +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModuleUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.roots.ExternalLibraryDescriptor +import com.intellij.openapi.roots.ModuleRootManager +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.openapi.vfs.WritingAccessProvider +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.intellij.util.PathUtil +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.CoroutineSupport +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion +import org.jetbrains.kotlin.idea.framework.ui.ConfigureDialogWithModulesAndVersion +import org.jetbrains.kotlin.idea.quickfix.ChangeCoroutineSupportFix +import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.idea.versions.LibraryJarDescriptor +import org.jetbrains.kotlin.idea.versions.getStdlibArtifactId +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.plugins.gradle.util.GradleConstants +import org.jetbrains.plugins.groovy.lang.psi.GroovyFile +import java.io.File +import java.util.* + +abstract class KotlinWithGradleConfigurator : KotlinProjectConfigurator { + + override fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus { + val module = moduleSourceRootGroup.baseModule + if (!isApplicable(module)) { + return ConfigureKotlinStatus.NON_APPLICABLE + } + + if (moduleSourceRootGroup.sourceRootModules.all(::hasAnyKotlinRuntimeInScope)) { + return ConfigureKotlinStatus.CONFIGURED + } + + val buildFiles = runReadAction { + listOf( + module.getBuildScriptPsiFile(), + module.project.getTopLevelBuildScriptPsiFile() + ).filterNotNull() + } + + if (buildFiles.isEmpty()) { + return ConfigureKotlinStatus.NON_APPLICABLE + } + + if (buildFiles.none { it.isConfiguredByAnyGradleConfigurator() }) { + return ConfigureKotlinStatus.CAN_BE_CONFIGURED + } + + return ConfigureKotlinStatus.BROKEN + } + + private fun PsiFile.isConfiguredByAnyGradleConfigurator(): Boolean { + return Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME) + .filterIsInstance<KotlinWithGradleConfigurator>() + .any { it.isFileConfigured(this) } + } + + protected open fun isApplicable(module: Module): Boolean = + module.getBuildSystemType() == Gradle + + protected open fun getMinimumSupportedVersion() = "1.0.0" + + protected fun PsiFile.isKtDsl() = this is KtFile + + private fun isFileConfigured(buildScript: PsiFile): Boolean = with(getManipulator(buildScript)) { + isConfiguredWithOldSyntax(kotlinPluginName) || isConfigured(getKotlinPluginExpression(buildScript.isKtDsl())) + } + + @JvmSuppressWildcards + override fun configure(project: Project, excludeModules: Collection<Module>) { + val dialog = ConfigureDialogWithModulesAndVersion(project, this, excludeModules, getMinimumSupportedVersion()) + + dialog.show() + if (!dialog.isOK) return + + val collector = configureSilently(project, dialog.modulesToConfigure, dialog.kotlinVersion) + collector.showNotification() + } + + fun configureSilently(project: Project, modules: List<Module>, version: String): NotificationMessageCollector { + return project.executeCommand("Configure Kotlin") { + val collector = createConfigureKotlinNotificationCollector(project) + val changedFiles = configureWithVersion(project, modules, version, collector) + + for (file in changedFiles) { + OpenFileAction.openFile(file.virtualFile, project) + } + collector + } + } + + fun configureWithVersion( + project: Project, + modulesToConfigure: List<Module>, + kotlinVersion: String, + collector: NotificationMessageCollector + ): HashSet<PsiFile> { + val filesToOpen = HashSet<PsiFile>() + val buildScript = project.getTopLevelBuildScriptPsiFile() + if (buildScript != null && canConfigureFile(buildScript)) { + val isModified = configureBuildScript(buildScript, true, kotlinVersion, collector) + if (isModified) { + filesToOpen.add(buildScript) + } + } + + for (module in modulesToConfigure) { + val file = module.getBuildScriptPsiFile() + if (file != null && canConfigureFile(file)) { + configureModule(module, file, false, kotlinVersion, collector, filesToOpen) + } else { + showErrorMessage(project, "Cannot find build.gradle file for module " + module.name) + } + } + return filesToOpen + } + + open fun configureModule( + module: Module, + file: PsiFile, + isTopLevelProjectFile: Boolean, + version: String, + collector: NotificationMessageCollector, + filesToOpen: MutableCollection<PsiFile> + ) { + val isModified = configureBuildScript(file, isTopLevelProjectFile, version, collector) + if (isModified) { + filesToOpen.add(file) + } + } + + protected fun configureModuleBuildScript(file: PsiFile, version: String): Boolean { + val sdk = ModuleUtil.findModuleForPsiElement(file)?.let { ModuleRootManager.getInstance(it).sdk } + val jvmTarget = getJvmTarget(sdk, version) + return getManipulator(file).configureModuleBuildScript( + kotlinPluginName, + getKotlinPluginExpression(file.isKtDsl()), + getStdlibArtifactName(sdk, version), + version, + jvmTarget + ) + } + + protected open fun getStdlibArtifactName(sdk: Sdk?, version: String) = getStdlibArtifactId(sdk, version) + + protected open fun getJvmTarget(sdk: Sdk?, version: String): String? = null + + protected abstract val kotlinPluginName: String + protected abstract fun getKotlinPluginExpression(forKotlinDsl: Boolean): String + + protected open fun addElementsToFile( + file: PsiFile, + isTopLevelProjectFile: Boolean, + version: String + ): Boolean { + if (!isTopLevelProjectFile) { + var wasModified = getManipulator(file).configureProjectBuildScript(kotlinPluginName, version) + wasModified = wasModified or configureModuleBuildScript(file, version) + return wasModified + } + return false + } + + private fun configureBuildScript( + file: PsiFile, + isTopLevelProjectFile: Boolean, + version: String, + collector: NotificationMessageCollector + ): Boolean { + val isModified = file.project.executeWriteCommand("Configure ${file.name}", null) { + val isModified = addElementsToFile(file, isTopLevelProjectFile, version) + + CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(file) + isModified + } + + val virtualFile = file.virtualFile + if (virtualFile != null && isModified) { + collector.addMessage(virtualFile.path + " was modified") + } + return isModified + } + + override fun updateLanguageVersion( + module: Module, + languageVersion: String?, + apiVersion: String?, + requiredStdlibVersion: ApiVersion, + forTests: Boolean + ) { + val runtimeUpdateRequired = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) }?.let { runtimeVersion -> + runtimeVersion < requiredStdlibVersion + } ?: false + + if (runtimeUpdateRequired) { + Messages.showErrorDialog( + module.project, + "This language feature requires version $requiredStdlibVersion or later of the Kotlin runtime library. " + + "Please update the version in your build script.", + "Update Language Version" + ) + return + } + + val element = changeLanguageVersion(module, languageVersion, apiVersion, forTests) + + element?.let { + OpenFileDescriptor(module.project, it.containingFile.virtualFile, it.textRange.startOffset).navigate(true) + } + } + + override fun changeCoroutineConfiguration(module: Module, state: LanguageFeature.State) { + val runtimeUpdateRequired = state != LanguageFeature.State.DISABLED && + (getRuntimeLibraryVersion(module)?.startsWith("1.0") ?: false) + + if (runtimeUpdateRequired) { + Messages.showErrorDialog( + module.project, + "Coroutines support requires version 1.1 or later of the Kotlin runtime library. " + + "Please update the version in your build script.", + ChangeCoroutineSupportFix.getFixText(state) + ) + return + } + + val element = changeCoroutineConfiguration(module, CoroutineSupport.getCompilerArgument(state)) + if (element != null) { + OpenFileDescriptor(module.project, element.containingFile.virtualFile, element.textRange.startOffset).navigate(true) + } + } + + override fun addLibraryDependency( + module: Module, + element: PsiElement, + library: ExternalLibraryDescriptor, + libraryJarDescriptors: List<LibraryJarDescriptor> + ) { + val scope = OrderEntryFix.suggestScopeByLocation(module, element) + KotlinWithGradleConfigurator.addKotlinLibraryToModule(module, scope, library) + } + + companion object { + fun getManipulator(file: PsiFile, preferNewSyntax: Boolean = true): GradleBuildScriptManipulator<*> = when (file) { + is KtFile -> KotlinBuildScriptManipulator(file, preferNewSyntax) + is GroovyFile -> GroovyBuildScriptManipulator(file, preferNewSyntax) + else -> error("Unknown build script file type!") + } + + val GROUP_ID = "org.jetbrains.kotlin" + val GRADLE_PLUGIN_ID = "kotlin-gradle-plugin" + + val CLASSPATH = "classpath \"$GROUP_ID:$GRADLE_PLUGIN_ID:\$kotlin_version\"" + + private val KOTLIN_BUILD_SCRIPT_NAME = "build.gradle.kts" + private val KOTLIN_SETTINGS_SCRIPT_NAME = "settings.gradle.kts" + + fun getGroovyDependencySnippet(artifactName: String, scope: String, withVersion: Boolean) = + "$scope \"org.jetbrains.kotlin:$artifactName${if (withVersion) ":\$kotlin_version" else ""}\"" + + fun getGroovyApplyPluginDirective(pluginName: String) = "apply plugin: '$pluginName'" + + fun addKotlinLibraryToModule(module: Module, scope: DependencyScope, libraryDescriptor: ExternalLibraryDescriptor) { + val buildScript = module.getBuildScriptPsiFile() ?: return + if (!canConfigureFile(buildScript)) { + return + } + + getManipulator(buildScript) + .addKotlinLibraryToModuleBuildScript(scope, libraryDescriptor, module.getBuildSystemType() == AndroidGradle) + + buildScript.virtualFile?.let { + createConfigureKotlinNotificationCollector(buildScript.project) + .addMessage(it.path + " was modified") + .showNotification() + } + } + + fun changeCoroutineConfiguration(module: Module, coroutineOption: String): PsiElement? = changeBuildGradle(module) { + getManipulator(it).changeCoroutineConfiguration(coroutineOption) + } + + fun changeLanguageVersion(module: Module, languageVersion: String?, apiVersion: String?, forTests: Boolean) = + changeBuildGradle(module) { buildScriptFile -> + val manipulator = getManipulator(buildScriptFile) + var result: PsiElement? = null + if (languageVersion != null) { + result = manipulator.changeLanguageVersion(languageVersion, forTests) + } + + if (apiVersion != null) { + result = manipulator.changeApiVersion(apiVersion, forTests) + } + + result + } + + private fun changeBuildGradle(module: Module, body: (PsiFile) -> PsiElement?): PsiElement? { + val buildScriptFile = module.getBuildScriptPsiFile() + if (buildScriptFile != null && canConfigureFile(buildScriptFile)) { + return buildScriptFile.project.executeWriteCommand("Change build.gradle configuration", null) { + body(buildScriptFile) + } + } + return null + } + + fun getKotlinStdlibVersion(module: Module): String? { + return module.getBuildScriptPsiFile()?.let { + getManipulator(it).getKotlinStdlibVersion() + } + } + + private fun canConfigureFile(file: PsiFile): Boolean = WritingAccessProvider.isPotentiallyWritable(file.virtualFile, null) + + private fun Module.getBuildScriptPsiFile() = + getBuildScriptFile(GradleConstants.DEFAULT_SCRIPT_NAME, KOTLIN_BUILD_SCRIPT_NAME)?.getPsiFile(project) + + fun Module.getBuildScriptSettingsPsiFile() = + getBuildScriptSettingsFile(GradleConstants.SETTINGS_FILE_NAME, KOTLIN_SETTINGS_SCRIPT_NAME)?.getPsiFile(project) + + private fun Project.getTopLevelBuildScriptPsiFile() = basePath?.let { + findBuildGradleFile(it, GradleConstants.DEFAULT_SCRIPT_NAME, KOTLIN_BUILD_SCRIPT_NAME)?.getPsiFile(this) + } + + fun Module.getTopLevelBuildScriptSettingsPsiFile() = + ExternalSystemApiUtil.getExternalRootProjectPath(this)?.let { externalProjectPath -> + findBuildGradleFile(externalProjectPath, GradleConstants.SETTINGS_FILE_NAME, KOTLIN_SETTINGS_SCRIPT_NAME)?.getPsiFile(project) + } + + private fun Module.getBuildScriptFile(vararg fileNames: String): File? { + val moduleDir = File(moduleFilePath).parent + findBuildGradleFile(moduleDir, *fileNames)?.let { + return it + } + + ModuleRootManager.getInstance(this).contentRoots.forEach { root -> + findBuildGradleFile(root.path, *fileNames)?.let { + return it + } + } + + ExternalSystemApiUtil.getExternalProjectPath(this)?.let { externalProjectPath -> + findBuildGradleFile(externalProjectPath, *fileNames)?.let { + return it + } + } + + return null + } + + private fun Module.getBuildScriptSettingsFile(vararg fileNames: String): File? { + ExternalSystemApiUtil.getExternalProjectPath(this)?.let { externalProjectPath -> + return generateSequence(externalProjectPath) { + PathUtil.getParentPath(it).let { if (it.isBlank()) null else it } + }.mapNotNull { + findBuildGradleFile(it, *fileNames) + }.firstOrNull() + } + + return null + } + + private fun findBuildGradleFile(path: String, vararg fileNames: String): File? = + fileNames.asSequence().map { File(path + "/" + it) }.firstOrNull { it.exists() } + + private fun File.getPsiFile(project: Project) = VfsUtil.findFileByIoFile(this, true)?.let { + PsiManager.getInstance(project).findFile(it) + } + + private fun showErrorMessage(project: Project, message: String?) { + Messages.showErrorDialog( + project, + "<html>Couldn't configure kotlin-gradle plugin automatically.<br/>" + + (if (message != null) message + "<br/>" else "") + + "<br/>See manual installation instructions <a href=\"https://kotlinlang.org/docs/reference/using-gradle.html\">here</a>.</html>", + "Configure Kotlin-Gradle Plugin" + ) + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.as33c4 b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.as33c4 new file mode 100644 index 00000000000..dc0c28f012f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/actions/JavaToKotlinAction.kt.as33c4 @@ -0,0 +1,231 @@ +/* + * 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.idea.actions + +import com.intellij.codeInsight.navigation.NavigationUtil +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.ide.scratch.ScratchFileService +import com.intellij.ide.scratch.ScratchRootType +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.command.CommandProcessor +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.ex.MessagesEx +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileVisitor +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiErrorElement +import com.intellij.psi.PsiJavaFile +import com.intellij.psi.PsiManager +import com.intellij.psi.search.FileTypeIndex +import com.intellij.psi.search.GlobalSearchScopesCore +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices +import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor +import org.jetbrains.kotlin.idea.refactoring.toPsiFile +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.idea.util.application.progressIndicatorNullable +import org.jetbrains.kotlin.idea.util.application.runReadAction +import org.jetbrains.kotlin.j2k.ConverterSettings +import org.jetbrains.kotlin.j2k.JavaToKotlinConverter +import org.jetbrains.kotlin.psi.KtFile +import java.io.File +import java.io.IOException +import java.util.* + +class JavaToKotlinAction : AnAction() { + companion object { + private fun uniqueKotlinFileName(javaFile: VirtualFile): String { + val ioFile = File(javaFile.path.replace('/', File.separatorChar)) + + var i = 0 + while (true) { + val fileName = javaFile.nameWithoutExtension + (if (i > 0) i else "") + ".kt" + if (!ioFile.resolveSibling(fileName).exists()) return fileName + i++ + } + } + + val title = "Convert Java to Kotlin" + + private fun saveResults(javaFiles: List<PsiJavaFile>, convertedTexts: List<String>): List<VirtualFile> { + val result = ArrayList<VirtualFile>() + for ((psiFile, text) in javaFiles.zip(convertedTexts)) { + try { + val document = PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile) + if (document == null) { + MessagesEx.error(psiFile.project, "Failed to save conversion result: couldn't find document for " + psiFile.name).showLater() + continue + } + document.replaceString(0, document.textLength, text) + FileDocumentManager.getInstance().saveDocument(document) + + val virtualFile = psiFile.virtualFile + if (ScratchRootType.getInstance().containsFile(virtualFile)) { + val mapping = ScratchFileService.getInstance().scratchesMapping + mapping.setMapping(virtualFile, KotlinFileType.INSTANCE.language) + } + else { + val fileName = uniqueKotlinFileName(virtualFile) + virtualFile.rename(this, fileName) + } + } + catch (e: IOException) { + MessagesEx.error(psiFile.project, e.message ?: "").showLater() + } + } + return result + } + + fun convertFiles(javaFiles: List<PsiJavaFile>, project: Project, + enableExternalCodeProcessing: Boolean = true, + askExternalCodeProcessing: Boolean = true): List<KtFile> { + var converterResult: JavaToKotlinConverter.FilesResult? = null + fun convert() { + val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaJavaToKotlinServices) + converterResult = converter.filesToKotlin( + javaFiles, J2kPostProcessor(formatCode = true), + ProgressManager.getInstance().progressIndicatorNullable!! + ) + } + + + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( + ::convert, + title, + true, + project)) return emptyList() + + + var externalCodeUpdate: (() -> Unit)? = null + + if (enableExternalCodeProcessing && converterResult!!.externalCodeProcessing != null) { + val question = "Some code in the rest of your project may require corrections after performing this conversion. Do you want to find such code and correct it too?" + if (!askExternalCodeProcessing || (Messages.showOkCancelDialog(project, question, title, Messages.getQuestionIcon()) == Messages.OK)) { + ProgressManager.getInstance().runProcessWithProgressSynchronously( + { + runReadAction { + externalCodeUpdate = converterResult!!.externalCodeProcessing!!.prepareWriteOperation( + ProgressManager.getInstance().progressIndicatorNullable!! + ) + } + }, + title, + true, + project) + } + } + + return project.executeWriteCommand("Convert files from Java to Kotlin", null) { + CommandProcessor.getInstance().markCurrentCommandAsGlobal(project) + + val newFiles = saveResults(javaFiles, converterResult!!.results) + + externalCodeUpdate?.invoke() + + PsiDocumentManager.getInstance(project).commitAllDocuments() + + newFiles.singleOrNull()?.let { + FileEditorManager.getInstance(project).openFile(it, true) + } + + newFiles.map { it.toPsiFile(project) as KtFile } + } + } + } + + override fun actionPerformed(e: AnActionEvent) { + val javaFiles = selectedJavaFiles(e).filter { it.isWritable }.toList() + val project = CommonDataKeys.PROJECT.getData(e.dataContext)!! + + val firstSyntaxError = javaFiles.asSequence().map { PsiTreeUtil.findChildOfType(it, PsiErrorElement::class.java) }.firstOrNull() + + if (firstSyntaxError != null) { + val count = javaFiles.filter { PsiTreeUtil.hasErrorElements(it) }.count() + val question = firstSyntaxError.containingFile.name + + (if (count > 1) " and ${count - 1} more Java files" else " file") + + " contain syntax errors, the conversion result may be incorrect" + + val okText = "Investigate Errors" + val cancelText = "Proceed with Conversion" + if (Messages.showOkCancelDialog( + project, + question, + title, + okText, + cancelText, + Messages.getWarningIcon() + ) == Messages.OK) { + NavigationUtil.activateFileWithPsiElement(firstSyntaxError.navigationElement) + return + } + } + + convertFiles(javaFiles, project) + } + + override fun update(e: AnActionEvent) { + val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return + val project = e.project ?: return + + e.presentation.isEnabled = isAnyJavaFileSelected(project, virtualFiles) + } + + private fun isAnyJavaFileSelected(project: Project, files: Array<VirtualFile>): Boolean { + + val filesScope = GlobalSearchScopesCore.directoriesScope(project, true, *files) + val potentialJavaFiles = FileTypeIndex.getFiles(JavaFileType.INSTANCE, filesScope) + + if (potentialJavaFiles.isEmpty()) return false + + val manager = PsiManager.getInstance(project) + return potentialJavaFiles.any { manager.findFile(it) is PsiJavaFile && it.isWritable } + } + + private fun selectedJavaFiles(e: AnActionEvent): Sequence<PsiJavaFile> { + val virtualFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY) ?: return sequenceOf() + val project = e.project ?: return sequenceOf() + return allJavaFiles(virtualFiles, project) + } + + private fun allJavaFiles(filesOrDirs: Array<VirtualFile>, project: Project): Sequence<PsiJavaFile> { + val manager = PsiManager.getInstance(project) + return allFiles(filesOrDirs) + .asSequence() + .mapNotNull { manager.findFile(it) as? PsiJavaFile } + } + + private fun allFiles(filesOrDirs: Array<VirtualFile>): Collection<VirtualFile> { + val result = ArrayList<VirtualFile>() + for (file in filesOrDirs) { + VfsUtilCore.visitChildrenRecursively(file, object : VirtualFileVisitor<Unit>() { + override fun visitFile(file: VirtualFile): Boolean { + result.add(file) + return true + } + }) + } + return result + } +} |