Java to Kotlin converter: no usage search and body analysis for members out of conversion scope

This commit is contained in:
Valentin Kipyatkov
2014-06-10 14:34:37 +04:00
parent d6755b91ee
commit 324040a02f
7 changed files with 67 additions and 67 deletions
@@ -26,9 +26,11 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.j2k.Converter; import org.jetbrains.jet.j2k.Converter;
import org.jetbrains.jet.j2k.ConverterSettings; import org.jetbrains.jet.j2k.ConverterSettings;
import org.jetbrains.jet.j2k.FilesConversionScope;
import java.util.List; import java.util.List;
@@ -43,7 +45,7 @@ public class JavaToKotlinAction extends AnAction {
assert virtualFiles != null; assert virtualFiles != null;
final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext()); final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
assert project != null; assert project != null;
final List<PsiFile> selectedJavaFiles = getAllJavaFiles(virtualFiles, project); final List<PsiJavaFile> selectedJavaFiles = getAllJavaFiles(virtualFiles, project);
if (selectedJavaFiles.isEmpty()) { if (selectedJavaFiles.isEmpty()) {
return; return;
} }
@@ -77,8 +79,8 @@ public class JavaToKotlinAction extends AnAction {
} }
@NotNull @NotNull
private static Converter prepareConverter(@NotNull Project project, @NotNull List<PsiFile> selectedJavaFiles) { private static Converter prepareConverter(@NotNull Project project, @NotNull List<PsiJavaFile> selectedJavaFiles) {
Converter converter = new Converter(project, ConverterSettings.defaultSettings); Converter converter = new Converter(project, ConverterSettings.defaultSettings, new FilesConversionScope(selectedJavaFiles));
converter.clearClassIdentifiers(); converter.clearClassIdentifiers();
for (PsiFile f : selectedJavaFiles) { for (PsiFile f : selectedJavaFiles) {
if (f.getFileType() instanceof JavaFileType) { if (f.getFileType() instanceof JavaFileType) {
@@ -16,7 +16,6 @@
package org.jetbrains.jet.plugin.actions; package org.jetbrains.jet.plugin.actions;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ex.MessagesEx; import com.intellij.openapi.ui.ex.MessagesEx;
@@ -53,14 +52,14 @@ public class JavaToKotlinActionUtil {
} }
@NotNull @NotNull
/*package*/ static List<PsiFile> getAllJavaFiles(@NotNull VirtualFile[] vFiles, Project project) { /*package*/ static List<PsiJavaFile> getAllJavaFiles(@NotNull VirtualFile[] vFiles, Project project) {
Set<VirtualFile> filesSet = allVirtualFiles(vFiles); Set<VirtualFile> filesSet = allVirtualFiles(vFiles);
PsiManager manager = PsiManager.getInstance(project); PsiManager manager = PsiManager.getInstance(project);
List<PsiFile> res = new ArrayList<PsiFile>(); List<PsiJavaFile> res = new ArrayList<PsiJavaFile>();
for (VirtualFile file : filesSet) { for (VirtualFile file : filesSet) {
PsiFile psiFile = manager.findFile(file); PsiFile psiFile = manager.findFile(file);
if (psiFile != null && psiFile.getFileType() instanceof JavaFileType) { if (psiFile != null && psiFile instanceof PsiJavaFile) {
res.add(psiFile); res.add((PsiJavaFile)psiFile);
} }
} }
return res; return res;
@@ -92,7 +91,7 @@ public class JavaToKotlinActionUtil {
} }
@NotNull @NotNull
static List<VirtualFile> convertFiles(final Converter converter, List<PsiFile> allJavaFilesNear) { static List<VirtualFile> convertFiles(final Converter converter, List<PsiJavaFile> allJavaFilesNear) {
final List<VirtualFile> result = new LinkedList<VirtualFile>(); final List<VirtualFile> result = new LinkedList<VirtualFile>();
for (final PsiFile f : allJavaFilesNear) { for (final PsiFile f : allJavaFilesNear) {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -108,7 +107,7 @@ public class JavaToKotlinActionUtil {
return result; return result;
} }
static void deleteFiles(List<PsiFile> allJavaFilesNear) { static void deleteFiles(List<PsiJavaFile> allJavaFilesNear) {
for (final PsiFile f : allJavaFilesNear) { for (final PsiFile f : allJavaFilesNear) {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
@@ -150,7 +149,7 @@ public class JavaToKotlinActionUtil {
return null; return null;
} }
static void renameFiles(@NotNull List<PsiFile> psiFiles) { static void renameFiles(@NotNull List<PsiJavaFile> psiFiles) {
for (final PsiFile f : psiFiles) { for (final PsiFile f : psiFiles) {
ApplicationManager.getApplication().runWriteAction(new Runnable() { ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override @Override
@@ -48,44 +48,38 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
} }
public override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): TextBlockTransferableData? { public override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): TextBlockTransferableData? {
if (!(file is PsiJavaFile)) { if (file !is PsiJavaFile) return null
return null
}
val lightFile = PsiFileFactory.getInstance(file.getProject())!!.createFileFromText(file.getText()!!, file) val lightFile = PsiFileFactory.getInstance(file.getProject())!!.createFileFromText(file.getText()!!, file)
return CopiedCode(lightFile as? PsiJavaFile, startOffsets, endOffsets) return CopiedCode(lightFile as? PsiJavaFile, startOffsets, endOffsets)
} }
public override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, value: TextBlockTransferableData) { public override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, value: TextBlockTransferableData) {
if (value !is CopiedCode) if (value !is CopiedCode) return
return val sourceFile = value.getFile() ?: return
if (value.getFile() == null) val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument())
return if (targetFile !is JetFile) return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument())
if (file !is JetFile)
return
val jetEditorOptions = JetEditorOptions.getInstance()!! val jetEditorOptions = JetEditorOptions.getInstance()!!
val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project)) val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project))
if (needConvert) { if (needConvert) {
val text = convertCopiedCodeToKotlin(value, file.getProject()) val text = convertCopiedCodeToKotlin(value, sourceFile)
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
ApplicationManager.getApplication()!!.runWriteAction { ApplicationManager.getApplication()!!.runWriteAction {
val startOffset = bounds.getStartOffset() val startOffset = bounds.getStartOffset()
editor.getDocument().replaceString(bounds.getStartOffset(), bounds.getEndOffset(), text) editor.getDocument().replaceString(bounds.getStartOffset(), bounds.getEndOffset(), text)
val endOffsetAfterCopy = startOffset + text.length() val endOffsetAfterCopy = startOffset + text.length()
editor.getCaretModel().moveToOffset(endOffsetAfterCopy) editor.getCaretModel().moveToOffset(endOffsetAfterCopy)
CodeStyleManager.getInstance(project)!!.reformatText(file, startOffset, endOffsetAfterCopy) CodeStyleManager.getInstance(project)!!.reformatText(targetFile, startOffset, endOffsetAfterCopy)
PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument()) PsiDocumentManager.getInstance(targetFile.getProject()).commitDocument(editor.getDocument())
} }
} }
} }
} }
private fun convertCopiedCodeToKotlin(code: CopiedCode, project: Project): String { private fun convertCopiedCodeToKotlin(code: CopiedCode, file: PsiJavaFile): String {
val converter = Converter(project, ConverterSettings.defaultSettings) val converter = Converter(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)))
val startOffsets = code.getStartOffsets() val startOffsets = code.getStartOffsets()
val endOffsets = code.getEndOffsets() val endOffsets = code.getEndOffsets()
assert(startOffsets.size == endOffsets.size) { "Must have the same size" } assert(startOffsets.size == endOffsets.size) { "Must have the same size" }
@@ -93,7 +87,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
for (i in startOffsets.indices) { for (i in startOffsets.indices) {
val startOffset = startOffsets[i] val startOffset = startOffsets[i]
val endOffset = endOffsets[i] val endOffset = endOffsets[i]
result.append(convertRangeToKotlin(code.getFile()!!, TextRange(startOffset, endOffset), converter)) result.append(convertRangeToKotlin(file, TextRange(startOffset, endOffset), converter))
} }
return StringUtil.convertLineSeparators(result.toString()) return StringUtil.convertLineSeparators(result.toString())
} }
+10 -2
View File
@@ -25,9 +25,17 @@ import org.jetbrains.jet.lang.types.expressions.OperatorConventions.*
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.util.PsiUtil import com.intellij.psi.util.PsiUtil
public class Converter(val project: Project, val settings: ConverterSettings) { public trait ConversionScope {
public fun contains(element: PsiElement): Boolean
}
private val typeConverter = TypeConverter(settings) public class FilesConversionScope(val files: Collection<PsiJavaFile>) : ConversionScope {
override fun contains(element: PsiElement) = files.any { element.getContainingFile() == it }
}
public class Converter(val project: Project, val settings: ConverterSettings, val conversionScope: ConversionScope) {
private val typeConverter = TypeConverter(settings, conversionScope)
private var classIdentifiersSet: MutableSet<String> = HashSet() private var classIdentifiersSet: MutableSet<String> = HashSet()
@@ -19,7 +19,6 @@ package org.jetbrains.jet.j2k
import com.intellij.core.JavaCoreApplicationEnvironment import com.intellij.core.JavaCoreApplicationEnvironment
import com.intellij.core.JavaCoreProjectEnvironment import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.lang.java.JavaLanguage import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
@@ -40,8 +39,8 @@ public object JavaToKotlinTranslator {
return PsiFileFactory.getInstance(javaCoreEnvironment?.getProject())?.createFileFromText("test.java", JavaLanguage.INSTANCE, text) return PsiFileFactory.getInstance(javaCoreEnvironment?.getProject())?.createFileFromText("test.java", JavaLanguage.INSTANCE, text)
} }
fun createFile(project: Project, text: String): PsiFile? { fun createFile(project: Project, text: String): PsiJavaFile {
return PsiFileFactory.getInstance(project)?.createFileFromText("test.java", JavaLanguage.INSTANCE, text) return PsiFileFactory.getInstance(project)?.createFileFromText("test.java", JavaLanguage.INSTANCE, text) as PsiJavaFile
} }
fun setUpJavaCoreEnvironment(): JavaCoreProjectEnvironment { fun setUpJavaCoreEnvironment(): JavaCoreProjectEnvironment {
@@ -98,7 +97,7 @@ public object JavaToKotlinTranslator {
fun generateKotlinCode(javaCode: String): String { fun generateKotlinCode(javaCode: String): String {
val file = createFile(javaCode) val file = createFile(javaCode)
if (file is PsiJavaFile) { if (file is PsiJavaFile) {
val converter = Converter(file.getProject(), ConverterSettings.defaultSettings) val converter = Converter(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)))
setClassIdentifiers(converter, file) setClassIdentifiers(converter, file)
return prettify(converter.convertFile(file).toKotlin()) return prettify(converter.convertFile(file).toKotlin())
} }
@@ -22,7 +22,7 @@ import com.intellij.psi.*
import org.jetbrains.jet.j2k.visitors.TypeVisitor import org.jetbrains.jet.j2k.visitors.TypeVisitor
import java.util.HashMap import java.util.HashMap
class TypeConverter(val settings: ConverterSettings) { class TypeConverter(val settings: ConverterSettings, val conversionScope: ConversionScope) {
private val nullabilityCache = HashMap<PsiElement, Nullability>() private val nullabilityCache = HashMap<PsiElement, Nullability>()
public fun convertType(`type`: PsiType?, nullability: Nullability = Nullability.Default): Type { public fun convertType(`type`: PsiType?, nullability: Nullability = Nullability.Default): Type {
@@ -68,6 +68,8 @@ class TypeConverter(val settings: ConverterSettings) {
} }
} }
if (!conversionScope.contains(variable)) return nullability // do not analyze usages of fields out of our conversion scope
if (nullability == Nullability.Default) { if (nullability == Nullability.Default) {
val scope = searchScope(variable) val scope = searchScope(variable)
if (scope != null) { if (scope != null) {
@@ -114,6 +116,8 @@ class TypeConverter(val settings: ConverterSettings) {
private fun methodNullabilityNoCache(method: PsiMethod): Nullability { private fun methodNullabilityNoCache(method: PsiMethod): Nullability {
var nullability = method.nullabilityFromAnnotations() var nullability = method.nullabilityFromAnnotations()
if (!conversionScope.contains(method)) return nullability // do not analyze body and usages of methods out of our conversion scope
if (nullability == Nullability.Default) { if (nullability == Nullability.Default) {
method.getBody()?.accept(object: JavaRecursiveElementVisitor() { method.getBody()?.accept(object: JavaRecursiveElementVisitor() {
override fun visitReturnStatement(statement: PsiReturnStatement) { override fun visitReturnStatement(statement: PsiReturnStatement) {
@@ -196,7 +200,6 @@ class TypeConverter(val settings: ConverterSettings) {
return when(this) { return when(this) {
is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod()) is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod())
is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false
is PsiParameter -> true
else -> false else -> false
} }
} }
@@ -33,6 +33,7 @@ import org.jetbrains.jet.JetTestUtils
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.j2k.FilesConversionScope
abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() { abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() {
val testHeaderPattern = Pattern.compile("//(element|expression|statement|method|class|file|comp)\n") val testHeaderPattern = Pattern.compile("//(element|expression|statement|method|class|file|comp)\n")
@@ -74,15 +75,14 @@ abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() {
else -> throw IllegalArgumentException("Unknown option: $name") else -> throw IllegalArgumentException("Unknown option: $name")
} }
} }
val converter = Converter(project, settings)
val rawConverted = when (prefix) { val rawConverted = when (prefix) {
"element" -> elementToKotlin(converter, javaCode) "element" -> elementToKotlin(javaCode, settings, project)
"expression" -> expressionToKotlin(converter, javaCode) "expression" -> expressionToKotlin(javaCode, settings, project)
"statement" -> statementToKotlin(converter, javaCode) "statement" -> statementToKotlin(javaCode, settings, project)
"method" -> methodToKotlin(converter, javaCode) "method" -> methodToKotlin(javaCode, settings, project)
"class" -> fileToKotlin(converter, javaCode) "class" -> fileToKotlin(javaCode, settings, project)
"file" -> fileToKotlin(converter, javaCode) "file" -> fileToKotlin(javaCode, settings, project)
else -> throw IllegalStateException("Specify what is it: file, class, method, statement or expression " + else -> throw IllegalStateException("Specify what is it: file, class, method, statement or expression " +
"using the first line of test data file") "using the first line of test data file")
} }
@@ -114,42 +114,37 @@ abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() {
reformattedText reformattedText
} }
private fun elementToKotlin(converter: Converter, text: String): String { private fun elementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val fileWithText = JavaToKotlinTranslator.createFile(converter.project, text)!! val fileWithText = JavaToKotlinTranslator.createFile(project, text)
val converter = Converter(project, settings, FilesConversionScope(listOf(fileWithText)))
val element = fileWithText.getFirstChild()!! val element = fileWithText.getFirstChild()!!
return converter.elementToKotlin(element) return converter.elementToKotlin(element)
} }
private fun fileToKotlin(converter: Converter, text: String): String { private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
return generateKotlinCode(converter, JavaToKotlinTranslator.createFile(converter.project, text)) val file = JavaToKotlinTranslator.createFile(project, text)
val converter = Converter(project, settings, FilesConversionScope(listOf(file)))
return generateKotlinCode(converter, file)
} }
private fun methodToKotlin(converter: Converter, text: String?): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
var result = fileToKotlin(converter, "final class C {" + text + "}").replaceAll("class C\\(\\) \\{", "") val result = fileToKotlin("final class C {" + text + "}", settings, project).replaceAll("class C\\(\\) \\{", "")
result = result.substring(0, (result.lastIndexOf("}"))).trim() return result.substring(0, (result.lastIndexOf("}"))).trim()
return result
} }
private fun statementToKotlin(converter: Converter, text: String?): String { private fun statementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
var result = methodToKotlin(converter, "void main() {" + text + "}") val result = methodToKotlin("void main() {" + text + "}", settings, project)
val pos = result.lastIndexOf("}") return result.substring(0, result.lastIndexOf("}")).replaceFirst("fun main\\(\\) \\{", "").trim()
result = result.substring(0, pos).replaceFirst("fun main\\(\\) \\{", "").trim()
return result
} }
private fun expressionToKotlin(converter: Converter, code: String?): String { private fun expressionToKotlin(code: String, settings: ConverterSettings, project: Project): String {
var result = statementToKotlin(converter, "final Object o =" + code + "}") val result = statementToKotlin("final Object o =" + code + "}", settings, project)
result = result.replaceFirst("val o : Any\\? =", "").replaceFirst("val o : Any = ", "").replaceFirst("val o = ", "").trim() return result.replaceFirst("val o : Any\\? =", "").replaceFirst("val o : Any = ", "").replaceFirst("val o = ", "").trim()
return result
} }
private fun generateKotlinCode(converter: Converter, file: PsiFile?): String { private fun generateKotlinCode(converter: Converter, file: PsiJavaFile): String {
if (file is PsiJavaFile) { JavaToKotlinTranslator.setClassIdentifiers(converter, file)
JavaToKotlinTranslator.setClassIdentifiers(converter, file) return converter.elementToKotlin(file)
return converter.elementToKotlin(file)
}
return ""
} }
override fun getProjectJDK(): Sdk? { override fun getProjectJDK(): Sdk? {