as31: Implement ConvertJavaToKotlinProvider
(cherry picked from commit 34dfa24)
This commit is contained in:
committed by
Nikolay Krasko
parent
38b38511a0
commit
abce7170be
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.android.tools.idea.npw.template.ConvertJavaToKotlinProvider
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import com.intellij.psi.PsiJavaFile
|
||||||
|
import org.jetbrains.kotlin.android.configure.KotlinAndroidGradleModuleConfigurator
|
||||||
|
import org.jetbrains.kotlin.idea.actions.JavaToKotlinAction
|
||||||
|
import org.jetbrains.kotlin.idea.configuration.ConfigureKotlinStatus
|
||||||
|
import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator
|
||||||
|
import org.jetbrains.kotlin.idea.configuration.excludeSourceRootModules
|
||||||
|
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
|
||||||
|
import org.jetbrains.kotlin.idea.versions.bundledRuntimeVersion
|
||||||
|
|
||||||
|
class ConvertJavaToKotlinProviderImpl : ConvertJavaToKotlinProvider {
|
||||||
|
override fun configureKotlin(project: Project) {
|
||||||
|
val configurator = KotlinProjectConfigurator.EP_NAME.findExtension(KotlinAndroidGradleModuleConfigurator::class.java)
|
||||||
|
val nonConfiguredModules = project.allModules().excludeSourceRootModules().filter {
|
||||||
|
configurator.getStatus(it) == ConfigureKotlinStatus.CAN_BE_CONFIGURED
|
||||||
|
}
|
||||||
|
configurator.configureSilently(project, nonConfiguredModules, bundledRuntimeVersion())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getKotlinVersion(): String {
|
||||||
|
return bundledRuntimeVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun convertToKotlin(project: Project, files: List<PsiJavaFile>): List<PsiFile> {
|
||||||
|
return JavaToKotlinAction.convertFiles(files, project, askExternalCodeProcessing = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
+375
@@ -0,0 +1,375 @@
|
|||||||
|
/*
|
||||||
|
* 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 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"
|
||||||
|
|
||||||
|
private fun isFileConfigured(buildScript: PsiFile): Boolean = getManipulator(buildScript).isConfigured(kotlinPluginName)
|
||||||
|
|
||||||
|
@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,
|
||||||
|
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 open fun addElementsToFile(
|
||||||
|
file: PsiFile,
|
||||||
|
isTopLevelProjectFile: Boolean,
|
||||||
|
version: String
|
||||||
|
): Boolean {
|
||||||
|
if (!isTopLevelProjectFile) {
|
||||||
|
var wasModified = configureProjectFile(file, 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): GradleBuildScriptManipulator = when (file) {
|
||||||
|
is KtFile -> KotlinBuildScriptManipulator(file)
|
||||||
|
is GroovyFile -> GroovyBuildScriptManipulator(file)
|
||||||
|
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"
|
||||||
|
|
||||||
|
fun getGroovyDependencySnippet(artifactName: String, scope: String) =
|
||||||
|
"$scope \"org.jetbrains.kotlin:$artifactName:\$kotlin_version\""
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun configureProjectFile(file: PsiFile, version: String): Boolean = getManipulator(file).configureProjectBuildScript(version)
|
||||||
|
|
||||||
|
private fun canConfigureFile(file: PsiFile): Boolean = WritingAccessProvider.isPotentiallyWritable(file.virtualFile, null)
|
||||||
|
|
||||||
|
private fun Module.getBuildScriptPsiFile() = getBuildScriptFile()?.getPsiFile(project)
|
||||||
|
|
||||||
|
private fun Project.getTopLevelBuildScriptPsiFile() = basePath?.let { findBuildGradleFile(it)?.getPsiFile(this) }
|
||||||
|
|
||||||
|
private fun Module.getBuildScriptFile(): File? {
|
||||||
|
val moduleDir = File(moduleFilePath).parent
|
||||||
|
findBuildGradleFile(moduleDir)?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
|
||||||
|
ModuleRootManager.getInstance(this).contentRoots.forEach { root ->
|
||||||
|
findBuildGradleFile(root.path)?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalSystemApiUtil.getExternalProjectPath(this)?.let { externalProjectPath ->
|
||||||
|
findBuildGradleFile(externalProjectPath)?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findBuildGradleFile(path: String): File? =
|
||||||
|
File(path + "/" + GradleConstants.DEFAULT_SCRIPT_NAME).takeIf { it.exists() }
|
||||||
|
?: File(path + "/" + KOTLIN_BUILD_SCRIPT_NAME).takeIf { 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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,6 +102,10 @@
|
|||||||
<highlighterExtension implementation="org.jetbrains.kotlin.android.AndroidHighlighterExtension"/>
|
<highlighterExtension implementation="org.jetbrains.kotlin.android.AndroidHighlighterExtension"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
|
<extensions defaultExtensionNs="com.android.tools.idea">
|
||||||
|
<npw.template.convertJavaToKotlinProvider implementation="org.jetbrains.kotlin.android.ConvertJavaToKotlinProviderImpl"/>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.android">
|
<extensions defaultExtensionNs="org.jetbrains.android">
|
||||||
<androidLintQuickFixProvider implementation="org.jetbrains.kotlin.android.quickfix.KotlinAndroidQuickFixProvider" />
|
<androidLintQuickFixProvider implementation="org.jetbrains.kotlin.android.quickfix.KotlinAndroidQuickFixProvider" />
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.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.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().progressIndicator)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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().progressIndicator)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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 manager = PsiManager.getInstance(project)
|
||||||
|
|
||||||
|
if (files.any { manager.findFile(it) is PsiJavaFile && it.isWritable }) return true
|
||||||
|
return files.any { it.isDirectory && isAnyJavaFileSelected(project, it.children) }
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user