From 141c88aaa92917e53867904089e1e46f3e5b105e Mon Sep 17 00:00:00 2001 From: Natalia Ukhorskaya Date: Tue, 7 Oct 2014 17:20:35 +0400 Subject: [PATCH] Make ReferenceSearcher a parameter for JavaToKotlinConverter --- .../plugin/actions/JavaToKotlinAction.java | 2 + .../copy/ConvertJavaCopyPastePostProcessor.kt | 1 + .../jetbrains/jet/j2k/ConstructorConverter.kt | 6 +-- j2k/src/org/jetbrains/jet/j2k/Converter.kt | 27 +++++----- .../jet/j2k/JavaToKotlinTranslator.kt | 2 +- .../jetbrains/jet/j2k/ReferenceSearcher.kt | 54 +++++++++++++++++++ .../org/jetbrains/jet/j2k/TypeConverter.kt | 12 ++--- j2k/src/org/jetbrains/jet/j2k/Utils.kt | 34 +++--------- .../jet/j2k/visitors/ForConverter.kt | 4 +- .../test/AbstractJavaToKotlinConverterTest.kt | 5 +- 10 files changed, 94 insertions(+), 53 deletions(-) create mode 100644 j2k/src/org/jetbrains/jet/j2k/ReferenceSearcher.kt diff --git a/idea/src/org/jetbrains/jet/plugin/actions/JavaToKotlinAction.java b/idea/src/org/jetbrains/jet/plugin/actions/JavaToKotlinAction.java index 77f81f95e5f..75099cd533c 100644 --- a/idea/src/org/jetbrains/jet/plugin/actions/JavaToKotlinAction.java +++ b/idea/src/org/jetbrains/jet/plugin/actions/JavaToKotlinAction.java @@ -28,6 +28,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.j2k.Converter; import org.jetbrains.jet.j2k.ConverterSettings; import org.jetbrains.jet.j2k.FilesConversionScope; +import org.jetbrains.jet.j2k.ReferenceSearcherImpl; import org.jetbrains.jet.plugin.j2k.J2kPostProcessor; import java.util.List; @@ -49,6 +50,7 @@ public class JavaToKotlinAction extends AnAction { final Converter converter = Converter.OBJECT$.create(project, ConverterSettings.defaultSettings, new FilesConversionScope(selectedJavaFiles), + ReferenceSearcherImpl.INSTANCE$, //TODO: (module refactoring) resulting files should be analyzed in context of respective java files new J2kPostProcessor(selectedJavaFiles.iterator().next())); CommandProcessor.getInstance().executeCommand( diff --git a/idea/src/org/jetbrains/jet/plugin/conversion/copy/ConvertJavaCopyPastePostProcessor.kt b/idea/src/org/jetbrains/jet/plugin/conversion/copy/ConvertJavaCopyPastePostProcessor.kt index b28ec20b8fa..3e272995a7f 100644 --- a/idea/src/org/jetbrains/jet/plugin/conversion/copy/ConvertJavaCopyPastePostProcessor.kt +++ b/idea/src/org/jetbrains/jet/plugin/conversion/copy/ConvertJavaCopyPastePostProcessor.kt @@ -89,6 +89,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor? { val body = constructor.getBody() ?: return null - val refs = findVariableUsages(parameter, body) + val refs = converter.referenceSearcher.findVariableUsages(parameter, body) if (refs.any { PsiUtil.isAccessedForWriting(it) }) return null @@ -297,7 +297,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter if (statement.getParent() != body) continue // and no other assignments to field should exist in the constructor - if (findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue + if (converter.referenceSearcher.findVariableUsages(field, body).any { it != assignee && PsiUtil.isAccessedForWriting(it) && isQualifierEmptyOrThis(it) }) continue //TODO: check access to field before assignment return field to statement diff --git a/j2k/src/org/jetbrains/jet/j2k/Converter.kt b/j2k/src/org/jetbrains/jet/j2k/Converter.kt index c0e6049b7af..0bbc452254d 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Converter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Converter.kt @@ -45,6 +45,7 @@ public trait PostProcessor { public class Converter private(val project: Project, val settings: ConverterSettings, val conversionScope: ConversionScope, + val referenceSearcher: ReferenceSearcher, private val postProcessor: PostProcessor?, private val state: Converter.State) { private class State(val methodReturnType: PsiType?, @@ -67,34 +68,34 @@ public class Converter private(val project: Project, val annotationConverter = AnnotationConverter(this) class object { - public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope, postProcessor: PostProcessor?): Converter { + public fun create(project: Project, settings: ConverterSettings, conversionScope: ConversionScope, referenceSearcher: ReferenceSearcher, postProcessor: PostProcessor?): Converter { val state = State(null, { ExpressionVisitor(it) }, { StatementVisitor(it) }, null, null, null) - return Converter(project, settings, conversionScope, postProcessor, state) + return Converter(project, settings, conversionScope, referenceSearcher, postProcessor, state) } } fun withMethodReturnType(methodReturnType: PsiType?): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd)) fun withExpressionVisitor(factory: (Converter) -> ExpressionVisitor): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(state.methodReturnType, factory, state.statementVisitorFactory, state.specialContext, state.importList, state.importsToAdd)) fun withStatementVisitor(factory: (Converter) -> StatementVisitor): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(state.methodReturnType, state.expressionVisitorFactory, factory, state.specialContext, state.importList, state.importsToAdd)) fun withSpecialContext(context: PsiElement): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, context, state.importList, state.importsToAdd)) private fun withImportList(importList: ImportList): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, importList, state.importsToAdd)) private fun withImportsToAdd(importsToAdd: MutableCollection): Converter - = Converter(project, settings, conversionScope, postProcessor, + = Converter(project, settings, conversionScope, referenceSearcher, postProcessor, State(state.methodReturnType, state.expressionVisitorFactory, state.statementVisitorFactory, state.specialContext, state.importList, importsToAdd)) public fun elementToKotlin(element: PsiElement): String { @@ -220,7 +221,7 @@ public class Converter private(val project: Project, val classObjectMembers = members.filter { it !is PsiClass && it.hasModifierProperty(PsiModifier.STATIC) } val nestedClasses = members.filterIsInstance(javaClass()).filter { it.hasModifierProperty(PsiModifier.STATIC) } if (classObjectMembers.all { it is PsiMethod && it.hasModifierProperty(PsiModifier.PRIVATE) }) { - return nestedClasses.any { nestedClass -> classObjectMembers.any { findMethodCalls(it as PsiMethod, nestedClass).isNotEmpty() } } + return nestedClasses.any { nestedClass -> classObjectMembers.any { referenceSearcher.findMethodCalls(it as PsiMethod, nestedClass).isNotEmpty() } } } else { return true @@ -332,7 +333,7 @@ public class Converter private(val project: Project, ExpressionList(convertExpressions(argumentList?.getExpressions())).assignPrototype(argumentList)) } else { - val isVal = isVal(field) + val isVal = isVal(referenceSearcher, field) val typeToDeclare = variableTypeToDeclare(field, settings.specifyFieldTypeByDefault || modifiers.isPublic || modifiers.isProtected, isVal && modifiers.isPrivate) @@ -344,7 +345,7 @@ public class Converter private(val project: Project, initializer, isVal, typeToDeclare != null, - initializer.isEmpty && shouldGenerateDefaultInitializer(field)) + initializer.isEmpty && shouldGenerateDefaultInitializer(referenceSearcher, field)) } return converted.assignPrototype(field) } @@ -378,7 +379,7 @@ public class Converter private(val project: Project, val statementsToInsert = ArrayList() for (parameter in method.getParameterList().getParameters()) { - if (parameter.hasWriteAccesses(method)) { + if (parameter.hasWriteAccesses(referenceSearcher, method)) { val variable = LocalVariable(parameter.declarationIdentifier(), Annotations.Empty, Modifiers.Empty, @@ -488,7 +489,7 @@ public class Converter private(val project: Project, fun convertLocalVariable(variable: PsiLocalVariable): LocalVariable { val isVal = variable.hasModifierProperty(PsiModifier.FINAL) || variable.getInitializer() == null/* we do not know actually and prefer val until we have better analysis*/ || - !variable.hasWriteAccesses(variable.getContainingMethod()) + !variable.hasWriteAccesses(referenceSearcher, variable.getContainingMethod()) return LocalVariable(variable.declarationIdentifier(), annotationConverter.convertAnnotations(variable), convertModifiers(variable), diff --git a/j2k/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.kt b/j2k/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.kt index 10f99da126f..3bd93247b06 100644 --- a/j2k/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.kt +++ b/j2k/src/org/jetbrains/jet/j2k/JavaToKotlinTranslator.kt @@ -83,7 +83,7 @@ public object JavaToKotlinTranslator { fun generateKotlinCode(javaCode: String): String { val file = createFile(javaCode) if (file is PsiJavaFile) { - val converter = Converter.create(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)), null) + val converter = Converter.create(file.getProject(), ConverterSettings.defaultSettings, FilesConversionScope(listOf(file)), EmptyReferenceSearcher, null) return prettify(converter.elementToKotlin(file)) } return "" diff --git a/j2k/src/org/jetbrains/jet/j2k/ReferenceSearcher.kt b/j2k/src/org/jetbrains/jet/j2k/ReferenceSearcher.kt new file mode 100644 index 00000000000..e9377341879 --- /dev/null +++ b/j2k/src/org/jetbrains/jet/j2k/ReferenceSearcher.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2014 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.jet.j2k + +import com.intellij.psi.PsiVariable +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReferenceExpression +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiMethodCallExpression +import java.util.Collections + +public trait ReferenceSearcher { + fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection + fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection +} + +public object EmptyReferenceSearcher: ReferenceSearcher { + override fun findVariableUsages(variable: PsiVariable, scope: PsiElement) = Collections.emptyList() + override fun findMethodCalls(method: PsiMethod, scope: PsiElement) = Collections.emptyList() +} + +public object ReferenceSearcherImpl: ReferenceSearcher { + override fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection { + return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass()) + } + + override fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection { + return ReferencesSearch.search(method, LocalSearchScope(scope)).findAll().map { + if (it is PsiReferenceExpression) { + val methodCall = it.getParent() as? PsiMethodCallExpression + if (methodCall?.getMethodExpression() == it) methodCall else null + } + else { + null + } + }.filterNotNull() + } +} diff --git a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt index 023bad75cdf..7314ccfdb54 100644 --- a/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/TypeConverter.kt @@ -126,7 +126,7 @@ class TypeConverter(val converter: Converter) { } if (nullability == Nullability.Default) { - if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) && shouldGenerateDefaultInitializer(variable)) { + if (variable is PsiField && variable.hasModifierProperty(PsiModifier.PRIVATE) && shouldGenerateDefaultInitializer(converter.referenceSearcher, variable)) { return Nullability.Nullable } } @@ -134,7 +134,7 @@ class TypeConverter(val converter: Converter) { if (nullability == Nullability.Default) { val scope = searchScope(variable) if (scope != null) { - if (findVariableUsages(variable, scope).any { isNullableFromUsage(it) }) { + if (converter.referenceSearcher.findVariableUsages(variable, scope).any { isNullableFromUsage(it) }) { nullability = Nullability.Nullable } } @@ -147,7 +147,7 @@ class TypeConverter(val converter: Converter) { if (scope != null) { val parameters = method.getParameterList().getParameters() val parameterIndex = parameters.indexOf(variable) - for (call in findMethodCalls(method, scope)) { + for (call in converter.referenceSearcher.findMethodCalls(method, scope)) { val args = call.getArgumentList().getExpressions() if (args.size == parameters.size) { if (args[parameterIndex].nullability() == Nullability.Nullable) { @@ -211,7 +211,7 @@ class TypeConverter(val converter: Converter) { if (nullability == Nullability.Default) { val scope = searchScope(method) if (scope != null) { - if (findMethodCalls(method, scope).any { isNullableFromUsage(it) }) { + if (converter.referenceSearcher.findMethodCalls(method, scope).any { isNullableFromUsage(it) }) { nullability = Nullability.Nullable } } @@ -275,8 +275,8 @@ class TypeConverter(val converter: Converter) { private fun PsiVariable.isEffectivelyFinal(): Boolean { if (hasModifierProperty(PsiModifier.FINAL)) return true return when(this) { - is PsiLocalVariable -> !hasWriteAccesses(getContainingMethod()) - is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(getContainingClass()) else false + is PsiLocalVariable -> !hasWriteAccesses(converter.referenceSearcher, getContainingMethod()) + is PsiField -> if (hasModifierProperty(PsiModifier.PRIVATE)) !hasWriteAccesses(converter.referenceSearcher, getContainingClass()) else false else -> false } } diff --git a/j2k/src/org/jetbrains/jet/j2k/Utils.kt b/j2k/src/org/jetbrains/jet/j2k/Utils.kt index 9f1cdf95918..634d17c2f92 100644 --- a/j2k/src/org/jetbrains/jet/j2k/Utils.kt +++ b/j2k/src/org/jetbrains/jet/j2k/Utils.kt @@ -19,34 +19,16 @@ package org.jetbrains.jet.j2k import org.jetbrains.jet.lang.types.expressions.OperatorConventions import com.intellij.psi.* import com.intellij.psi.util.PsiUtil -import com.intellij.psi.search.LocalSearchScope -import com.intellij.psi.search.searches.ReferencesSearch import org.jetbrains.jet.j2k.ast.* import com.intellij.psi.util.PsiMethodUtil fun quoteKeywords(packageName: String): String = packageName.split("\\.").map { Identifier.toKotlin(it) }.joinToString(".") -fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection { - return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass()) -} +fun PsiVariable.countWriteAccesses(searcher: ReferenceSearcher, scope: PsiElement?): Int + = if (scope != null) searcher.findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0 -fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection { - return ReferencesSearch.search(method, LocalSearchScope(scope)).findAll().map { - if (it is PsiReferenceExpression) { - val methodCall = it.getParent() as? PsiMethodCallExpression - if (methodCall?.getMethodExpression() == it) methodCall else null - } - else { - null - } - }.filterNotNull() -} - -fun PsiVariable.countWriteAccesses(scope: PsiElement?): Int - = if (scope != null) findVariableUsages(this, scope).count { PsiUtil.isAccessedForWriting(it) } else 0 - -fun PsiVariable.hasWriteAccesses(scope: PsiElement?): Boolean - = if (scope != null) findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false +fun PsiVariable.hasWriteAccesses(searcher: ReferenceSearcher, scope: PsiElement?): Boolean + = if (scope != null) searcher.findVariableUsages(this, scope).any { PsiUtil.isAccessedForWriting(it) } else false fun getDefaultInitializer(field: Field): Expression? { val t = field.`type` @@ -68,11 +50,11 @@ fun getDefaultInitializer(field: Field): Expression? { return result?.assignNoPrototype() } -fun isVal(field: PsiField): Boolean { +fun isVal(searcher: ReferenceSearcher, field: PsiField): Boolean { if (field.hasModifierProperty(PsiModifier.FINAL)) return true if (!field.hasModifierProperty(PsiModifier.PRIVATE)) return false val containingClass = field.getContainingClass() ?: return false - val writes = findVariableUsages(field, containingClass).filter { PsiUtil.isAccessedForWriting(it) } + val writes = searcher.findVariableUsages(field, containingClass).filter { PsiUtil.isAccessedForWriting(it) } if (writes.size == 0) return true if (writes.size > 1) return false val write = writes.single() @@ -89,8 +71,8 @@ fun isVal(field: PsiField): Boolean { return false } -fun shouldGenerateDefaultInitializer(field: PsiField) - = field.getInitializer() == null && !(isVal(field) && field.hasWriteAccesses(field.getContainingClass())) +fun shouldGenerateDefaultInitializer(searcher: ReferenceSearcher, field: PsiField) + = field.getInitializer() == null && !(isVal(searcher, field) && field.hasWriteAccesses(searcher, field.getContainingClass())) fun isQualifierEmptyOrThis(ref: PsiReferenceExpression): Boolean { val qualifier = ref.getQualifierExpression() diff --git a/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt b/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt index 91d22cdf762..9e63198cd4c 100644 --- a/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt +++ b/j2k/src/org/jetbrains/jet/j2k/visitors/ForConverter.kt @@ -118,8 +118,8 @@ class ForConverter(private val statement: PsiForStatement, private val converter if (initialization is PsiDeclarationStatement) { val loopVar = initialization.getDeclaredElements().singleOrNull2() as? PsiLocalVariable if (loopVar != null - && !loopVar.hasWriteAccesses(body) - && !loopVar.hasWriteAccesses(condition) + && !loopVar.hasWriteAccesses(converter.referenceSearcher, body) + && !loopVar.hasWriteAccesses(converter.referenceSearcher, condition) && condition is PsiBinaryExpression) { val operationTokenType = condition.getOperationTokenType() val lowerBound = condition.getLOperand() diff --git a/j2k/tests/test/org/jetbrains/jet/j2k/test/AbstractJavaToKotlinConverterTest.kt b/j2k/tests/test/org/jetbrains/jet/j2k/test/AbstractJavaToKotlinConverterTest.kt index a8864a8fa60..9ea2698fbd4 100644 --- a/j2k/tests/test/org/jetbrains/jet/j2k/test/AbstractJavaToKotlinConverterTest.kt +++ b/j2k/tests/test/org/jetbrains/jet/j2k/test/AbstractJavaToKotlinConverterTest.kt @@ -36,6 +36,7 @@ import com.intellij.testFramework.LightProjectDescriptor import org.jetbrains.jet.plugin.JetWithJdkAndRuntimeLightProjectDescriptor import com.intellij.psi.PsiJavaFile import com.intellij.psi.PsiFile +import org.jetbrains.jet.j2k.ReferenceSearcherImpl public abstract class AbstractJavaToKotlinConverterTest() : LightCodeInsightFixtureTestCase() { val testHeaderPattern = Pattern.compile("//(element|expression|statement|method|class|file|comp)\n") @@ -125,14 +126,14 @@ public abstract class AbstractJavaToKotlinConverterTest() : LightCodeInsightFixt private fun elementToKotlin(text: String, settings: ConverterSettings, project: Project): String { val fileWithText = createJavaFile(text) - val converter = Converter.create(project, settings, FilesConversionScope(listOf(fileWithText)), J2kPostProcessor(fileWithText)) + val converter = Converter.create(project, settings, FilesConversionScope(listOf(fileWithText)), ReferenceSearcherImpl, J2kPostProcessor(fileWithText)) val element = fileWithText.getFirstChild()!! return converter.elementToKotlin(element) } private fun fileToKotlin(text: String, settings: ConverterSettings, project: Project): String { val file = createJavaFile(text) - val converter = Converter.create(project, settings, FilesConversionScope(listOf(file)), J2kPostProcessor(file)) + val converter = Converter.create(project, settings, FilesConversionScope(listOf(file)), ReferenceSearcherImpl, J2kPostProcessor(file)) return converter.elementToKotlin(file) }