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.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.j2k.Converter;
import org.jetbrains.jet.j2k.ConverterSettings;
import org.jetbrains.jet.j2k.FilesConversionScope;
import java.util.List;
@@ -43,7 +45,7 @@ public class JavaToKotlinAction extends AnAction {
assert virtualFiles != null;
final Project project = CommonDataKeys.PROJECT.getData(e.getDataContext());
assert project != null;
final List<PsiFile> selectedJavaFiles = getAllJavaFiles(virtualFiles, project);
final List<PsiJavaFile> selectedJavaFiles = getAllJavaFiles(virtualFiles, project);
if (selectedJavaFiles.isEmpty()) {
return;
}
@@ -77,8 +79,8 @@ public class JavaToKotlinAction extends AnAction {
}
@NotNull
private static Converter prepareConverter(@NotNull Project project, @NotNull List<PsiFile> selectedJavaFiles) {
Converter converter = new Converter(project, ConverterSettings.defaultSettings);
private static Converter prepareConverter(@NotNull Project project, @NotNull List<PsiJavaFile> selectedJavaFiles) {
Converter converter = new Converter(project, ConverterSettings.defaultSettings, new FilesConversionScope(selectedJavaFiles));
converter.clearClassIdentifiers();
for (PsiFile f : selectedJavaFiles) {
if (f.getFileType() instanceof JavaFileType) {
@@ -16,7 +16,6 @@
package org.jetbrains.jet.plugin.actions;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ex.MessagesEx;
@@ -53,14 +52,14 @@ public class JavaToKotlinActionUtil {
}
@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);
PsiManager manager = PsiManager.getInstance(project);
List<PsiFile> res = new ArrayList<PsiFile>();
List<PsiJavaFile> res = new ArrayList<PsiJavaFile>();
for (VirtualFile file : filesSet) {
PsiFile psiFile = manager.findFile(file);
if (psiFile != null && psiFile.getFileType() instanceof JavaFileType) {
res.add(psiFile);
if (psiFile != null && psiFile instanceof PsiJavaFile) {
res.add((PsiJavaFile)psiFile);
}
}
return res;
@@ -92,7 +91,7 @@ public class JavaToKotlinActionUtil {
}
@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>();
for (final PsiFile f : allJavaFilesNear) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -108,7 +107,7 @@ public class JavaToKotlinActionUtil {
return result;
}
static void deleteFiles(List<PsiFile> allJavaFilesNear) {
static void deleteFiles(List<PsiJavaFile> allJavaFilesNear) {
for (final PsiFile f : allJavaFilesNear) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
@@ -150,7 +149,7 @@ public class JavaToKotlinActionUtil {
return null;
}
static void renameFiles(@NotNull List<PsiFile> psiFiles) {
static void renameFiles(@NotNull List<PsiJavaFile> psiFiles) {
for (final PsiFile f : psiFiles) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
@@ -48,44 +48,38 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
}
public override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): TextBlockTransferableData? {
if (!(file is PsiJavaFile)) {
return null
}
if (file !is PsiJavaFile) return null
val lightFile = PsiFileFactory.getInstance(file.getProject())!!.createFileFromText(file.getText()!!, file)
return CopiedCode(lightFile as? PsiJavaFile, startOffsets, endOffsets)
}
public override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, value: TextBlockTransferableData) {
if (value !is CopiedCode)
return
if (value !is CopiedCode) return
val sourceFile = value.getFile() ?: return
if (value.getFile() == null)
return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument())
if (file !is JetFile)
return
val targetFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument())
if (targetFile !is JetFile) return
val jetEditorOptions = JetEditorOptions.getInstance()!!
val needConvert = jetEditorOptions.isEnableJavaToKotlinConversion() && (jetEditorOptions.isDonTShowConversionDialog() || okFromDialog(project))
if (needConvert) {
val text = convertCopiedCodeToKotlin(value, file.getProject())
val text = convertCopiedCodeToKotlin(value, sourceFile)
if (text.isNotEmpty()) {
ApplicationManager.getApplication()!!.runWriteAction {
val startOffset = bounds.getStartOffset()
editor.getDocument().replaceString(bounds.getStartOffset(), bounds.getEndOffset(), text)
val endOffsetAfterCopy = startOffset + text.length()
editor.getCaretModel().moveToOffset(endOffsetAfterCopy)
CodeStyleManager.getInstance(project)!!.reformatText(file, startOffset, endOffsetAfterCopy)
PsiDocumentManager.getInstance(file.getProject()).commitDocument(editor.getDocument())
CodeStyleManager.getInstance(project)!!.reformatText(targetFile, startOffset, endOffsetAfterCopy)
PsiDocumentManager.getInstance(targetFile.getProject()).commitDocument(editor.getDocument())
}
}
}
}
private fun convertCopiedCodeToKotlin(code: CopiedCode, project: Project): String {
val converter = Converter(project, ConverterSettings.defaultSettings)
private fun convertCopiedCodeToKotlin(code: CopiedCode, file: PsiJavaFile): String {
val converter = Converter(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)))
val startOffsets = code.getStartOffsets()
val endOffsets = code.getEndOffsets()
assert(startOffsets.size == endOffsets.size) { "Must have the same size" }
@@ -93,7 +87,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
for (i in startOffsets.indices) {
val startOffset = startOffsets[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())
}
+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.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()
@@ -19,7 +19,6 @@ package org.jetbrains.jet.j2k
import com.intellij.core.JavaCoreApplicationEnvironment
import com.intellij.core.JavaCoreProjectEnvironment
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiElement
@@ -40,8 +39,8 @@ public object JavaToKotlinTranslator {
return PsiFileFactory.getInstance(javaCoreEnvironment?.getProject())?.createFileFromText("test.java", JavaLanguage.INSTANCE, text)
}
fun createFile(project: Project, text: String): PsiFile? {
return PsiFileFactory.getInstance(project)?.createFileFromText("test.java", JavaLanguage.INSTANCE, text)
fun createFile(project: Project, text: String): PsiJavaFile {
return PsiFileFactory.getInstance(project)?.createFileFromText("test.java", JavaLanguage.INSTANCE, text) as PsiJavaFile
}
fun setUpJavaCoreEnvironment(): JavaCoreProjectEnvironment {
@@ -98,7 +97,7 @@ public object JavaToKotlinTranslator {
fun generateKotlinCode(javaCode: String): String {
val file = createFile(javaCode)
if (file is PsiJavaFile) {
val converter = Converter(file.getProject(), ConverterSettings.defaultSettings)
val converter = Converter(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)))
setClassIdentifiers(converter, file)
return prettify(converter.convertFile(file).toKotlin())
}
@@ -22,7 +22,7 @@ import com.intellij.psi.*
import org.jetbrains.jet.j2k.visitors.TypeVisitor
import java.util.HashMap
class TypeConverter(val settings: ConverterSettings) {
class TypeConverter(val settings: ConverterSettings, val conversionScope: ConversionScope) {
private val nullabilityCache = HashMap<PsiElement, Nullability>()
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) {
val scope = searchScope(variable)
if (scope != null) {
@@ -114,6 +116,8 @@ class TypeConverter(val settings: ConverterSettings) {
private fun methodNullabilityNoCache(method: PsiMethod): Nullability {
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) {
method.getBody()?.accept(object: JavaRecursiveElementVisitor() {
override fun visitReturnStatement(statement: PsiReturnStatement) {
@@ -196,7 +200,6 @@ class TypeConverter(val settings: ConverterSettings) {
return when(this) {
is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod())
is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false
is PsiParameter -> true
else -> false
}
}
@@ -33,6 +33,7 @@ import org.jetbrains.jet.JetTestUtils
import com.intellij.openapi.project.Project
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.j2k.FilesConversionScope
abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() {
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")
}
}
val converter = Converter(project, settings)
val rawConverted = when (prefix) {
"element" -> elementToKotlin(converter, javaCode)
"expression" -> expressionToKotlin(converter, javaCode)
"statement" -> statementToKotlin(converter, javaCode)
"method" -> methodToKotlin(converter, javaCode)
"class" -> fileToKotlin(converter, javaCode)
"file" -> fileToKotlin(converter, javaCode)
"element" -> elementToKotlin(javaCode, settings, project)
"expression" -> expressionToKotlin(javaCode, settings, project)
"statement" -> statementToKotlin(javaCode, settings, project)
"method" -> methodToKotlin(javaCode, settings, project)
"class" -> fileToKotlin(javaCode, settings, project)
"file" -> fileToKotlin(javaCode, settings, project)
else -> throw IllegalStateException("Specify what is it: file, class, method, statement or expression " +
"using the first line of test data file")
}
@@ -114,42 +114,37 @@ abstract class AbstractJavaToKotlinConverterTest() : LightIdeaTestCase() {
reformattedText
}
private fun elementToKotlin(converter: Converter, text: String): String {
val fileWithText = JavaToKotlinTranslator.createFile(converter.project, text)!!
private fun elementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val fileWithText = JavaToKotlinTranslator.createFile(project, text)
val converter = Converter(project, settings, FilesConversionScope(listOf(fileWithText)))
val element = fileWithText.getFirstChild()!!
return converter.elementToKotlin(element)
}
private fun fileToKotlin(converter: Converter, text: String): String {
return generateKotlinCode(converter, JavaToKotlinTranslator.createFile(converter.project, text))
private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
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 {
var result = fileToKotlin(converter, "final class C {" + text + "}").replaceAll("class C\\(\\) \\{", "")
result = result.substring(0, (result.lastIndexOf("}"))).trim()
return result
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val result = fileToKotlin("final class C {" + text + "}", settings, project).replaceAll("class C\\(\\) \\{", "")
return result.substring(0, (result.lastIndexOf("}"))).trim()
}
private fun statementToKotlin(converter: Converter, text: String?): String {
var result = methodToKotlin(converter, "void main() {" + text + "}")
val pos = result.lastIndexOf("}")
result = result.substring(0, pos).replaceFirst("fun main\\(\\) \\{", "").trim()
return result
private fun statementToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val result = methodToKotlin("void main() {" + text + "}", settings, project)
return result.substring(0, result.lastIndexOf("}")).replaceFirst("fun main\\(\\) \\{", "").trim()
}
private fun expressionToKotlin(converter: Converter, code: String?): String {
var result = statementToKotlin(converter, "final Object o =" + code + "}")
result = result.replaceFirst("val o : Any\\? =", "").replaceFirst("val o : Any = ", "").replaceFirst("val o = ", "").trim()
return result
private fun expressionToKotlin(code: String, settings: ConverterSettings, project: Project): String {
val result = statementToKotlin("final Object o =" + code + "}", settings, project)
return result.replaceFirst("val o : Any\\? =", "").replaceFirst("val o : Any = ", "").replaceFirst("val o = ", "").trim()
}
private fun generateKotlinCode(converter: Converter, file: PsiFile?): String {
if (file is PsiJavaFile) {
JavaToKotlinTranslator.setClassIdentifiers(converter, file)
return converter.elementToKotlin(file)
}
return ""
private fun generateKotlinCode(converter: Converter, file: PsiJavaFile): String {
JavaToKotlinTranslator.setClassIdentifiers(converter, file)
return converter.elementToKotlin(file)
}
override fun getProjectJDK(): Sdk? {