Make ReferenceSearcher a parameter for JavaToKotlinConverter

This commit is contained in:
Natalia Ukhorskaya
2014-10-07 17:20:35 +04:00
parent db86ade5fb
commit 141c88aaa9
10 changed files with 94 additions and 53 deletions
@@ -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(
@@ -89,6 +89,7 @@ public class ConvertJavaCopyPastePostProcessor() : CopyPastePostProcessor<TextBl
val converter = Converter.create(fileCopiedFrom.getProject(),
ConverterSettings.defaultSettings,
FilesConversionScope(listOf(fileCopiedFrom)),
ReferenceSearcherImpl,
J2kPostProcessor(fileCopiedTo))
val startOffsets = code.startOffsets
val endOffsets = code.endOffsets
@@ -266,7 +266,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
val (field, `type`) = parameterToField[parameter]!!
Parameter(field.declarationIdentifier(),
`type`,
if (isVal(field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
if (isVal(converter.referenceSearcher, field)) Parameter.VarValModifier.Val else Parameter.VarValModifier.Var,
converter.convertAnnotations(parameter) + converter.convertAnnotations(field),
converter.convertModifiers(field).filter { it in ACCESS_MODIFIERS },
defaultValue).assignPrototypes(listOf(parameter, field), CommentsAndSpacesInheritance(blankLinesBefore = false))
@@ -278,7 +278,7 @@ class ConstructorConverter(private val psiClass: PsiClass, private val converter
private fun findBackingFieldForConstructorParameter(parameter: PsiParameter, constructor: PsiMethod): Pair<PsiField, PsiStatement>? {
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
+14 -13
View File
@@ -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<String>): 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<PsiClass>()).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<Statement>()
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),
@@ -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 ""
@@ -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<PsiReferenceExpression>
fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression>
}
public object EmptyReferenceSearcher: ReferenceSearcher {
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement) = Collections.emptyList<PsiReferenceExpression>()
override fun findMethodCalls(method: PsiMethod, scope: PsiElement) = Collections.emptyList<PsiMethodCallExpression>()
}
public object ReferenceSearcherImpl: ReferenceSearcher {
override fun findVariableUsages(variable: PsiVariable, scope: PsiElement): Collection<PsiReferenceExpression> {
return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass<PsiReferenceExpression>())
}
override fun findMethodCalls(method: PsiMethod, scope: PsiElement): Collection<PsiMethodCallExpression> {
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()
}
}
@@ -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
}
}
+8 -26
View File
@@ -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<PsiReferenceExpression> {
return ReferencesSearch.search(variable, LocalSearchScope(scope)).findAll().filterIsInstance(javaClass<PsiReferenceExpression>())
}
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<PsiMethodCallExpression> {
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()
@@ -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()
@@ -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)
}