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?) {
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()
@@ -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
@@ -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
+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.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");