Introduce Parameter: Java -> Kotlin Interoperability

This commit is contained in:
Alexey Sedunov
2015-04-14 20:09:15 +03:00
parent 93861e16c8
commit f13eb038e1
15 changed files with 376 additions and 11 deletions
@@ -626,6 +626,7 @@ fun main(args: Array<String>) {
model("refactoring/introduceProperty", extension = "kt", testMethod = "doIntroducePropertyTest")
model("refactoring/introduceParameter", extension = "kt", testMethod = "doIntroduceSimpleParameterTest")
model("refactoring/introduceLambdaParameter", extension = "kt", testMethod = "doIntroduceLambdaParameterTest")
model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest")
}
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
@@ -43,7 +43,7 @@ fun PsiElement.isOverridableElement(): Boolean = when (this) {
else -> false
}
public fun HierarchySearchRequest<PsiElement>.searchOverriders(): Query<PsiMethod> {
public fun HierarchySearchRequest<*>.searchOverriders(): Query<PsiMethod> {
val psiMethods = originalElement.toLightMethods()
if (psiMethods.isEmpty()) return EmptyQuery.getEmptyQuery()
+2
View File
@@ -262,6 +262,8 @@
language="jet" />
<refactoring.copyHandler implementation="org.jetbrains.kotlin.idea.refactoring.copy.JetCopyClassHandler"/>
<refactoring.changeSignatureUsageProcessor implementation="org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureUsageProcessor"/>
<refactoring.introduceParameterMethodUsagesProcessor
implementation="org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterMethodUsageProcessor"/>
<inlineActionHandler implementation="org.jetbrains.kotlin.idea.refactoring.inline.KotlinInlineValHandler"/>
<treeStructureProvider implementation="org.jetbrains.kotlin.idea.projectView.JetProjectViewProvider"/>
@@ -46,7 +46,7 @@ public class JetChangeInfo(
val methodDescriptor: JetMethodDescriptor,
private var name: String = methodDescriptor.getName(),
val newReturnType: JetType? = methodDescriptor.baseDescriptor.getReturnType(),
var newReturnTypeText: String = newReturnType?.let { IdeDescriptorRenderers.SOURCE_CODE.renderType(it) } ?: "",
var newReturnTypeText: String = methodDescriptor.renderOriginalReturnType(),
var newVisibility: Visibility = methodDescriptor.getVisibility(),
parameterInfos: List<JetParameterInfo> = methodDescriptor.getParameters(),
receiver: JetParameterInfo? = methodDescriptor.receiver,
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter
import com.intellij.lang.java.JavaLanguage
import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiExpression
import com.intellij.psi.PsiMethod
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.refactoring.introduceParameter.IntroduceParameterData
import com.intellij.refactoring.introduceParameter.IntroduceParameterMethodUsagesProcessor
import com.intellij.usageView.UsageInfo
import com.intellij.util.ArrayUtil
import com.intellij.util.containers.MultiMap
import gnu.trove.TIntArrayList
import org.jetbrains.kotlin.asJava.KotlinLightMethod
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.refactoring.j2k
import org.jetbrains.kotlin.idea.j2k.IdeaResolverForConverter
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeInfo
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetChangeSignatureData
import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetParameterInfo
import org.jetbrains.kotlin.idea.refactoring.changeSignature.originalBaseFunctionDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.*
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.j2k.ConverterSettings
import org.jetbrains.kotlin.j2k.IdeaReferenceSearcher
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.types.JetType
import java.util.Arrays
import java.util.Collections
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchOverriders
public class KotlinIntroduceParameterMethodUsageProcessor : IntroduceParameterMethodUsagesProcessor {
override fun isMethodUsage(usage: UsageInfo): Boolean = (usage.getElement() as? JetElement)?.let {
it.getParentOfTypeAndBranch<JetCallElement>(true) { getCalleeExpression() } != null
} ?: false
override fun findConflicts(data: IntroduceParameterData, usages: Array<UsageInfo>, conflicts: MultiMap<PsiElement, String>) {
}
private fun createChangeInfo(data: IntroduceParameterData, method: PsiElement): JetChangeInfo? {
val psiMethodDescriptor = when (method) {
is JetFunction -> method.resolveToDescriptor() as? FunctionDescriptor
is PsiMethod -> method.getJavaMethodDescriptor()
else -> null
} ?: return null
val changeSignatureData = JetChangeSignatureData(psiMethodDescriptor, method, Collections.singletonList(psiMethodDescriptor))
val changeInfo = JetChangeInfo(methodDescriptor = changeSignatureData, context = method)
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) }
changeInfo.addParameter(JetParameterInfo(functionDescriptor = psiMethodDescriptor,
name = data.getParameterName(),
type = KotlinBuiltIns.getInstance().getAnyType(),
defaultValueForCall = defaultValueForCall))
return changeInfo
}
override fun processChangeMethodSignature(data: IntroduceParameterData, usage: UsageInfo, usages: Array<out UsageInfo>): Boolean {
val element = usage.getElement() as? JetFunction ?: return true
val changeInfo = createChangeInfo(data, element) ?: return true
// Java method is already updated at this point
val addedParameterType = data.getMethodToReplaceIn().getJavaMethodDescriptor().getValueParameters().last().getType()
changeInfo.getNewParameters().last().currentTypeText = IdeDescriptorRenderers.SOURCE_CODE.renderType(addedParameterType)
val scope = element.getUseScope().let {
if (it is GlobalSearchScope) GlobalSearchScope.getScopeRestrictedByFileTypes(it, JetFileType.INSTANCE) else it
}
val kotlinFunctions = HierarchySearchRequest(element, scope)
.searchOverriders()
.map { it.unwrapped }
.filterIsInstance<JetFunction>()
return (kotlinFunctions + element).all {
JetFunctionDefinitionUsage(it, changeInfo.originalBaseFunctionDescriptor, null, null).processUsage(changeInfo, it)
}
}
override fun processChangeMethodUsage(data: IntroduceParameterData, usage: UsageInfo, usages: Array<out UsageInfo>): Boolean {
val psiMethod = data.getMethodToReplaceIn()
val changeInfo = createChangeInfo(data, psiMethod) ?: return true
val refElement = usage.getElement() as? JetReferenceExpression ?: return true
val callElement = refElement.getParentOfTypeAndBranch<JetCallElement>(true) { getCalleeExpression() } ?: return true
val delegateUsage = if (callElement is JetConstructorDelegationCall) {
[suppress("CAST_NEVER_SUCCEEDS")]
(JetConstructorDelegationCallUsage(callElement, changeInfo) as JetUsageInfo<JetCallElement>)
}
else {
JetFunctionCallUsage(callElement, changeInfo.methodDescriptor.originalPrimaryFunction)
}
return delegateUsage.processUsage(changeInfo, callElement)
}
override fun processAddSuperCall(data: IntroduceParameterData, usage: UsageInfo, usages: Array<out UsageInfo>): Boolean = true
override fun processAddDefaultConstructor(data: IntroduceParameterData, usage: UsageInfo, usages: Array<out UsageInfo>): Boolean = true
}
@@ -0,0 +1,3 @@
fun test() {
val x = J().foo(1, 2, 3)
}
@@ -0,0 +1,3 @@
fun test() {
val x = J().foo(3, A(a + b))
}
@@ -0,0 +1,19 @@
class A {
int x;
A(int x) {
this.x = x;
}
}
class J {
int foo(int a, int b, int c) {
return <selection>new A(a + b)</selection>.x * c
}
}
class Test {
void test() {
new J().foo(1, 2, 3);
}
}
@@ -0,0 +1,19 @@
class A {
int x;
A(int x) {
this.x = x;
}
}
class J {
int foo(int c, A a1) {
return a1.x * c
}
}
class Test {
void test() {
new J().foo(3, new A(1 + 2));
}
}
@@ -0,0 +1,12 @@
fun test() {
J().foo(1, 2, 3)
K().foo(1, 2, 3)
}
abstract class K0 {
abstract fun foo(a: Int, b: Int, c: Int): Int
}
open class K: K0() {
override fun foo(a: Int, b: Int, c: Int) = a + b + c
}
@@ -0,0 +1,12 @@
fun test() {
J().foo(3, A(a + b))
K().foo(3, A(a + b))
}
abstract class K0 {
abstract fun foo(c: Int, a1: A?): Int
}
open class K: K0() {
override fun foo(c: Int, a1: A?) = a + b + c
}
@@ -0,0 +1,26 @@
// APPLY_TO_SUPER
class A {
int x;
A(int x) {
this.x = x;
}
}
interface T {
int foo(int a, int b, int c);
}
class J extends K implements T {
@Override
public int foo(int a, int b, int c) {
return <selection>new A(a + b)</selection>.x * c;
}
}
class Test {
void test() {
new J().foo(1, 2, 3);
new K().foo(1, 2, 3);
}
}
@@ -0,0 +1,26 @@
// APPLY_TO_SUPER
class A {
int x;
A(int x) {
this.x = x;
}
}
interface T {
int foo(int a, int b, int c);
}
class J extends K implements T {
@Override
public int foo(int c, A a1) {
return a1.x * c;
}
}
class Test {
void test() {
new J().foo(3, new A(1 + 2));
new K().foo(3, new A(1 + 2));
}
}
@@ -16,11 +16,23 @@
package org.jetbrains.kotlin.idea.refactoring.introduce
import com.intellij.codeInsight.CodeInsightUtil
import com.intellij.codeInsight.completion.JavaCompletionUtil
import com.intellij.ide.DataManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.refactoring.IntroduceParameterRefactoring
import com.intellij.refactoring.introduceField.ElementToWorkOn
import com.intellij.refactoring.introduceParameter.AbstractJavaInplaceIntroducer
import com.intellij.refactoring.introduceParameter.IntroduceParameterProcessor
import com.intellij.refactoring.introduceParameter.Util
import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringUtil
@@ -52,6 +64,8 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
protected fun doIntroduceVariableTest(path: String) {
doTest(path) { file ->
file as JetFile
KotlinIntroduceVariableHandler().invoke(
fixture.getProject(),
fixture.getEditor(),
@@ -111,9 +125,85 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
protected fun doIntroduceLambdaParameterTest(path: String) {
doIntroduceParameterTest(path, true)
}
protected fun doIntroduceJavaParameterTest(path: String) {
// Copied from com.intellij.refactoring.IntroduceParameterTest.perform()
doTest(path, true) { file ->
file as PsiJavaFile
var elementToWorkOn: ElementToWorkOn? = null
ElementToWorkOn.processElementToWorkOn(
getEditor(),
file,
"Introduce parameter",
null,
getProject(),
object : ElementToWorkOn.ElementsProcessor<ElementToWorkOn> {
override fun accept(e: ElementToWorkOn): Boolean {
return true
}
override fun pass(e: ElementToWorkOn?) {
if (e != null) {
elementToWorkOn = e
}
}
})
val expr = elementToWorkOn!!.getExpression()
val localVar = elementToWorkOn!!.getLocalVariable()
val context = expr ?: localVar
val method = Util.getContainingMethod(context) ?: throw AssertionError("No containing method found")
val applyToSuper = InTextDirectivesUtils.isDirectiveDefined(file.getText(), "// APPLY_TO_SUPER")
val methodToSearchFor = if (applyToSuper) method.findDeepestSuperMethods()[0] else method
val (initializer, occurrences) =
if (expr == null) {
localVar.getInitializer()!! to CodeInsightUtil.findReferenceExpressions(method, localVar)
}
else {
expr to ExpressionOccurrenceManager(expr, method, null).findExpressionOccurrences()
}
val type = initializer.getType()
val parametersToRemove = Util.findParametersToRemove(method, initializer, occurrences)
val codeStyleManager = JavaCodeStyleManager.getInstance(getProject())
val info = codeStyleManager.suggestUniqueVariableName(
codeStyleManager.suggestVariableName(VariableKind.PARAMETER, localVar?.getName(), initializer, type),
expr,
true
)
val suggestedNames = AbstractJavaInplaceIntroducer.appendUnresolvedExprName(
JavaCompletionUtil.completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, info),
initializer
)
IntroduceParameterProcessor(getProject(),
method,
methodToSearchFor,
initializer,
expr,
localVar,
true,
suggestedNames.first(),
true,
IntroduceParameterRefactoring.REPLACE_FIELDS_WITH_GETTERS_NONE,
false,
false,
null,
parametersToRemove).run()
getEditor().getSelectionModel().removeSelection()
}
}
protected fun doIntroducePropertyTest(path: String) {
doTest(path) { file ->
file as JetFile
val extractionTarget = propertyTargets.single {
it.name == InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// EXTRACTION_TARGET: ")
}
@@ -144,6 +234,8 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
protected fun doExtractFunctionTest(path: String) {
doTest(path) { file ->
file as JetFile
val explicitPreviousSibling = file.findElementByComment("// SIBLING:")
val fileText = file.getText() ?: ""
val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ")
@@ -206,7 +298,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
}
}
protected fun doTest(path: String, action: (JetFile) -> Unit) {
protected fun doTest(path: String, checkAdditionalAfterdata: Boolean = false, action: (PsiFile) -> Unit) {
val mainFile = File(path)
val afterFile = File("$path.after")
val conflictFile = File("$path.conflicts")
@@ -215,13 +307,11 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
val mainFileName = mainFile.getName()
val mainFileBaseName = FileUtil.getNameWithoutExtension(mainFileName)
mainFile.getParentFile()
.listFiles { file, name ->
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
}.forEach {
fixture.configureByFile(it.getName())
}
val file = fixture.configureByFile(mainFileName) as JetFile
val extraFiles = mainFile.getParentFile().listFiles { file, name ->
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
}
val extraFilesToPsi = extraFiles.toMap { fixture.configureByFile(it.getName()) }
val file = fixture.configureByFile(mainFileName)
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// WITH_RUNTIME") != null
if (addKotlinRuntime) {
@@ -233,6 +323,12 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" }
JetTestUtils.assertEqualsToFile(afterFile, file.getText()!!)
if (checkAdditionalAfterdata) {
for ((extraPsiFile, extraFile) in extraFilesToPsi) {
JetTestUtils.assertEqualsToFile(File("${extraFile.getPath()}.after"), extraPsiFile.getText())
}
}
}
catch(e: Exception) {
val message = if (e is ConflictsInTestsException) e.getMessages().sort().joinToString(" ") else e.getMessage()
@@ -2521,4 +2521,25 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doIntroduceLambdaParameterTest(fileName);
}
}
@TestMetadata("idea/testData/refactoring/introduceJavaParameter")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class IntroduceJavaParameter extends AbstractJetExtractionTest {
public void testAllFilesPresentInIntroduceJavaParameter() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceJavaParameter"), Pattern.compile("^(.+)\\.java$"), true);
}
@TestMetadata("javaMethod.java")
public void testJavaMethod() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceJavaParameter/javaMethod.java");
doIntroduceJavaParameterTest(fileName);
}
@TestMetadata("javaMethodOverridingKotlinFunctionWithUsages.java")
public void testJavaMethodOverridingKotlinFunctionWithUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceJavaParameter/javaMethodOverridingKotlinFunctionWithUsages.java");
doIntroduceJavaParameterTest(fileName);
}
}
}