J2K: updated to use of Kotlin's KClass instead of Java's class in annotation declarations and usages

This commit is contained in:
Valentin Kipyatkov
2015-05-27 19:18:15 +03:00
parent 0a2983d602
commit ce9a986524
27 changed files with 294 additions and 187 deletions
@@ -102,12 +102,6 @@ public abstract class JetCodeFragment(
override fun addImportsFromString(imports: String?) { override fun addImportsFromString(imports: String?) {
if (imports == null || imports.isEmpty()) return if (imports == null || imports.isEmpty()) return
// We should increment modification tracker after inserting import in code fragment to invalidate resolve caches.
// Without this modification references with new import won't be resolved without any modification in code fragment.
// Also shorten references won't work.
(PsiModificationTracker.SERVICE.getInstance(getProject()) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()
myImports.addAll(imports.split(IMPORT_SEPARATOR)) myImports.addAll(imports.split(IMPORT_SEPARATOR))
} }
@@ -47,12 +47,10 @@ public class JavaToKotlinAction : AnAction() {
val javaFiles = selectedJavaFiles(e).toList() val javaFiles = selectedJavaFiles(e).toList()
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!! val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
var converterResult: JavaToKotlinConverter.Result? = null var converterResult: JavaToKotlinConverter.FilesResult? = null
fun convert() { fun convert() {
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter)
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
converterResult = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
} }
val title = "Convert Java to Kotlin" val title = "Convert Java to Kotlin"
@@ -85,7 +83,7 @@ public class JavaToKotlinAction : AnAction() {
project.executeWriteCommand("Convert files from Java to Kotlin") { project.executeWriteCommand("Convert files from Java to Kotlin") {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project) CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
val newFiles = saveResults(javaFiles, converterResult!!.results.map { it!!.text /*conversion of a file always succeeds*/ }) val newFiles = saveResults(javaFiles, converterResult!!.results)
externalCodeUpdate?.invoke() externalCodeUpdate?.invoke()
@@ -145,11 +145,10 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
project, project,
ConverterSettings.defaultSettings, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaReferenceSearcher,
IdeaResolverForConverter, IdeaResolverForConverter
null
) )
val inputElements = elementsAndTexts.filterIsInstance<PsiElement>().map { JavaToKotlinConverter.InputElement(it, null) } val inputElements = elementsAndTexts.filterIsInstance<PsiElement>()
val results = converter.elementsToKotlin(inputElements).results val results = converter.elementsToKotlin(inputElements).results
var resultIndex = 0 var resultIndex = 0
@@ -163,6 +162,8 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
val result = results[resultIndex++] val result = results[resultIndex++]
if (result != null) { if (result != null) {
//TODO: insert imports
convertedCodeBuilder.append(result.text) convertedCodeBuilder.append(result.text)
if (parseContext == null) { // use parse context of the first converted element as parse context for the whole text if (parseContext == null) { // use parse context of the first converted element as parse context for the whole text
parseContext = result.parseContext parseContext = result.parseContext
@@ -32,7 +32,9 @@ import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.I
import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToSafeAccessIntention import org.jetbrains.kotlin.idea.intentions.branchedTransformations.intentions.IfThenToSafeAccessIntention
import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix import org.jetbrains.kotlin.idea.quickfix.RemoveModifierFix
import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.j2k.PostProcessor import org.jetbrains.kotlin.j2k.PostProcessor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
@@ -49,6 +51,11 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
return file.getResolutionFacade().analyzeFullyAndGetResult(elements).bindingContext return file.getResolutionFacade().analyzeFullyAndGetResult(elements).bindingContext
} }
override fun insertImport(file: JetFile, fqName: FqName) {
val descriptors = file.getResolutionFacade().resolveImportReference(file, fqName)
descriptors.firstOrNull()?.let { ImportInsertHelper.getInstance(file.getProject()).importDescriptor(file, it) }
}
override fun fixForProblem(problem: Diagnostic): (() -> Unit)? { override fun fixForProblem(problem: Diagnostic): (() -> Unit)? {
val psiElement = problem.getPsiElement() val psiElement = problem.getPsiElement()
return when (problem.getFactory()) { return when (problem.getFactory()) {
@@ -301,7 +301,7 @@ public fun ChangeInfo.toJetChangeInfo(originalChangeSignatureDescriptor: JetMeth
val defaultValueExpr = if (getLanguage().`is`(JavaLanguage.INSTANCE) && !defaultValueText.isNullOrEmpty()) { val defaultValueExpr = if (getLanguage().`is`(JavaLanguage.INSTANCE) && !defaultValueText.isNullOrEmpty()) {
PsiElementFactory.SERVICE.getInstance(method.getProject()) PsiElementFactory.SERVICE.getInstance(method.getProject())
.createExpressionFromText(defaultValueText!!, null) .createExpressionFromText(defaultValueText!!, null)
.j2k(originalChangeSignatureDescriptor.baseDeclaration) .j2k()
} }
else null else null
@@ -76,7 +76,7 @@ public class KotlinIntroduceParameterMethodUsageProcessor : IntroduceParameterMe
data.getParametersToRemove().toNativeArray().toList().sortDescending().forEach { changeInfo.removeParameter(it) } data.getParametersToRemove().toNativeArray().toList().sortDescending().forEach { changeInfo.removeParameter(it) }
// Temporarily assume that the new parameter is of Any type. Actual type is substituted during the signature update phase // Temporarily assume that the new parameter is of Any type. Actual type is substituted during the signature update phase
val defaultValueForCall = (data.getParameterInitializer().getExpression()!! as? PsiExpression)?.let { it.j2k(it) } val defaultValueForCall = (data.getParameterInitializer().getExpression()!! as? PsiExpression)?.let { it.j2k() }
changeInfo.addParameter(JetParameterInfo(functionDescriptor = psiMethodDescriptor, changeInfo.addParameter(JetParameterInfo(functionDescriptor = psiMethodDescriptor,
name = data.getParameterName(), name = data.getParameterName(),
type = KotlinBuiltIns.getInstance().getAnyType(), type = KotlinBuiltIns.getInstance().getAnyType(),
@@ -558,16 +558,14 @@ fun createJavaClass(klass: JetClass, targetClass: PsiClass): PsiMember {
return javaClass return javaClass
} }
fun PsiExpression.j2k(postProcessingContext: PsiElement): JetExpression? { fun PsiExpression.j2k(): JetExpression? {
if (getLanguage() != JavaLanguage.INSTANCE) return null if (getLanguage() != JavaLanguage.INSTANCE) return null
val project = getProject() val project = getProject()
val j2kConverter = JavaToKotlinConverter(project, val j2kConverter = JavaToKotlinConverter(project,
ConverterSettings.defaultSettings, ConverterSettings.defaultSettings,
IdeaReferenceSearcher, IdeaReferenceSearcher,
IdeaResolverForConverter, IdeaResolverForConverter)
J2kPostProcessor(true)) val text = j2kConverter.elementsToKotlin(listOf(this)).results.single()?.text ?: return null //TODO: insert imports
val inputElements = Collections.singletonList(JavaToKotlinConverter.InputElement(this, postProcessingContext))
val text = j2kConverter.elementsToKotlin(inputElements).results.singleOrNull()?.text ?: return null
return JetPsiFactory(getProject()).createExpression(text) return JetPsiFactory(getProject()).createExpression(text)
} }
@@ -18,41 +18,37 @@ package org.jetbrains.kotlin.util
import com.intellij.codeInsight.CodeInsightSettings import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.actions.OptimizeImportsProcessor import com.intellij.codeInsight.actions.OptimizeImportsProcessor
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.name.* import com.intellij.psi.impl.PsiModificationTrackerImpl
import org.jetbrains.kotlin.psi.* import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.idea.imports.canBeReferencedViaImport
import com.intellij.psi.PsiElement import org.jetbrains.kotlin.idea.imports.getImportableTargets
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.idea.util.ImportInsertHelper
import org.jetbrains.kotlin.idea.util.ImportInsertHelper.ImportDescriptorResult import org.jetbrains.kotlin.idea.util.ImportInsertHelper.ImportDescriptorResult
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported import org.jetbrains.kotlin.name.FqName
import java.util.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.idea.imports.* import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.descriptorUtil.getImportableDescriptor
import org.jetbrains.kotlin.resolve.jvm.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.JetScope
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.utils.addIfNotNull
import java.util.ArrayList
import java.util.Comparator
import java.util.LinkedHashSet
public class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper() { public class ImportInsertHelperImpl(private val project: Project) : ImportInsertHelper() {
@@ -70,6 +66,11 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
} }
private fun addImport(file: JetFile, importPath: ImportPath): JetImportDirective { private fun addImport(file: JetFile, importPath: ImportPath): JetImportDirective {
//TODO: it's a temporary hack for JetCodeFragment's and non-physical files
// We should increment modification tracker after inserting import to invalidate resolve caches.
// Without this modification references with new import won't be resolved.
(PsiModificationTracker.SERVICE.getInstance(project) as PsiModificationTrackerImpl).incOutOfCodeBlockModificationCounter()
val psiFactory = JetPsiFactory(project) val psiFactory = JetPsiFactory(project)
if (file is JetCodeFragment) { if (file is JetCodeFragment) {
val newDirective = psiFactory.createImportDirective(importPath) val newDirective = psiFactory.createImportDirective(importPath)
@@ -39,10 +39,16 @@ public class AfterConversionPass(val project: Project, val postProcessor: PostPr
} }
.filterNotNull() .filterNotNull()
val document = kotlinFile.getViewProvider().getDocument()!! val rangeMarker = if (range != null) {
val rangeMarker = if (range != null) document.createRangeMarker(range.getStartOffset(), range.getEndOffset()) else null val document = kotlinFile.getViewProvider().getDocument()!!
rangeMarker?.setGreedyToLeft(true) val marker = document.createRangeMarker(range.getStartOffset(), range.getEndOffset())
rangeMarker?.setGreedyToRight(true) marker.setGreedyToLeft(true)
marker.setGreedyToRight(true)
marker
}
else {
null
}
for ((psiElement, fix) in fixes) { for ((psiElement, fix) in fixes) {
if (psiElement.isValid()) { if (psiElement.isValid()) {
@@ -121,7 +121,18 @@ class AnnotationConverter(private val converter: Converter) {
private fun convertAttributeValue(value: PsiAnnotationMemberValue?, expectedType: PsiType?, isVararg: Boolean, isUnnamed: Boolean): List<(CodeConverter) -> Expression> { private fun convertAttributeValue(value: PsiAnnotationMemberValue?, expectedType: PsiType?, isVararg: Boolean, isUnnamed: Boolean): List<(CodeConverter) -> Expression> {
when (value) { when (value) {
is PsiExpression -> return listOf({ codeConverter -> codeConverter.convertExpression(value, expectedType).assignPrototype(value) }) is PsiExpression -> {
return listOf({ codeConverter ->
val expression = if (value is PsiClassObjectAccessExpression) {
val typeElement = converter.convertTypeElement(value.getOperand())
ClassLiteralExpression(typeElement.type.toNotNullType())
}
else {
codeConverter.convertExpression(value, expectedType)
}
expression.assignPrototype(value)
})
}
is PsiArrayInitializerMemberValue -> { is PsiArrayInitializerMemberValue -> {
val componentType = (expectedType as? PsiArrayType)?.getComponentType() val componentType = (expectedType as? PsiArrayType)?.getComponentType()
@@ -21,6 +21,7 @@ import com.intellij.psi.*
import com.intellij.psi.javadoc.PsiDocComment import com.intellij.psi.javadoc.PsiDocComment
import org.jetbrains.kotlin.j2k.ast.CommentsAndSpacesInheritance import org.jetbrains.kotlin.j2k.ast.CommentsAndSpacesInheritance
import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.ast.Element
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import java.util.ArrayList import java.util.ArrayList
import java.util.HashSet import java.util.HashSet
@@ -55,9 +56,15 @@ class CodeBuilder(private val topElement: PsiElement?) {
private val commentsAndSpacesUsed = HashSet<PsiElement>() private val commentsAndSpacesUsed = HashSet<PsiElement>()
private val imports = ArrayList<FqName>()
public fun append(text: String): CodeBuilder public fun append(text: String): CodeBuilder
= append(text, false) = append(text, false)
public fun addImport(fqName: FqName) {
imports.add(fqName)
}
private fun appendCommentOrWhiteSpace(element: PsiElement) { private fun appendCommentOrWhiteSpace(element: PsiElement) {
if (element is PsiDocComment) { if (element is PsiDocComment) {
append(DocCommentConverter.convertDocComment(element), false) append(DocCommentConverter.convertDocComment(element), false)
@@ -83,9 +90,12 @@ class CodeBuilder(private val topElement: PsiElement?) {
return this return this
} }
public val result: String public val resultText: String
get() = builder.toString() get() = builder.toString()
public val importsToAdd: Collection<FqName>
get() = imports
public fun append(element: Element): CodeBuilder { public fun append(element: Element): CodeBuilder {
if (element.isEmpty) return this // do not insert comment and spaces for empty elements to avoid multiple blank lines if (element.isEmpty) return this // do not insert comment and spaces for empty elements to avoid multiple blank lines
+17 -11
View File
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.j2k.ast.Object
import org.jetbrains.kotlin.j2k.usageProcessing.FieldToPropertyProcessing import org.jetbrains.kotlin.j2k.usageProcessing.FieldToPropertyProcessing
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessingExpressionConverter import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessingExpressionConverter
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.expressions.OperatorConventions.* import org.jetbrains.kotlin.types.expressions.OperatorConventions.*
import java.util.ArrayList import java.util.ArrayList
import java.util.HashMap import java.util.HashMap
@@ -77,10 +78,15 @@ class Converter private(
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null) private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
public data class IntermediateResult( public data class IntermediateResult(
val codeGenerator: (Map<PsiElement, Collection<UsageProcessing>>) -> String, val codeGenerator: (Map<PsiElement, Collection<UsageProcessing>>) -> Result,
val parseContext: ParseContext val parseContext: ParseContext
) )
public data class Result(
val text: String,
val importsToAdd: Collection<FqName>
)
public fun convert(): IntermediateResult? { public fun convert(): IntermediateResult? {
val element = convertTopElement(elementToConvert) ?: return null val element = convertTopElement(elementToConvert) ?: return null
val parseContext = when (elementToConvert) { val parseContext = when (elementToConvert) {
@@ -93,7 +99,7 @@ class Converter private(
val builder = CodeBuilder(elementToConvert) val builder = CodeBuilder(elementToConvert)
builder.append(element) builder.append(element)
builder.result Result(builder.resultText, builder.importsToAdd)
}, },
parseContext) parseContext)
} }
@@ -253,24 +259,24 @@ class Converter private(
annotationConverter.convertAnnotationMethodDefault(method)).assignPrototype(method, noBlankLinesInheritance) annotationConverter.convertAnnotationMethodDefault(method)).assignPrototype(method, noBlankLinesInheritance)
} }
fun convertType(psiType: PsiType?): Type {
return typeConverter.convertType(psiType, Nullability.NotNull, inAnnotationType = true)
}
val parameters = val parameters =
// Argument named `value` comes first if it exists // Argument named `value` comes first if it exists
// Convert it as vararg if it's array // Convert it as vararg if it's array
methodsNamedValue. methodsNamedValue.map { method ->
map { method ->
val returnType = method.getReturnType() val returnType = method.getReturnType()
val typeConverted = if (returnType is PsiArrayType) val typeConverted = if (returnType is PsiArrayType)
VarArgType(typeConverter.convertType(returnType.getComponentType(), Nullability.NotNull)) VarArgType(convertType(returnType.getComponentType()))
else else
typeConverter.convertType(returnType, Nullability.NotNull) convertType(returnType)
createParameter(typeConverted, method) createParameter(typeConverted, method)
} + } +
otherMethods otherMethods.map { method -> createParameter(convertType(method.getReturnType()), method) }
.map { method ->
val typeConverted = typeConverter.convertType(method.getReturnType(), Nullability.NotNull)
createParameter(typeConverted, method)
}
val parameterList = ParameterList(parameters).assignNoPrototype() val parameterList = ParameterList(parameters).assignNoPrototype()
val constructorSignature = if (parameterList.parameters.isNotEmpty()) val constructorSignature = if (parameterList.parameters.isNotEmpty())
PrimaryConstructorSignature(Annotations.Empty, Modifiers.Empty, parameterList).assignNoPrototype() PrimaryConstructorSignature(Annotations.Empty, Modifiers.Empty, parameterList).assignNoPrototype()
@@ -26,16 +26,14 @@ import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement import com.intellij.psi.*
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.source.DummyHolder import com.intellij.psi.impl.source.DummyHolder
import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.idea.JetLanguage import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.j2k.ast.Element import org.jetbrains.kotlin.j2k.ast.Element
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.isAncestor
import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.parents
@@ -45,9 +43,11 @@ import java.util.Comparator
import java.util.LinkedHashMap import java.util.LinkedHashMap
import kotlin.properties.Delegates import kotlin.properties.Delegates
public trait PostProcessor { public interface PostProcessor {
public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext
public fun insertImport(file: JetFile, fqName: FqName)
public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? { public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? {
val psiElement = problem.getPsiElement() val psiElement = problem.getPsiElement()
return when (problem.getFactory()) { return when (problem.getFactory()) {
@@ -77,30 +77,57 @@ public enum class ParseContext {
CODE_BLOCK CODE_BLOCK
} }
public class JavaToKotlinConverter(private val project: Project, public class JavaToKotlinConverter(
private val settings: ConverterSettings, private val project: Project,
private val referenceSearcher: ReferenceSearcher, private val settings: ConverterSettings,
private val resolverForConverter: ResolverForConverter, private val referenceSearcher: ReferenceSearcher,
private val postProcessor: PostProcessor?) { private val resolverForConverter: ResolverForConverter
) {
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter") private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter")
public data class InputElement( public interface ExternalCodeProcessing {
val element: PsiElement,
val postProcessingContext: PsiElement?
)
public data class ElementResult(val text: String, val parseContext: ParseContext)
public trait ExternalCodeProcessing {
public fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit public fun prepareWriteOperation(progress: ProgressIndicator): () -> Unit
} }
public data class ElementResult(val text: String, val importsToAdd: Collection<FqName>, val parseContext: ParseContext)
public data class Result(val results: List<ElementResult?>, val externalCodeProcessing: ExternalCodeProcessing?) public data class Result(val results: List<ElementResult?>, val externalCodeProcessing: ExternalCodeProcessing?)
public fun elementsToKotlin( public data class FilesResult(val results: List<String>, val externalCodeProcessing: ExternalCodeProcessing?)
inputElements: List<InputElement>,
progress: ProgressIndicator = EmptyProgressIndicator() public fun filesToKotlin(files: List<PsiJavaFile>, postProcessor: PostProcessor, progress: ProgressIndicator = EmptyProgressIndicator()): FilesResult {
): Result { val withProgressProcessor = WithProgressProcessor(progress, files)
val (results, externalCodeProcessing) = elementsToKotlin(files, withProgressProcessor)
val texts = withProgressProcessor.processItems(0.5, results.withIndex()) { pair ->
val (i, result) = pair
try {
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result!!.text, files[i])
result.importsToAdd.forEach { postProcessor.insertImport(kotlinFile, it) }
AfterConversionPass(project, postProcessor).run(kotlinFile, range = null)
kotlinFile.getText()
}
catch(e: ProcessCanceledException) {
throw e
}
catch(t: Throwable) {
LOG.error(t)
result!!.text
}
}
return FilesResult(texts, externalCodeProcessing)
}
public fun elementsToKotlin(inputElements: List<PsiElement>): Result {
return elementsToKotlin(inputElements, WithProgressProcessor.DEFAULT)
}
private fun elementsToKotlin(inputElements: List<PsiElement>, processor: WithProgressProcessor): Result {
try { try {
val usageProcessings = LinkedHashMap<PsiElement, MutableCollection<UsageProcessing>>() val usageProcessings = LinkedHashMap<PsiElement, MutableCollection<UsageProcessing>>()
val usageProcessingCollector: (UsageProcessing) -> Unit = { val usageProcessingCollector: (UsageProcessing) -> Unit = {
@@ -108,87 +135,25 @@ public class JavaToKotlinConverter(private val project: Project,
} }
fun inConversionScope(element: PsiElement) fun inConversionScope(element: PsiElement)
= inputElements.any { it.element.isAncestor(element, strict = false) } = inputElements.any { it.isAncestor(element, strict = false) }
val progressText = "Converting Java to Kotlin"
val elementCount = inputElements.size()
val fileCountText = elementCount.toString() + " " + if (elementCount > 1) "files" else "file"
var fraction = 0.0
var pass = 1
fun processFilesWithProgress<TInputItem, TOutputItem>( val intermediateResults = processor.processItems(0.25, inputElements) { inputElement ->
fractionPortion: Double, Converter.create(inputElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector).convert()
inputItems: Iterable<TInputItem>,
processItem: (TInputItem) -> TOutputItem
): List<TOutputItem> {
val outputItems = ArrayList<TOutputItem>(elementCount)
// we use special process with EmptyProgressIndicator to avoid changing text in our progress by inheritors search inside etc
ProgressManager.getInstance().runProcess(
{
progress.setText("$progressText ($fileCountText) - pass $pass of 3")
for ((i, item) in inputItems.withIndex()) {
progress.checkCanceled()
progress.setFraction(fraction + fractionPortion * i / elementCount)
val psiFile = inputElements[i].element as? PsiFile
if (psiFile != null) {
progress.setText2(psiFile.getVirtualFile().getPresentableUrl())
}
outputItems.add(processItem(item))
}
pass++
fraction += fractionPortion
},
EmptyProgressIndicator())
return outputItems
}
val intermediateResults = processFilesWithProgress(0.25, inputElements) { inputElement ->
Converter.create(inputElement.element, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector).convert()
}.toArrayList() }.toArrayList()
val results = processFilesWithProgress(0.25, intermediateResults.withIndex()) { pair -> val results = processor.processItems(0.25, intermediateResults.withIndex()) { pair ->
val (i, result) = pair val (i, result) = pair
intermediateResults[i] = null // to not hold unused objects in the heap intermediateResults[i] = null // to not hold unused objects in the heap
if (result != null) result?.let {
ElementResult(result.codeGenerator(usageProcessings), result.parseContext) val (text, importsToAdd) = it.codeGenerator(usageProcessings)
else ElementResult(text, importsToAdd, it.parseContext)
null }
} }
val externalCodeProcessing = buildExternalCodeProcessing(usageProcessings, ::inConversionScope) val externalCodeProcessing = buildExternalCodeProcessing(usageProcessings, ::inConversionScope)
if (postProcessor == null) { return Result(results, externalCodeProcessing)
assert(progress is EmptyProgressIndicator, "Progress indicator not supported for postProcessor == null")
return Result(results, externalCodeProcessing)
}
val finalResults = processFilesWithProgress(0.5, results.withIndex()) { pair ->
val (i, result) = pair
if (result != null) {
try {
//TODO: post processing does not work correctly for ParseContext different from TOP_LEVEL
val kotlinFile = JetPsiFactory(project).createAnalyzableFile("dummy.kt", result.text, inputElements[i].postProcessingContext!!)
AfterConversionPass(project, postProcessor).run(kotlinFile, range = null)
ElementResult(kotlinFile.getText(), result.parseContext)
}
catch(e: ProcessCanceledException) {
throw e
}
catch(t: Throwable) {
LOG.error(t)
result
}
}
else {
null
}
}
return Result(finalResults, externalCodeProcessing)
} }
catch(e: ElementCreationStackTraceRequiredException) { catch(e: ElementCreationStackTraceRequiredException) {
// if we got this exception then we need to turn element creation stack traces on to get better diagnostic // if we got this exception then we need to turn element creation stack traces on to get better diagnostic
@@ -202,7 +167,7 @@ public class JavaToKotlinConverter(private val project: Project,
} }
} }
data class ReferenceInfo( private data class ReferenceInfo(
val reference: PsiReference, val reference: PsiReference,
val target: PsiElement, val target: PsiElement,
val file: PsiFile, val file: PsiFile,
@@ -295,6 +260,45 @@ public class JavaToKotlinConverter(private val project: Project,
} }
} }
private class WithProgressProcessor(private val progress: ProgressIndicator?, private val files: List<PsiJavaFile>?) {
public companion object {
val DEFAULT = WithProgressProcessor(null, null)
}
private val progressText = "Converting Java to Kotlin"
private val fileCount = files?.size() ?: 0
private val fileCountText = fileCount.toString() + " " + if (fileCount > 1) "files" else "file"
private var fraction = 0.0
private var pass = 1
fun processItems<TInputItem, TOutputItem>(
fractionPortion: Double,
inputItems: Iterable<TInputItem>,
processItem: (TInputItem) -> TOutputItem
): List<TOutputItem> {
val outputItems = ArrayList<TOutputItem>()
// we use special process with EmptyProgressIndicator to avoid changing text in our progress by inheritors search inside etc
ProgressManager.getInstance().runProcess(
{
progress?.setText("$progressText ($fileCountText) - pass $pass of 3")
for ((i, item) in inputItems.withIndex()) {
progress?.checkCanceled()
progress?.setFraction(fraction + fractionPortion * i / fileCount)
progress?.setText2(files!![i].getVirtualFile().getPresentableUrl())
outputItems.add(processItem(item))
}
pass++
fraction += fractionPortion
},
EmptyProgressIndicator())
return outputItems
}
}
private class ProgressPortionReporter( private class ProgressPortionReporter(
indicator: ProgressIndicator, indicator: ProgressIndicator,
private val start: Double, private val start: Double,
@@ -70,8 +70,8 @@ public object JavaToKotlinTranslator {
public fun generateKotlinCode(javaCode: String, project: Project): String { public fun generateKotlinCode(javaCode: String, project: Project): String {
val file = createFile(javaCode, project) val file = createFile(javaCode, project)
if (file is PsiJavaFile) { if (file is PsiJavaFile) {
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null) val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter)
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).results.single()!!.text) return prettify(converter.elementsToKotlin(listOf(file)).results.single()!!.text) //TODO: imports
} }
return "" return ""
} }
@@ -38,10 +38,15 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.CallableDescriptor
class TypeConverter(val converter: Converter) { class TypeConverter(val converter: Converter) {
public fun convertType(type: PsiType?, nullability: Nullability = Nullability.Default, mutability: Mutability = Mutability.Default): Type { public fun convertType(
type: PsiType?,
nullability: Nullability = Nullability.Default,
mutability: Mutability = Mutability.Default,
inAnnotationType: Boolean = false
): Type {
if (type == null) return ErrorType().assignNoPrototype() if (type == null) return ErrorType().assignNoPrototype()
val result = type.accept<Type>(TypeVisitor(converter, type, mutability))!!.assignNoPrototype() val result = type.accept<Type>(TypeVisitor(converter, type, mutability, inAnnotationType))!!.assignNoPrototype()
return when (nullability) { return when (nullability) {
Nullability.NotNull -> result.toNotNullType() Nullability.NotNull -> result.toNotNullType()
Nullability.Nullable -> result.toNullableType() Nullability.Nullable -> result.toNullableType()
@@ -16,10 +16,11 @@
package org.jetbrains.kotlin.j2k package org.jetbrains.kotlin.j2k
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.impl.source.PsiClassReferenceType import com.intellij.psi.impl.source.PsiClassReferenceType
import org.jetbrains.kotlin.j2k.ast.* import org.jetbrains.kotlin.j2k.ast.*
import com.intellij.openapi.util.text.StringUtil import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
private val PRIMITIVE_TYPES_NAMES = JvmPrimitiveType.values().map { it.getName() } private val PRIMITIVE_TYPES_NAMES = JvmPrimitiveType.values().map { it.getName() }
@@ -27,7 +28,8 @@ private val PRIMITIVE_TYPES_NAMES = JvmPrimitiveType.values().map { it.getName()
class TypeVisitor( class TypeVisitor(
private val converter: Converter, private val converter: Converter,
private val topLevelType: PsiType, private val topLevelType: PsiType,
private val topLevelTypeMutability: Mutability private val topLevelTypeMutability: Mutability,
private val inAnnotationType: Boolean
) : PsiTypeVisitor<Type>() { ) : PsiTypeVisitor<Type>() {
private val typeConverter: TypeConverter = converter.typeConverter private val typeConverter: TypeConverter = converter.typeConverter
@@ -49,7 +51,7 @@ class TypeVisitor(
} }
override fun visitArrayType(arrayType: PsiArrayType): Type { override fun visitArrayType(arrayType: PsiArrayType): Type {
return ArrayType(typeConverter.convertType(arrayType.getComponentType()), Nullability.Default, converter.settings) return ArrayType(typeConverter.convertType(arrayType.getComponentType(), inAnnotationType = inAnnotationType), Nullability.Default, converter.settings)
} }
override fun visitClassType(classType: PsiClassType): Type { override fun visitClassType(classType: PsiClassType): Type {
@@ -69,6 +71,12 @@ class TypeVisitor(
if (kotlinClassName != null) { if (kotlinClassName != null) {
return ReferenceElement(Identifier(getShortName(kotlinClassName)).assignNoPrototype(), typeArgs).assignNoPrototype() return ReferenceElement(Identifier(getShortName(kotlinClassName)).assignNoPrototype(), typeArgs).assignNoPrototype()
} }
if (inAnnotationType && javaClassName == "java.lang.Class") {
val fqName = FqName("kotlin.reflect.KClass")
val identifier = Identifier(fqName.shortName().getIdentifier(), imports = listOf(fqName)).assignNoPrototype()
return ReferenceElement(identifier, typeArgs).assignNoPrototype()
}
} }
if (classType is PsiClassReferenceType) { if (classType is PsiClassReferenceType) {
@@ -78,8 +86,7 @@ class TypeVisitor(
return ReferenceElement(Identifier(classType.getClassName() ?: "").assignNoPrototype(), typeArgs).assignNoPrototype() return ReferenceElement(Identifier(classType.getClassName() ?: "").assignNoPrototype(), typeArgs).assignNoPrototype()
} }
private fun getPackageName(className: String): String = className.substring(0, className.lastIndexOf('.')) private fun getShortName(className: String): String = className.substringAfterLast('.', className)
private fun getShortName(className: String): String = className.substring(className.lastIndexOf('.') + 1)
private fun convertTypeArgs(classType: PsiClassType): List<Type> { private fun convertTypeArgs(classType: PsiClassType): List<Type> {
if (classType.getParameterCount() == 0) { if (classType.getParameterCount() == 0) {
@@ -112,7 +119,7 @@ class TypeVisitor(
} }
override fun visitEllipsisType(ellipsisType: PsiEllipsisType): Type { override fun visitEllipsisType(ellipsisType: PsiEllipsisType): Type {
return VarArgType(typeConverter.convertType(ellipsisType.getComponentType())) return VarArgType(typeConverter.convertType(ellipsisType.getComponentType(), inAnnotationType = inAnnotationType))
} }
companion object { companion object {
@@ -53,7 +53,7 @@ data class CommentsAndSpacesInheritance(val blankLinesBefore: Boolean = true,
fun Element.canonicalCode(): String { fun Element.canonicalCode(): String {
val builder = CodeBuilder(null) val builder = CodeBuilder(null)
builder.append(this) builder.append(this)
return builder.result return builder.resultText
} }
abstract class Element { abstract class Element {
@@ -16,10 +16,11 @@
package org.jetbrains.kotlin.j2k.ast package org.jetbrains.kotlin.j2k.ast
import org.jetbrains.kotlin.j2k.CodeBuilder
import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.PsiNameIdentifierOwner
import org.jetbrains.kotlin.j2k.CodeBuilder
import org.jetbrains.kotlin.lexer.JetKeywordToken import org.jetbrains.kotlin.lexer.JetKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.FqName
fun PsiNameIdentifierOwner.declarationIdentifier(): Identifier { fun PsiNameIdentifierOwner.declarationIdentifier(): Identifier {
val name = getName() val name = getName()
@@ -29,7 +30,8 @@ fun PsiNameIdentifierOwner.declarationIdentifier(): Identifier {
class Identifier( class Identifier(
val name: String, val name: String,
override val isNullable: Boolean = true, override val isNullable: Boolean = true,
private val quotingNeeded: Boolean = true private val quotingNeeded: Boolean = true,
private val imports: Collection<FqName> = emptyList()
) : Expression() { ) : Expression() {
override val isEmpty: Boolean override val isEmpty: Boolean
@@ -45,6 +47,8 @@ class Identifier(
override fun generateCode(builder: CodeBuilder) { override fun generateCode(builder: CodeBuilder) {
builder.append(toKotlin()) builder.append(toKotlin())
imports.forEach { builder.addImport(it) }
} }
private fun quote(str: String): String = "`" + str + "`" private fun quote(str: String): String = "`" + str + "`"
@@ -6,8 +6,8 @@ Anon3(e = E.A, stringArray = array(), value = *array("a", "b"))
Anon4("x", "y") Anon4("x", "y")
Anon5(1) Anon5(1)
Anon6("x", "y") Anon6("x", "y")
Anon7(javaClass<String>(), javaClass<StringBuilder>()) Anon7(String::class, StringBuilder::class)
Anon8(classes = array(javaClass<String>(), javaClass<StringBuilder>())) Anon8(classes = array(String::class, StringBuilder::class))
class C { class C {
Anon5(1) deprecated("") private val field1 = 0 Anon5(1) deprecated("") private val field1 = 0
@@ -0,0 +1,8 @@
@interface Ann {
Class<?> value();
Class<?> other();
}
@Ann(other = String.class, value = Object.class)
class C {
}
@@ -0,0 +1,6 @@
import kotlin.reflect.KClass
annotation class Ann(public val value: KClass<*>, public val other: KClass<*>)
Ann(other = String::class, value = Any::class)
class C
@@ -0,0 +1,11 @@
@interface Ann {
Class<?>[] value();
}
@Ann({String.class, Object.class})
class C {
}
@Ann({})
class D {
}
@@ -0,0 +1,9 @@
import kotlin.reflect.KClass
annotation class Ann(public vararg val value: KClass<*>)
Ann(String::class, Any::class)
class C
Ann
class D
@@ -54,10 +54,8 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
assert(psiFile is PsiJavaFile || psiFile is JetFile) assert(psiFile is PsiJavaFile || psiFile is JetFile)
} }
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter)
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true)) val (results, externalCodeProcessor) = converter.filesToKotlin(psiFilesToConvert, J2kPostProcessor(formatCode = true))
val inputElements = psiFilesToConvert.map { JavaToKotlinConverter.InputElement(it, it) }
val (results, externalCodeProcessor) = converter.elementsToKotlin(inputElements)
val process = externalCodeProcessor?.prepareWriteOperation(EmptyProgressIndicator()) val process = externalCodeProcessor?.prepareWriteOperation(EmptyProgressIndicator())
project.executeWriteCommand("") { process?.invoke() } project.executeWriteCommand("") { process?.invoke() }
@@ -67,7 +65,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
val resultFiles = ArrayList<JetFile>() val resultFiles = ArrayList<JetFile>()
for ((i, javaFile) in psiFilesToConvert.withIndex()) { for ((i, javaFile) in psiFilesToConvert.withIndex()) {
deleteFile(javaFile.getVirtualFile()) deleteFile(javaFile.getVirtualFile())
val virtualFile = addFile(results.map { it!!.text }[i], expectedResultFile(i).getName(), "test") val virtualFile = addFile(results[i], expectedResultFile(i).getName(), "test")
resultFiles.add(psiManager.findFile(virtualFile) as JetFile) resultFiles.add(psiManager.findFile(virtualFile) as JetFile)
} }
@@ -108,9 +108,8 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
val file = createJavaFile(text) val file = createJavaFile(text)
val converter = JavaToKotlinConverter(project, settings, val converter = JavaToKotlinConverter(project, settings,
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false)) IdeaReferenceSearcher, IdeaResolverForConverter)
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file)) return converter.filesToKotlin(listOf(file), J2kPostProcessor(formatCode = true)).results.single()
return converter.elementsToKotlin(inputElements).results.single()!!.text
} }
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String { private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
@@ -73,6 +73,18 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
doTest(fileName); doTest(fileName);
} }
@TestMetadata("javaClassArgument.java")
public void testJavaClassArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/javaClassArgument.java");
doTest(fileName);
}
@TestMetadata("javaClassArrayArgument.java")
public void testJavaClassArrayArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/javaClassArrayArgument.java");
doTest(fileName);
}
@TestMetadata("jetbrainsNotNull.java") @TestMetadata("jetbrainsNotNull.java")
public void testJetbrainsNotNull() throws Exception { public void testJetbrainsNotNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java");
@@ -73,6 +73,18 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
doTest(fileName); doTest(fileName);
} }
@TestMetadata("javaClassArgument.java")
public void testJavaClassArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/javaClassArgument.java");
doTest(fileName);
}
@TestMetadata("javaClassArrayArgument.java")
public void testJavaClassArrayArgument() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/javaClassArrayArgument.java");
doTest(fileName);
}
@TestMetadata("jetbrainsNotNull.java") @TestMetadata("jetbrainsNotNull.java")
public void testJetbrainsNotNull() throws Exception { public void testJetbrainsNotNull() throws Exception {
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java"); String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java");