From 5c8035b3391f63c1b421cad57e538335ab6b4486 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 10 Dec 2018 13:14:02 +0300 Subject: [PATCH] New J2K: Add ConvertGettersAndSetters postProcessing --- .../ConvertGettersAndSetters.kt | 347 ++++++++++++++++++ .../conversions/FieldToPropertyConversion.kt | 251 +------------ 2 files changed, 366 insertions(+), 232 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/j2k/postProcessing/ConvertGettersAndSetters.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/j2k/postProcessing/ConvertGettersAndSetters.kt b/idea/src/org/jetbrains/kotlin/idea/j2k/postProcessing/ConvertGettersAndSetters.kt new file mode 100644 index 00000000000..416fac09347 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/j2k/postProcessing/ConvertGettersAndSetters.kt @@ -0,0 +1,347 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.j2k.postProcessing + +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.core.setVisibility +import org.jetbrains.kotlin.idea.j2k.J2kPostProcessing +import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference +import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass +import org.jetbrains.kotlin.lexer.KtKeywordToken +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.JvmAbi +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.addRemoveModifier.setModifierList +import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics + +class ConvertGettersAndSetters : J2kPostProcessing { + override val writeActionNeeded: Boolean = true + + data class PropertyData( + var name: String, + var getter: Accessor? = null, + var setter: Accessor? = null + ) + + data class Accessor( + val name: String, + val target: KtProperty?, + val function: KtFunction + ) + + private fun Accessor.isTrivial() = target != null + + private fun KtExpression.statements() = + if (this is KtBlockExpression) statements + else listOf(this) + + private fun KtFunction.asGetter(): Accessor? { + if (valueParameters.isNotEmpty()) return null + if (typeParameters.isNotEmpty()) return null + val name = getterName() ?: return null + val target = bodyExpression + ?.statements() + ?.singleOrNull() + ?.let { + if (it is KtReturnExpression) it.returnedExpression + else it + }?.let { + it.unpackedReferenceToProperty() + }?.takeIf { + it.type() == this.type() + } + return Accessor(name, target, this) + } + + private fun KtFunction.isProcedure() = + bodyExpression is KtBlockExpression? && !hasDeclaredReturnType() + || getReturnTypeReference()?.typeElement!!.text == "Unit" + + private fun KtFunction.asSetter(): Accessor? { + if (typeParameters.isNotEmpty()) return null + if (valueParameters.size != 1) return null + if (!isProcedure()) return null + val name = setterName() ?: return null + + val target = bodyExpression + ?.statements() + ?.singleOrNull() + ?.let { + if (it is KtBinaryExpression) { + if (it.operationToken != KtTokens.EQEQ) return@let null + val right = it.right as? KtNameReferenceExpression ?: return@let null + if (right.reference?.resolve() != valueParameters.single()) return@let null + it.left?.unpackedReferenceToProperty() + } else null + }?.takeIf { + it.type() == valueParameters.single().type() + } + return Accessor(name, target, this) + } + + private val keywords = KtTokens.KEYWORDS.types.map { (it as KtKeywordToken).value }.toSet() + + private fun String.escaped() = + if (this in keywords) "`$this`" + else this + + private fun KtFunction.getterName() = + name?.takeIf { JvmAbi.isGetterName(it) } + ?.removePrefix("get") + ?.takeIf { + it.isNotEmpty() && it.first().isUpperCase() + || it.startsWith("is") && it.length > 2 && it[2].isUpperCase() + }?.decapitalize() + ?.escaped() + + private fun KtFunction.setterName() = + name?.takeIf { JvmAbi.isSetterName(it) } + ?.removePrefix("set") + ?.takeIf { it.first().isUpperCase() } + ?.decapitalize() + ?.escaped() + + private fun generatePropertiesData(element: KtClassOrObject): List { + val properties = mutableMapOf() + + for (declaration in element.declarations) { + when (declaration) { + is KtFunction -> { + declaration.asGetter() + ?.also { getter -> + properties.getOrPut(getter.name.removePrefix("is").decapitalize()) { + PropertyData(getter.name) + }.also { it.getter = getter } + } + + declaration.asSetter() + ?.also { setter -> + properties.getOrPut(setter.name) { + PropertyData(setter.name) + }.also { it.setter = setter } + } + } + } + } + return properties.values.toList() + } + + private fun T.replaceBodyText( + from: KtElement, + to: KtExpression, + replaceOnlyWriteUsages: Boolean + ): T = + also { + from.usages() + .map { it.element } + .filter { it.isInsideOf(listOf(this)) } + .forEach { reference -> + val parent = reference.parent + val referenceExpression = when { + parent is KtQualifiedExpression && parent.receiverExpression is KtThisExpression -> + parent + else -> reference + } + if (!replaceOnlyWriteUsages + || (referenceExpression.parent as? KtExpression)?.asAssignment()?.left == referenceExpression + ) { + referenceExpression.replace(to) + } + } + } + + + private fun KtPsiFactory.createGetter(expression: KtExpression?): KtPropertyAccessor { + val property = + createProperty("val x get" + if (expression == null) "" else if (expression is KtBlockExpression) "() { return 1 }" else "() = 1") + val getter = property.getter!! + val bodyExpression = getter.bodyExpression + + bodyExpression?.replace(expression!!) + return getter + } + + + private fun KtProperty.addGetter(getterFunction: Accessor, target: KtProperty): KtPropertyAccessor { + val factory = KtPsiFactory(this) + val newBody = + if (!getterFunction.isTrivial()) { + getterFunction.function.bodyExpression + ?.replaceBodyText(target, factory.createExpression("field"), replaceOnlyWriteUsages = false) + } else null + add(factory.createGetter(newBody)) + getterFunction.function.modifierList?.also { + getter!!.addModifiers(it) + } + return getter!! + } + + private fun KtPropertyAccessor.addModifiers(newModifiers: KtModifierList) { + setModifierList(newModifiers) + removeModifier(KtTokens.OVERRIDE_KEYWORD) + removeModifier(KtTokens.FINAL_KEYWORD) + } + + private fun KtPsiFactory.createSetter(body: KtExpression?, fieldName: String): KtPropertyAccessor { + val property = when (body) { + null -> createProperty("var x = 1\n get() = 1\n set") + is KtBlockExpression -> createProperty("var x get() = 1\nset($fieldName) {\n field = $fieldName\n }") + else -> createProperty("var x get() = 1\nset($fieldName) = TODO()") + } + val setter = property.setter!! + if (body != null) { + setter.bodyExpression?.replace(body) + } + return setter + } + + private fun KtProperty.addSetter(setterInfo: Accessor): KtPropertyAccessor { + val factory = KtPsiFactory(this) + val newBody = + if (!setterInfo.isTrivial()) { + setterInfo.function.bodyExpression?.replaceBodyText(this, factory.createExpression("field"), true) + } else null + add(factory.createSetter(newBody, setterInfo.function.valueParameters.single().name!!)) + setterInfo.function.modifierList?.also { + setter!!.addModifiers(it) + } + return setter!! + } + + private fun KtElement.forAllUsages(action: (KtElement) -> Unit) { + ReferencesSearch.search(this, LocalSearchScope(containingKtFile)) + .forEach { action(it.element as KtElement) } + } + + private fun KtElement.usages() = + ReferencesSearch.search(this, LocalSearchScope(containingKtFile)) + .findAll() + + + override fun createAction(element: KtElement, diagnostics: Diagnostics): (() -> Unit)? { + if (element !is KtClassOrObject) return null + + return { + val factory = KtPsiFactory(element) + for ((name, getterFunction, setterFunction) in generatePropertiesData(element)) { + if (getterFunction == null) continue + + if (!getterFunction.isTrivial() && + element.declarations.any { (it as? KtProperty)?.name == name } + ) {//TODO improve?? + continue + } + + val setterAndGetterHaveTheSameTypes = + setterFunction != null && + setterFunction.function.valueParameters.single().type() == getterFunction.function.type() + + if (element.isInterfaceClass() && setterFunction != null && !setterAndGetterHaveTheSameTypes) { + continue + } + + val hasOverrideModifier = + getterFunction.target?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false + || getterFunction.function.hasModifier(KtTokens.OVERRIDE_KEYWORD) + || setterFunction?.function?.hasModifier(KtTokens.OVERRIDE_KEYWORD) ?: false + + val target = getterFunction.target + ?: factory.createProperty( + name, + getterFunction.function.getReturnTypeReference()?.text, + true + ) + + if (target.name != name) {//TODO use refactoring + target.forAllUsages { usage -> + val parent = (usage.parent as? KtExpression) + ?.asAssignment() + ?.takeIf { it.left == usage } + + val expression = if (parent != null) factory.createExpression("this.$name") + else factory.createExpression(name) + + usage.replace(expression) + } + target.setName(name) + } + + val propertyGetter = getterFunction.let { + val getter = target.addGetter(it, target) + it.function.forAllUsages { usage -> + usage.parentOfType()!!.replace(factory.createExpression(name)) + } + getter + } + + val propertySetter = setterFunction + ?.takeIf { setterAndGetterHaveTheSameTypes } + ?.let { + val setter = target.addSetter(it) + it.function.forAllUsages { usage -> + val callExpression = usage.parentOfType()!! + val qualifier = callExpression.getQualifiedExpressionForSelector() + val newValue = callExpression.valueArguments.single() + if (qualifier != null) { + qualifier + .replace(factory.createExpression("${qualifier.receiverExpression.text}.${target.name} = ${newValue.text}")) + } else { + callExpression.replace(factory.createExpression("${target.name} = ${newValue.text}")) + } + } + it.function.delete() + setter + } + ?: if (target.isVar + && target.visibilityModifierTypeOrDefault() in listOf(KtTokens.PRIVATE_KEYWORD, KtTokens.PROTECTED_KEYWORD) + ) { + factory.createSetter(null, "value").apply { + setVisibility(target.visibilityModifierTypeOrDefault()) + target.add(this) + } + target.setter!! + } else null + + + val isVar = propertySetter != null + + if (target.isVar != isVar) { + target.valOrVarKeyword + .replace(if (isVar) factory.createVarKeyword() else factory.createValKeyword()) + } + + + val visibility = listOfNotNull( + propertySetter?.visibilityModifierTypeOrDefault(), + propertyGetter.visibilityModifierTypeOrDefault(), + target.visibilityModifierTypeOrDefault() + ).maxBy { + when (it) { + KtTokens.PUBLIC_KEYWORD -> 4 + KtTokens.INTERNAL_KEYWORD -> 3 + KtTokens.PROTECTED_KEYWORD -> 2 + KtTokens.PRIVATE_KEYWORD -> 1 + else -> 0 + } + }!! + target.setVisibility(visibility) + + if (getterFunction.target == null) { + element.addDeclarationAfter(target, getterFunction.function) + getterFunction.function.delete() + } else { + getterFunction.function.delete() + } + if (!target.hasModifier(KtTokens.OVERRIDE_KEYWORD) && hasOverrideModifier) { + target.addModifier(KtTokens.OVERRIDE_KEYWORD) + } + } + } + } +} \ No newline at end of file diff --git a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/FieldToPropertyConversion.kt b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/FieldToPropertyConversion.kt index c4dbd3f14f5..09b8a9956a5 100644 --- a/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/FieldToPropertyConversion.kt +++ b/j2k/newSrc/org/jetbrains/kotlin/j2k/conversions/FieldToPropertyConversion.kt @@ -5,242 +5,29 @@ package org.jetbrains.kotlin.j2k.conversions -import org.jetbrains.kotlin.j2k.ConversionContext import org.jetbrains.kotlin.j2k.tree.* import org.jetbrains.kotlin.j2k.tree.impl.* -import org.jetbrains.kotlin.load.java.JvmAbi - -class FieldToPropertyConversion(private val context: ConversionContext) : RecursiveApplicableConversionBase() { - private data class PropertyInfo( - val name: String, - var setter: AccessorInfo? = null, - var getter: AccessorInfo? = null, - var field: JKJavaField? = null - ) - - private data class AccessorInfo( - var method: JKMethod, - val target: JKJavaField? - ) - - private fun AccessorInfo.isTrivialFor(field: JKJavaField) = - target == field - - private fun T.renamed( - fromName: String, - toName: String, - type: JKType, - renameType: RenameType - ): T = - also { - Renamer(fromName, toName, type, renameType).runConversion(it, context) - } - - private fun PropertyInfo.toKtProperty(): JKKtProperty? { - val propertyField = field - ?: getter?.method - ?.takeIf { propertyNameFromGet(it) != null } - ?.let { - JKJavaFieldImpl( - JKTypeElementImpl(it.returnType.type), - JKNameIdentifierImpl(propertyNameFromGet(it)!!), - JKStubExpressionImpl(), - JKAnnotationListImpl(), - emptyList(), - Visibility.PRIVATE, - Modality.OPEN, - Mutability.UNKNOWN - ) - } - ?: return null - - val ktGetter = getter?.let { - val method = it.method - val block = if (it.isTrivialFor(propertyField)) JKBodyStub else method::block.detached() - JKKtGetterOrSetterImpl( - JKBlockStatementImpl(block) - .renamed(propertyField.name.value, "field", propertyField.type.type, RenameType.RENAME_FIELD_ACCESS), - JKKtGetterOrSetter.Kind.GETTER, - method.visibility - ) - } ?: JKKtGetterOrSetterImpl( - JKBlockStatementImpl(JKBodyStub), - JKKtGetterOrSetter.Kind.GETTER, - propertyField.visibility - ) - - val ktSetter = setter?.let { - val method = it.method - val block = if (it.isTrivialFor(propertyField)) JKBodyStub else method::block.detached() - JKKtGetterOrSetterImpl( - JKBlockStatementImpl(block) - .renamed( - method.parameters.single().name.value, - "value", - method.parameters.single().type.type, - RenameType.RENAME_PARAMETER_ACCESS - ) - .renamed(propertyField.name.value, "field", propertyField.type.type, RenameType.RENAME_FIELD_ACCESS), - JKKtGetterOrSetter.Kind.SETTER, - method.visibility - ) - } ?: JKKtEmptyGetterOrSetterImpl() - propertyField.invalidate() - - val mutability = - if (propertyField.modality == Modality.FINAL) Mutability.IMMUTABLE - else if (propertyField.visibility == Visibility.PRIVATE && ktSetter is JKKtEmptyGetterOrSetter) - Mutability.IMMUTABLE - else Mutability.MUTABLE - - val visibility = - listOf( - ktGetter.visibility, - ktGetter.visibility - ).min() ?: Visibility.PUBLIC - - return JKKtPropertyImpl( - propertyField.type, - propertyField.name, - propertyField.initializer, - ktGetter, - ktSetter, - propertyField.annotationList, - propertyField.extraModifiers, - visibility, - propertyField.modality, - mutability - ) - } - - private fun JKJavaMethod.isGetterOrSetter() = - propertyNameFromGet(this) != null || propertyNameFromSet(this) != null +class FieldToPropertyConversion : RecursiveApplicableConversionBase() { override fun applyToElement(element: JKTreeElement): JKTreeElement { - if (element !is JKClass) return applyRecursive(element, this::applyToElement) - - val propertyInfos = mutableMapOf() - - fun propertyInfoFor(name: String): PropertyInfo { - return propertyInfos.getOrPut(name) { - PropertyInfo(name) - } - } - - val declarations = element.declarationList - declarations.forEach { - when (it) { - is JKJavaField -> - propertyInfoFor(it.name.value).field = it - is JKJavaMethod -> { - propertyNameFromGet(it)?.let { fieldName -> - propertyInfoFor(fieldName).getter = AccessorInfo(it, it.fieldFromGetter()) - } ?: propertyNameFromSet(it)?.let { fieldName -> - propertyInfoFor(fieldName).setter = AccessorInfo(it, it.fieldFromSetter()) - } - } - } - } - - element.classBody.declarations = - declarations.mapNotNull { declaration -> - when (declaration) { - is JKJavaField -> propertyInfoFor(declaration.name.value).toKtProperty() - is JKJavaMethod -> - if (declaration.isGetterOrSetter()) { - propertyNameFromGet(declaration)?.let { fieldName -> - propertyInfoFor(fieldName) - }?.takeIf { it.field == null }?.toKtProperty() - } else declaration - else -> declaration - } - } - return recurse(element) - } - - - private enum class RenameType { - RENAME_FIELD_ACCESS, - RENAME_PARAMETER_ACCESS - } - - private inner class Renamer( - private val fromName: String, - private val toName: String, - private val type: JKType, - private val renameType: RenameType - ) : RecursiveApplicableConversionBase() { - override fun applyToElement(element: JKTreeElement): JKTreeElement = - recurse( - when (renameType) { - RenameType.RENAME_FIELD_ACCESS -> renameFieldAccessExpression(element) - RenameType.RENAME_PARAMETER_ACCESS -> renameParameter(element) - } ?: element + if (element !is JKJavaField) return recurse(element) + element.invalidate() + val mutability = + if (element.modality == Modality.FINAL) Mutability.IMMUTABLE + else Mutability.MUTABLE + return recurse( + JKKtPropertyImpl( + element.type, + element.name, + element.initializer, + JKKtEmptyGetterOrSetterImpl(), + JKKtEmptyGetterOrSetterImpl(), + element.annotationList, + element.extraModifiers, + element.visibility, + Modality.FINAL, + mutability ) - - private fun renameFieldAccessExpression(element: JKTreeElement): JKFieldAccessExpressionImpl? = - (element as? JKExpression)?.unboxFieldReference()?.let { - if (it.identifier.name == fromName) JKFieldAccessExpressionImpl(createSymbol()) - else null - } - - - private fun renameParameter(element: JKTreeElement): JKFieldAccessExpressionImpl? { - if (element !is JKFieldAccessExpression) return null - val target = element.identifier.target as? JKParameter ?: return null - return if (target.name.value == fromName) { - JKFieldAccessExpressionImpl(createSymbol()) - } else null - } - - private fun createSymbol() = object : JKFieldSymbol { - override val target: String = toName - override val declaredIn: JKSymbol? = null - override val name: String = toName - override val fqName: String = toName - override val fieldType: JKType = type - } - } - - - private fun propertyNameFromGet(method: JKMethod): String? { - if (!JvmAbi.isGetterName(method.name.value)) return null - if (method.parameters.isNotEmpty()) return null - if (method !is JKJavaMethod) return null - return method.name.value - .removePrefix("get") - .removePrefix("is") - .decapitalize() - } - - private fun propertyNameFromSet(method: JKMethod): String? { - if (!JvmAbi.isSetterName(method.name.value)) return null - if (method.parameters.size != 1) return null - if (method !is JKJavaMethod) return null - return method.name.value - .removePrefix("set") - .decapitalize() - } - - private fun JKExpression.unboxFieldReference(): JKFieldAccessExpression? = when { - this is JKFieldAccessExpression -> this - this is JKQualifiedExpression && receiver is JKThisExpression -> selector as? JKFieldAccessExpression - else -> null - } - - private fun JKMethod.fieldFromGetter(): JKJavaField? { - if (this !is JKJavaMethod) return null - val returnStatement = block.statements.singleOrNull() as? JKReturnStatement ?: return null - val fieldAccess = returnStatement.expression.unboxFieldReference() ?: return null - return fieldAccess.identifier.target as? JKJavaField - } - - private fun JKMethod.fieldFromSetter(): JKJavaField? { - if (this !is JKJavaMethod) return null - val expressionStatement = this.block.statements.singleOrNull() as? JKExpressionStatement ?: return null - val assignment = expressionStatement.expression as? JKJavaAssignmentExpression ?: return null - val lhs = assignment.field - val fieldAccess = lhs.unboxFieldReference() - return fieldAccess?.identifier?.target as? JKJavaField + ) } }