J2K: updated to use of Kotlin's KClass instead of Java's class in annotation declarations and usages
This commit is contained in:
@@ -102,12 +102,6 @@ public abstract class JetCodeFragment(
|
||||
|
||||
override fun addImportsFromString(imports: String?) {
|
||||
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))
|
||||
}
|
||||
|
||||
|
||||
@@ -47,12 +47,10 @@ public class JavaToKotlinAction : AnAction() {
|
||||
val javaFiles = selectedJavaFiles(e).toList()
|
||||
val project = CommonDataKeys.PROJECT.getData(e.getDataContext())!!
|
||||
|
||||
var converterResult: JavaToKotlinConverter.Result? = null
|
||||
var converterResult: JavaToKotlinConverter.FilesResult? = null
|
||||
fun convert() {
|
||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
|
||||
val inputElements = javaFiles.map { JavaToKotlinConverter.InputElement(it, it) }
|
||||
converterResult = converter.elementsToKotlin(inputElements, ProgressManager.getInstance().getProgressIndicator())
|
||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter)
|
||||
converterResult = converter.filesToKotlin(javaFiles, J2kPostProcessor(formatCode = true), ProgressManager.getInstance().getProgressIndicator())
|
||||
}
|
||||
|
||||
val title = "Convert Java to Kotlin"
|
||||
@@ -85,7 +83,7 @@ public class JavaToKotlinAction : AnAction() {
|
||||
project.executeWriteCommand("Convert files from Java to Kotlin") {
|
||||
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()
|
||||
|
||||
|
||||
+4
-3
@@ -145,11 +145,10 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
project,
|
||||
ConverterSettings.defaultSettings,
|
||||
IdeaReferenceSearcher,
|
||||
IdeaResolverForConverter,
|
||||
null
|
||||
IdeaResolverForConverter
|
||||
)
|
||||
|
||||
val inputElements = elementsAndTexts.filterIsInstance<PsiElement>().map { JavaToKotlinConverter.InputElement(it, null) }
|
||||
val inputElements = elementsAndTexts.filterIsInstance<PsiElement>()
|
||||
val results = converter.elementsToKotlin(inputElements).results
|
||||
|
||||
var resultIndex = 0
|
||||
@@ -163,6 +162,8 @@ public class ConvertJavaCopyPastePostProcessor : CopyPastePostProcessor<TextBloc
|
||||
|
||||
val result = results[resultIndex++]
|
||||
if (result != null) {
|
||||
//TODO: insert imports
|
||||
|
||||
convertedCodeBuilder.append(result.text)
|
||||
if (parseContext == null) { // use parse context of the first converted element as parse context for the whole text
|
||||
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.quickfix.RemoveModifierFix
|
||||
import org.jetbrains.kotlin.idea.quickfix.RemoveRightPartOfBinaryExpressionFix
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.j2k.PostProcessor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.elementsInRange
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
@@ -49,6 +51,11 @@ public class J2kPostProcessor(private val formatCode: Boolean) : PostProcessor {
|
||||
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)? {
|
||||
val psiElement = problem.getPsiElement()
|
||||
return when (problem.getFactory()) {
|
||||
|
||||
@@ -301,7 +301,7 @@ public fun ChangeInfo.toJetChangeInfo(originalChangeSignatureDescriptor: JetMeth
|
||||
val defaultValueExpr = if (getLanguage().`is`(JavaLanguage.INSTANCE) && !defaultValueText.isNullOrEmpty()) {
|
||||
PsiElementFactory.SERVICE.getInstance(method.getProject())
|
||||
.createExpressionFromText(defaultValueText!!, null)
|
||||
.j2k(originalChangeSignatureDescriptor.baseDeclaration)
|
||||
.j2k()
|
||||
}
|
||||
else null
|
||||
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ public class KotlinIntroduceParameterMethodUsageProcessor : IntroduceParameterMe
|
||||
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
|
||||
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,
|
||||
name = data.getParameterName(),
|
||||
type = KotlinBuiltIns.getInstance().getAnyType(),
|
||||
|
||||
@@ -558,16 +558,14 @@ fun createJavaClass(klass: JetClass, targetClass: PsiClass): PsiMember {
|
||||
return javaClass
|
||||
}
|
||||
|
||||
fun PsiExpression.j2k(postProcessingContext: PsiElement): JetExpression? {
|
||||
fun PsiExpression.j2k(): JetExpression? {
|
||||
if (getLanguage() != JavaLanguage.INSTANCE) return null
|
||||
|
||||
val project = getProject()
|
||||
val j2kConverter = JavaToKotlinConverter(project,
|
||||
ConverterSettings.defaultSettings,
|
||||
IdeaReferenceSearcher,
|
||||
IdeaResolverForConverter,
|
||||
J2kPostProcessor(true))
|
||||
val inputElements = Collections.singletonList(JavaToKotlinConverter.InputElement(this, postProcessingContext))
|
||||
val text = j2kConverter.elementsToKotlin(inputElements).results.singleOrNull()?.text ?: return null
|
||||
IdeaResolverForConverter)
|
||||
val text = j2kConverter.elementsToKotlin(listOf(this)).results.single()?.text ?: return null //TODO: insert imports
|
||||
return JetPsiFactory(getProject()).createExpression(text)
|
||||
}
|
||||
@@ -18,41 +18,37 @@ package org.jetbrains.kotlin.util
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightSettings
|
||||
import com.intellij.codeInsight.actions.OptimizeImportsProcessor
|
||||
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
|
||||
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
|
||||
import org.jetbrains.kotlin.name.*
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
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 com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.impl.PsiModificationTrackerImpl
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.core.formatter.JetCodeStyleSettings
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
|
||||
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.imports.canBeReferencedViaImport
|
||||
import org.jetbrains.kotlin.idea.imports.getImportableTargets
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqName
|
||||
import org.jetbrains.kotlin.idea.imports.importableFqNameSafe
|
||||
import org.jetbrains.kotlin.idea.project.ProjectStructureUtil
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper
|
||||
import org.jetbrains.kotlin.idea.util.ImportInsertHelper.ImportDescriptorResult
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.refactoring.fqName.isImported
|
||||
import java.util.*
|
||||
import org.jetbrains.kotlin.idea.imports.*
|
||||
import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
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() {
|
||||
|
||||
@@ -70,6 +66,11 @@ public class ImportInsertHelperImpl(private val project: Project) : ImportInsert
|
||||
}
|
||||
|
||||
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)
|
||||
if (file is JetCodeFragment) {
|
||||
val newDirective = psiFactory.createImportDirective(importPath)
|
||||
|
||||
@@ -39,10 +39,16 @@ public class AfterConversionPass(val project: Project, val postProcessor: PostPr
|
||||
}
|
||||
.filterNotNull()
|
||||
|
||||
val document = kotlinFile.getViewProvider().getDocument()!!
|
||||
val rangeMarker = if (range != null) document.createRangeMarker(range.getStartOffset(), range.getEndOffset()) else null
|
||||
rangeMarker?.setGreedyToLeft(true)
|
||||
rangeMarker?.setGreedyToRight(true)
|
||||
val rangeMarker = if (range != null) {
|
||||
val document = kotlinFile.getViewProvider().getDocument()!!
|
||||
val marker = document.createRangeMarker(range.getStartOffset(), range.getEndOffset())
|
||||
marker.setGreedyToLeft(true)
|
||||
marker.setGreedyToRight(true)
|
||||
marker
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
|
||||
for ((psiElement, fix) in fixes) {
|
||||
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> {
|
||||
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 -> {
|
||||
val componentType = (expectedType as? PsiArrayType)?.getComponentType()
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.intellij.psi.*
|
||||
import com.intellij.psi.javadoc.PsiDocComment
|
||||
import org.jetbrains.kotlin.j2k.ast.CommentsAndSpacesInheritance
|
||||
import org.jetbrains.kotlin.j2k.ast.Element
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import java.util.ArrayList
|
||||
import java.util.HashSet
|
||||
@@ -55,9 +56,15 @@ class CodeBuilder(private val topElement: PsiElement?) {
|
||||
|
||||
private val commentsAndSpacesUsed = HashSet<PsiElement>()
|
||||
|
||||
private val imports = ArrayList<FqName>()
|
||||
|
||||
public fun append(text: String): CodeBuilder
|
||||
= append(text, false)
|
||||
|
||||
public fun addImport(fqName: FqName) {
|
||||
imports.add(fqName)
|
||||
}
|
||||
|
||||
private fun appendCommentOrWhiteSpace(element: PsiElement) {
|
||||
if (element is PsiDocComment) {
|
||||
append(DocCommentConverter.convertDocComment(element), false)
|
||||
@@ -83,9 +90,12 @@ class CodeBuilder(private val topElement: PsiElement?) {
|
||||
return this
|
||||
}
|
||||
|
||||
public val result: String
|
||||
public val resultText: String
|
||||
get() = builder.toString()
|
||||
|
||||
public val importsToAdd: Collection<FqName>
|
||||
get() = imports
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.j2k.ast.Object
|
||||
import org.jetbrains.kotlin.j2k.usageProcessing.FieldToPropertyProcessing
|
||||
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing
|
||||
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessingExpressionConverter
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.types.expressions.OperatorConventions.*
|
||||
import java.util.ArrayList
|
||||
import java.util.HashMap
|
||||
@@ -77,10 +78,15 @@ class Converter private(
|
||||
private fun createDefaultCodeConverter() = CodeConverter(this, DefaultExpressionConverter(), DefaultStatementConverter(), null)
|
||||
|
||||
public data class IntermediateResult(
|
||||
val codeGenerator: (Map<PsiElement, Collection<UsageProcessing>>) -> String,
|
||||
val codeGenerator: (Map<PsiElement, Collection<UsageProcessing>>) -> Result,
|
||||
val parseContext: ParseContext
|
||||
)
|
||||
|
||||
public data class Result(
|
||||
val text: String,
|
||||
val importsToAdd: Collection<FqName>
|
||||
)
|
||||
|
||||
public fun convert(): IntermediateResult? {
|
||||
val element = convertTopElement(elementToConvert) ?: return null
|
||||
val parseContext = when (elementToConvert) {
|
||||
@@ -93,7 +99,7 @@ class Converter private(
|
||||
|
||||
val builder = CodeBuilder(elementToConvert)
|
||||
builder.append(element)
|
||||
builder.result
|
||||
Result(builder.resultText, builder.importsToAdd)
|
||||
},
|
||||
parseContext)
|
||||
}
|
||||
@@ -253,24 +259,24 @@ class Converter private(
|
||||
annotationConverter.convertAnnotationMethodDefault(method)).assignPrototype(method, noBlankLinesInheritance)
|
||||
}
|
||||
|
||||
fun convertType(psiType: PsiType?): Type {
|
||||
return typeConverter.convertType(psiType, Nullability.NotNull, inAnnotationType = true)
|
||||
}
|
||||
|
||||
val parameters =
|
||||
// Argument named `value` comes first if it exists
|
||||
// Convert it as vararg if it's array
|
||||
methodsNamedValue.
|
||||
map { method ->
|
||||
methodsNamedValue.map { method ->
|
||||
val returnType = method.getReturnType()
|
||||
val typeConverted = if (returnType is PsiArrayType)
|
||||
VarArgType(typeConverter.convertType(returnType.getComponentType(), Nullability.NotNull))
|
||||
VarArgType(convertType(returnType.getComponentType()))
|
||||
else
|
||||
typeConverter.convertType(returnType, Nullability.NotNull)
|
||||
convertType(returnType)
|
||||
|
||||
createParameter(typeConverted, method)
|
||||
} +
|
||||
otherMethods
|
||||
.map { method ->
|
||||
val typeConverted = typeConverter.convertType(method.getReturnType(), Nullability.NotNull)
|
||||
createParameter(typeConverted, method)
|
||||
}
|
||||
otherMethods.map { method -> createParameter(convertType(method.getReturnType()), method) }
|
||||
|
||||
val parameterList = ParameterList(parameters).assignNoPrototype()
|
||||
val constructorSignature = if (parameterList.parameters.isNotEmpty())
|
||||
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.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.DummyHolder
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.JetLanguage
|
||||
import org.jetbrains.kotlin.j2k.ast.Element
|
||||
import org.jetbrains.kotlin.j2k.usageProcessing.UsageProcessing
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
@@ -45,9 +43,11 @@ import java.util.Comparator
|
||||
import java.util.LinkedHashMap
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
public trait PostProcessor {
|
||||
public interface PostProcessor {
|
||||
public fun analyzeFile(file: JetFile, range: TextRange?): BindingContext
|
||||
|
||||
public fun insertImport(file: JetFile, fqName: FqName)
|
||||
|
||||
public open fun fixForProblem(problem: Diagnostic): (() -> Unit)? {
|
||||
val psiElement = problem.getPsiElement()
|
||||
return when (problem.getFactory()) {
|
||||
@@ -77,30 +77,57 @@ public enum class ParseContext {
|
||||
CODE_BLOCK
|
||||
}
|
||||
|
||||
public class JavaToKotlinConverter(private val project: Project,
|
||||
private val settings: ConverterSettings,
|
||||
private val referenceSearcher: ReferenceSearcher,
|
||||
private val resolverForConverter: ResolverForConverter,
|
||||
private val postProcessor: PostProcessor?) {
|
||||
public class JavaToKotlinConverter(
|
||||
private val project: Project,
|
||||
private val settings: ConverterSettings,
|
||||
private val referenceSearcher: ReferenceSearcher,
|
||||
private val resolverForConverter: ResolverForConverter
|
||||
) {
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.j2k.JavaToKotlinConverter")
|
||||
|
||||
public data class InputElement(
|
||||
val element: PsiElement,
|
||||
val postProcessingContext: PsiElement?
|
||||
)
|
||||
|
||||
public data class ElementResult(val text: String, val parseContext: ParseContext)
|
||||
|
||||
public trait ExternalCodeProcessing {
|
||||
public interface ExternalCodeProcessing {
|
||||
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 fun elementsToKotlin(
|
||||
inputElements: List<InputElement>,
|
||||
progress: ProgressIndicator = EmptyProgressIndicator()
|
||||
): Result {
|
||||
public data class FilesResult(val results: List<String>, val externalCodeProcessing: ExternalCodeProcessing?)
|
||||
|
||||
public fun filesToKotlin(files: List<PsiJavaFile>, postProcessor: PostProcessor, progress: ProgressIndicator = EmptyProgressIndicator()): FilesResult {
|
||||
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 {
|
||||
val usageProcessings = LinkedHashMap<PsiElement, MutableCollection<UsageProcessing>>()
|
||||
val usageProcessingCollector: (UsageProcessing) -> Unit = {
|
||||
@@ -108,87 +135,25 @@ public class JavaToKotlinConverter(private val project: Project,
|
||||
}
|
||||
|
||||
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>(
|
||||
fractionPortion: Double,
|
||||
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()
|
||||
val intermediateResults = processor.processItems(0.25, inputElements) { inputElement ->
|
||||
Converter.create(inputElement, settings, ::inConversionScope, referenceSearcher, resolverForConverter, usageProcessingCollector).convert()
|
||||
}.toArrayList()
|
||||
|
||||
val results = processFilesWithProgress(0.25, intermediateResults.withIndex()) { pair ->
|
||||
val results = processor.processItems(0.25, intermediateResults.withIndex()) { pair ->
|
||||
val (i, result) = pair
|
||||
intermediateResults[i] = null // to not hold unused objects in the heap
|
||||
if (result != null)
|
||||
ElementResult(result.codeGenerator(usageProcessings), result.parseContext)
|
||||
else
|
||||
null
|
||||
result?.let {
|
||||
val (text, importsToAdd) = it.codeGenerator(usageProcessings)
|
||||
ElementResult(text, importsToAdd, it.parseContext)
|
||||
}
|
||||
}
|
||||
|
||||
val externalCodeProcessing = buildExternalCodeProcessing(usageProcessings, ::inConversionScope)
|
||||
|
||||
if (postProcessor == null) {
|
||||
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)
|
||||
return Result(results, externalCodeProcessing)
|
||||
}
|
||||
catch(e: ElementCreationStackTraceRequiredException) {
|
||||
// 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 target: PsiElement,
|
||||
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(
|
||||
indicator: ProgressIndicator,
|
||||
private val start: Double,
|
||||
|
||||
@@ -70,8 +70,8 @@ public object JavaToKotlinTranslator {
|
||||
public fun generateKotlinCode(javaCode: String, project: Project): String {
|
||||
val file = createFile(javaCode, project)
|
||||
if (file is PsiJavaFile) {
|
||||
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter, null)
|
||||
return prettify(converter.elementsToKotlin(listOf(JavaToKotlinConverter.InputElement(file, null))).results.single()!!.text)
|
||||
val converter = JavaToKotlinConverter(file.getProject(), ConverterSettings.defaultSettings, EmptyReferenceSearcher, EmptyResolverForConverter)
|
||||
return prettify(converter.elementsToKotlin(listOf(file)).results.single()!!.text) //TODO: imports
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -38,10 +38,15 @@ import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
|
||||
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()
|
||||
|
||||
val result = type.accept<Type>(TypeVisitor(converter, type, mutability))!!.assignNoPrototype()
|
||||
val result = type.accept<Type>(TypeVisitor(converter, type, mutability, inAnnotationType))!!.assignNoPrototype()
|
||||
return when (nullability) {
|
||||
Nullability.NotNull -> result.toNotNullType()
|
||||
Nullability.Nullable -> result.toNullableType()
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.j2k
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.source.PsiClassReferenceType
|
||||
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
|
||||
|
||||
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(
|
||||
private val converter: Converter,
|
||||
private val topLevelType: PsiType,
|
||||
private val topLevelTypeMutability: Mutability
|
||||
private val topLevelTypeMutability: Mutability,
|
||||
private val inAnnotationType: Boolean
|
||||
) : PsiTypeVisitor<Type>() {
|
||||
|
||||
private val typeConverter: TypeConverter = converter.typeConverter
|
||||
@@ -49,7 +51,7 @@ class TypeVisitor(
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -69,6 +71,12 @@ class TypeVisitor(
|
||||
if (kotlinClassName != null) {
|
||||
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) {
|
||||
@@ -78,8 +86,7 @@ class TypeVisitor(
|
||||
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.substring(className.lastIndexOf('.') + 1)
|
||||
private fun getShortName(className: String): String = className.substringAfterLast('.', className)
|
||||
|
||||
private fun convertTypeArgs(classType: PsiClassType): List<Type> {
|
||||
if (classType.getParameterCount() == 0) {
|
||||
@@ -112,7 +119,7 @@ class TypeVisitor(
|
||||
}
|
||||
|
||||
override fun visitEllipsisType(ellipsisType: PsiEllipsisType): Type {
|
||||
return VarArgType(typeConverter.convertType(ellipsisType.getComponentType()))
|
||||
return VarArgType(typeConverter.convertType(ellipsisType.getComponentType(), inAnnotationType = inAnnotationType))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -53,7 +53,7 @@ data class CommentsAndSpacesInheritance(val blankLinesBefore: Boolean = true,
|
||||
fun Element.canonicalCode(): String {
|
||||
val builder = CodeBuilder(null)
|
||||
builder.append(this)
|
||||
return builder.result
|
||||
return builder.resultText
|
||||
}
|
||||
|
||||
abstract class Element {
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
|
||||
package org.jetbrains.kotlin.j2k.ast
|
||||
|
||||
import org.jetbrains.kotlin.j2k.CodeBuilder
|
||||
import com.intellij.psi.PsiNameIdentifierOwner
|
||||
import org.jetbrains.kotlin.j2k.CodeBuilder
|
||||
import org.jetbrains.kotlin.lexer.JetKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.JetTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
fun PsiNameIdentifierOwner.declarationIdentifier(): Identifier {
|
||||
val name = getName()
|
||||
@@ -29,7 +30,8 @@ fun PsiNameIdentifierOwner.declarationIdentifier(): Identifier {
|
||||
class Identifier(
|
||||
val name: String,
|
||||
override val isNullable: Boolean = true,
|
||||
private val quotingNeeded: Boolean = true
|
||||
private val quotingNeeded: Boolean = true,
|
||||
private val imports: Collection<FqName> = emptyList()
|
||||
) : Expression() {
|
||||
|
||||
override val isEmpty: Boolean
|
||||
@@ -45,6 +47,8 @@ class Identifier(
|
||||
|
||||
override fun generateCode(builder: CodeBuilder) {
|
||||
builder.append(toKotlin())
|
||||
|
||||
imports.forEach { builder.addImport(it) }
|
||||
}
|
||||
|
||||
private fun quote(str: String): String = "`" + str + "`"
|
||||
|
||||
@@ -6,8 +6,8 @@ Anon3(e = E.A, stringArray = array(), value = *array("a", "b"))
|
||||
Anon4("x", "y")
|
||||
Anon5(1)
|
||||
Anon6("x", "y")
|
||||
Anon7(javaClass<String>(), javaClass<StringBuilder>())
|
||||
Anon8(classes = array(javaClass<String>(), javaClass<StringBuilder>()))
|
||||
Anon7(String::class, StringBuilder::class)
|
||||
Anon8(classes = array(String::class, StringBuilder::class))
|
||||
class C {
|
||||
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)
|
||||
}
|
||||
|
||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings,
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = true))
|
||||
val inputElements = psiFilesToConvert.map { JavaToKotlinConverter.InputElement(it, it) }
|
||||
val (results, externalCodeProcessor) = converter.elementsToKotlin(inputElements)
|
||||
val converter = JavaToKotlinConverter(project, ConverterSettings.defaultSettings, IdeaReferenceSearcher, IdeaResolverForConverter)
|
||||
val (results, externalCodeProcessor) = converter.filesToKotlin(psiFilesToConvert, J2kPostProcessor(formatCode = true))
|
||||
|
||||
val process = externalCodeProcessor?.prepareWriteOperation(EmptyProgressIndicator())
|
||||
project.executeWriteCommand("") { process?.invoke() }
|
||||
@@ -67,7 +65,7 @@ public abstract class AbstractJavaToKotlinConverterMultiFileTest : AbstractJavaT
|
||||
val resultFiles = ArrayList<JetFile>()
|
||||
for ((i, javaFile) in psiFilesToConvert.withIndex()) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -108,9 +108,8 @@ public abstract class AbstractJavaToKotlinConverterSingleFileTest : AbstractJava
|
||||
private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String {
|
||||
val file = createJavaFile(text)
|
||||
val converter = JavaToKotlinConverter(project, settings,
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter, J2kPostProcessor(formatCode = false))
|
||||
val inputElements = listOf(JavaToKotlinConverter.InputElement(file, file))
|
||||
return converter.elementsToKotlin(inputElements).results.single()!!.text
|
||||
IdeaReferenceSearcher, IdeaResolverForConverter)
|
||||
return converter.filesToKotlin(listOf(file), J2kPostProcessor(formatCode = true)).results.single()
|
||||
}
|
||||
|
||||
private fun methodToKotlin(text: String, settings: ConverterSettings, project: Project): String {
|
||||
|
||||
@@ -73,6 +73,18 @@ public class JavaToKotlinConverterForWebDemoTestGenerated extends AbstractJavaTo
|
||||
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")
|
||||
public void testJetbrainsNotNull() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java");
|
||||
|
||||
@@ -73,6 +73,18 @@ public class JavaToKotlinConverterSingleFileTestGenerated extends AbstractJavaTo
|
||||
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")
|
||||
public void testJetbrainsNotNull() throws Exception {
|
||||
String fileName = JetTestUtils.navigationMetadata("j2k/testData/fileOrElement/annotations/jetbrainsNotNull.java");
|
||||
|
||||
Reference in New Issue
Block a user