[jvm-lang] java: port _Create getter/setter from usage_ actions

- extract PropertyRenderer for reusing;
- make them work with type parameter guesser.
This commit is contained in:
Daniil Ovchinnikov
2018-02-13 01:14:19 +03:00
parent 6ca0d49785
commit 544ee7980f
12 changed files with 242 additions and 100 deletions

View File

@@ -468,7 +468,6 @@ public class HighlightMethodUtil {
else {
TextRange range = getFixRange(methodCall);
registerUsageFixes(methodCall, highlightInfo, range);
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createStaticImportMethodFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createQualifyStaticMethodCallFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.addMethodQualifierFix(methodCall));
@@ -498,6 +497,7 @@ public class HighlightMethodUtil {
else {
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateMethodFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateAbstractMethodFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, range, QUICK_FIX_FACTORY.createCreateGetterSetterPropertyFromUsageFix(methodCall));
}
}
@@ -849,7 +849,6 @@ public class HighlightMethodUtil {
registerUsageFixes(methodCall, highlightInfo, fixRange);
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromSuperFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreateConstructorFromThisFix(methodCall));
QuickFixAction.registerQuickFixAction(highlightInfo, fixRange, QUICK_FIX_FACTORY.createCreatePropertyFromUsageFix(methodCall));
CandidateInfo[] methodCandidates = resolveHelper.getReferencedMethodCandidates(methodCall, false);
CastMethodArgumentFix.REGISTRAR.registerCastActions(methodCandidates, methodCall, highlightInfo, fixRange);
PermuteArgumentsFix.registerFix(highlightInfo, methodCall, methodCandidates, fixRange);

View File

@@ -38,3 +38,15 @@ object CreatePropertyActionGroup : JvmActionGroup {
return message("create.property.from.usage.text", requireNotNull(data?.entityName))
}
}
object CreateReadOnlyPropertyActionGroup : JvmActionGroup {
override fun getDisplayText(data: JvmActionGroup.RenderData?): String {
return message("create.getter")
}
}
object CreateWriteOnlyPropertyActionGroup : JvmActionGroup {
override fun getDisplayText(data: JvmActionGroup.RenderData?): String {
return message("create.setter")
}
}

View File

@@ -87,6 +87,8 @@ class JavaElementActionsFactory(private val renderer: JavaElementRenderer) : Jvm
}
if (!javaClass.isInterface) {
result += CreatePropertyAction(javaClass, request)
result += CreateGetterWithFieldAction(javaClass, request)
result += CreateSetterWithFieldAction(javaClass, request)
}
return result
}

View File

@@ -0,0 +1,39 @@
// Copyright 2000-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 file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.daemon.QuickFixBundle.message
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleGetterPrototype
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.CreateReadOnlyPropertyActionGroup
import com.intellij.lang.jvm.actions.JvmActionGroup
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
/**
* This action renders a read-only property (field + getter) in Java class when getter is requested.
*/
internal class CreateGetterWithFieldAction(target: PsiClass, request: CreateMethodRequest) : CreatePropertyActionBase(target, request) {
override fun getActionGroup(): JvmActionGroup = CreateReadOnlyPropertyActionGroup
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
return super.isAvailable(project, editor, file) && propertyInfo.second != PropertyKind.SETTER
}
override fun getText(): String = message("create.getter")
override fun createRenderer(project: Project) = object : PropertyRenderer(project, target, request, propertyInfo) {
override fun fillTemplate(builder: TemplateBuilderImpl): RangeExpression {
val prototypeField = generatePrototypeField()
val prototype = generateSimpleGetterPrototype(prototypeField)
val accessor = insertAccessor(prototype)
val data = accessor.extractGetterTemplateData()
return builder.setupInput(data)
}
}
}

View File

@@ -1,44 +1,26 @@
// Copyright 2000-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 file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement
import com.intellij.codeInsight.ExpectedTypeInfo
import com.intellij.codeInsight.daemon.QuickFixBundle.message
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.positionCursor
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.ParameterNameExpression
import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleGetterPrototype
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleSetterPrototype
import com.intellij.codeInsight.template.TemplateBuilder
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.VariableNode
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.java.beans.PropertyKind.*
import com.intellij.lang.java.request.CreateMethodFromJavaUsageRequest
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.CreatePropertyActionGroup
import com.intellij.lang.jvm.actions.JvmActionGroup
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
import com.intellij.psi.presentation.java.ClassPresentationUtil.getNameForClass
import com.intellij.psi.util.PropertyUtilBase.getAccessorName
import com.intellij.util.component1
import com.intellij.util.component2
/**
* This action renders a property (field + getter + setter) in Java class when getter or a setter is requested.
*/
internal class CreatePropertyAction(target: PsiClass, request: CreateMethodRequest) : CreatePropertyActionBase(target, request) {
companion object {
private const val SETTER_PARAM_NAME = "SETTER_PARAM_NAME"
}
override fun getActionGroup(): JvmActionGroup = CreatePropertyActionGroup
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
@@ -61,61 +43,32 @@ internal class CreatePropertyAction(target: PsiClass, request: CreateMethodReque
override fun getText(): String = message("create.property.from.usage.full.text", propertyInfo.first, getNameForClass(target, false))
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val factory = JavaPsiFacade.getInstance(project).elementFactory
val codeStyleManager = JavaCodeStyleManager.getInstance(project)!!
override fun createRenderer(project: Project): PropertyRenderer = object : PropertyRenderer(project, target, request, propertyInfo) {
val (propertyName, propertyKind) = propertyInfo
val target = target
val isStatic = JvmModifier.STATIC in request.modifiers
val suggestedFieldName = run {
val kind = if (isStatic) VariableKind.STATIC_FIELD else VariableKind.FIELD
codeStyleManager.propertyNameToVariableName(propertyName, kind)
private fun generatePrototypes(): Pair<PsiMethod, PsiMethod> {
val prototypeField = generatePrototypeField()
val getter = generateSimpleGetterPrototype(prototypeField)
val setter = generateSimpleSetterPrototype(prototypeField, target)
return getter to setter
}
fun insertPrototypes(): Pair<PsiMethod, PsiMethod> {
val prototypeType = if (propertyKind == BOOLEAN_GETTER) PsiType.BOOLEAN else PsiType.VOID
val field = factory.createField(suggestedFieldName, prototypeType).setStatic(isStatic)
val getterPrototype = generateSimpleGetterPrototype(field)
val setterPrototype = generateSimpleSetterPrototype(field, target)
private fun insertPrototypes(): Pair<PsiMethod, PsiMethod> {
val (getterPrototype, setterPrototype) = generatePrototypes()
return if (propertyKind == SETTER) {
// Technology isn't there yet. See related: WEB-26575.
// We can't recalculate template segments which start before the current segment,
// so we add the setter before the getter.
val setter = forcePsiPostprocessAndRestoreElement(target.add(setterPrototype)) as PsiMethod
val getter = forcePsiPostprocessAndRestoreElement(target.add(getterPrototype)) as PsiMethod
val setter = insertAccessor(setterPrototype)
val getter = insertAccessor(getterPrototype)
getter to setter
}
else {
val getter = forcePsiPostprocessAndRestoreElement(target.add(getterPrototype)) as PsiMethod
val setter = forcePsiPostprocessAndRestoreElement(target.add(setterPrototype)) as PsiMethod
val getter = insertAccessor(getterPrototype)
val setter = insertAccessor(setterPrototype)
getter to setter
}
}
val (getter, setter) = insertPrototypes()
val expectedTypes: List<ExpectedTypeInfo> = when (propertyKind) {
PropertyKind.GETTER -> extractExpectedTypes(project, request.returnType)
PropertyKind.BOOLEAN_GETTER -> listOf(PsiType.BOOLEAN.toExpectedType())
PropertyKind.SETTER -> extractExpectedTypes(project, request.parameters.single().second)
}
fun TemplateBuilder.createTemplateContext(): TemplateContext {
val substitutor = request.targetSubstitutor.toPsiSubstitutor(project)
val guesserContext = (request as? CreateMethodFromJavaUsageRequest)?.context
val guesser = GuessTypeParameters(project, factory, this, substitutor)
return TemplateContext(project, factory, target, this, guesser, guesserContext)
}
val targetFile = target.containingFile
val targetDocument = requireNotNull(targetFile.viewProvider.document)
val targetEditor = positionCursor(project, targetFile, target) ?: return
/**
* Given user want to create a property from a getter reference, such as getFoo.
* In this case we insert both dummy getter and dummy setter.
@@ -130,41 +83,25 @@ internal class CreatePropertyAction(target: PsiClass, request: CreateMethodReque
*
* 3. Setter parameter name template is added in any case.
*/
fun TemplateBuilderImpl.setupTemplate(input: AccessorTemplateData, mirror: AccessorTemplateData): RangeExpression {
val templateTypeElement = createTemplateContext().setupTypeElement(input.typeElement, expectedTypes)
val typeExpression = RangeExpression(targetDocument, templateTypeElement.textRange)
replaceElement(mirror.typeElement, typeExpression, false) // copy type text to mirror
override fun fillTemplate(builder: TemplateBuilderImpl): RangeExpression {
val (getter, setter) = insertPrototypes()
val fieldExpression = FieldExpression(project, target, suggestedFieldName) { typeExpression.text }
replaceElement(input.fieldRef, FIELD_VARIABLE, fieldExpression, true)
replaceElement(mirror.fieldRef, VariableNode(FIELD_VARIABLE, null), false) // copy field name to mirror
val getterData = getter.extractGetterTemplateData()
val setterData = setter.extractSetterTemplateData()
input.endElement?.let(::setEndVariableAfter)
val getterTemplate = propertyKind != SETTER
val inputData = if (getterTemplate) getterData else setterData
val mirrorData = if (getterTemplate) setterData else getterData
val typeExpression = builder.setupInput(inputData)
builder.setupMirror(mirrorData, typeExpression)
builder.setupSetterParameter(setterData)
return typeExpression
}
fun TemplateBuilderImpl.setupSetterParameter(data: SetterTemplateData) {
val suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, propertyName, null, null)
val setterParameterExpression = ParameterNameExpression(suggestedNameInfo.names)
replaceElement(data.parameterName, SETTER_PARAM_NAME, setterParameterExpression, true)
replaceElement(data.parameterRef, VariableNode(SETTER_PARAM_NAME, null), false) // copy setter parameter name to mirror
private fun TemplateBuilderImpl.setupMirror(mirror: AccessorTemplateData, typeExpression: RangeExpression) {
replaceElement(mirror.typeElement, typeExpression, false) // copy type text to mirror
replaceElement(mirror.fieldRef, VariableNode(FIELD_VARIABLE, null), false) // copy field name to mirror
}
val getterData = getter.extractGetterTemplateData()
val setterData = setter.extractSetterTemplateData()
val builder = TemplateBuilderImpl(target)
val typeExpression = if (propertyKind == SETTER) {
builder.setupTemplate(setterData, getterData)
}
else {
builder.setupTemplate(getterData, setterData)
}
builder.setupSetterParameter(setterData)
val template = builder.buildInlineTemplate().apply {
isToShortenLongNames = true
}
val listener = InsertMissingFieldTemplateListener(project, target, typeExpression, isStatic)
TemplateManager.getInstance(project).startTemplate(targetEditor, template, listener)
}
}

View File

@@ -8,7 +8,6 @@ import com.intellij.lang.jvm.actions.JvmActionGroup
import com.intellij.lang.jvm.actions.JvmGroupIntentionAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Pair
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNameHelper
@@ -47,4 +46,10 @@ internal abstract class CreatePropertyActionBase(
protected val propertyInfo: Pair<String, PropertyKind> get() = requireNotNull(doGetPropertyInfo()).toNotNull()
override fun getRenderData() = JvmActionGroup.RenderData { propertyInfo.first }
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
createRenderer(project).doRender()
}
abstract fun createRenderer(project: Project): PropertyRenderer
}

View File

@@ -0,0 +1,41 @@
// Copyright 2000-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 file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.daemon.QuickFixBundle.message
import com.intellij.codeInsight.generation.GenerateMembersUtil.generateSimpleSetterPrototype
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.CreateWriteOnlyPropertyActionGroup
import com.intellij.lang.jvm.actions.JvmActionGroup
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
/**
* This action renders a write-only property (field + setter) in Java class when setter is requested.
*/
internal class CreateSetterWithFieldAction(target: PsiClass, request: CreateMethodRequest) : CreatePropertyActionBase(target, request) {
override fun getActionGroup(): JvmActionGroup = CreateWriteOnlyPropertyActionGroup
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
return super.isAvailable(project, editor, file) && propertyInfo.second == PropertyKind.SETTER
}
override fun getText(): String = message("create.setter")
override fun createRenderer(project: Project) = object : PropertyRenderer(project, target, request, propertyInfo) {
override fun fillTemplate(builder: TemplateBuilderImpl): RangeExpression {
val prototypeField = generatePrototypeField()
val prototype = generateSimpleSetterPrototype(prototypeField, target)
val accessor = insertAccessor(prototype)
val data = accessor.extractSetterTemplateData()
val typeExpression = builder.setupInput(data)
builder.setupSetterParameter(data)
return typeExpression
}
}
}

View File

@@ -0,0 +1,103 @@
// Copyright 2000-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 file.
package com.intellij.lang.java.actions
import com.intellij.codeInsight.CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement
import com.intellij.codeInsight.ExpectedTypeInfo
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.positionCursor
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.ParameterNameExpression
import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.codeInsight.template.impl.VariableNode
import com.intellij.lang.java.beans.PropertyKind
import com.intellij.lang.java.request.CreateMethodFromJavaUsageRequest
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.codeStyle.VariableKind
internal abstract class PropertyRenderer(
private val project: Project,
private val target: PsiClass,
private val request: CreateMethodRequest,
nameKind: Pair<String, PropertyKind>
) {
protected val factory = JavaPsiFacade.getInstance(project).elementFactory
private val codeStyleManager = JavaCodeStyleManager.getInstance(project)!!
private val javaUsage = request as? CreateMethodFromJavaUsageRequest
private val isStatic = JvmModifier.STATIC in request.modifiers
private val propertyName = nameKind.first
protected val propertyKind = nameKind.second
private val suggestedFieldName = run {
val kind = if (isStatic) VariableKind.STATIC_FIELD else VariableKind.FIELD
codeStyleManager.propertyNameToVariableName(propertyName, kind)
}
protected fun generatePrototypeField(): PsiField {
val prototypeType = if (propertyKind == PropertyKind.BOOLEAN_GETTER) PsiType.BOOLEAN else PsiType.VOID
return factory.createField(suggestedFieldName, prototypeType).setStatic(isStatic)
}
private val expectedTypes: List<ExpectedTypeInfo> = when (propertyKind) {
PropertyKind.GETTER -> extractExpectedTypes(project, request.returnType).orObject(target)
PropertyKind.BOOLEAN_GETTER -> listOf(PsiType.BOOLEAN.toExpectedType())
PropertyKind.SETTER -> extractExpectedTypes(project, request.parameters.single().second).orObject(target)
}
private lateinit var targetDocument: Document
private lateinit var targetEditor: Editor
private fun navigate(): Boolean {
val targetFile = target.containingFile ?: return false
targetDocument = targetFile.viewProvider.document ?: return false
targetEditor = positionCursor(project, targetFile, target) ?: return false
return true
}
fun doRender() {
if (!navigate()) return
val builder = TemplateBuilderImpl(target)
val typeExpression = fillTemplate(builder)
val template = builder.buildInlineTemplate().apply {
isToShortenLongNames = true
}
val listener = InsertMissingFieldTemplateListener(project, target, typeExpression, isStatic)
TemplateManager.getInstance(project).startTemplate(targetEditor, template, listener)
}
protected abstract fun fillTemplate(builder: TemplateBuilderImpl): RangeExpression
protected fun insertAccessor(prototype: PsiMethod): PsiMethod {
return forcePsiPostprocessAndRestoreElement(target.add(prototype)) as PsiMethod
}
private fun TemplateBuilderImpl.createTemplateContext(): TemplateContext {
val substitutor = request.targetSubstitutor.toPsiSubstitutor(project)
val guesser = GuessTypeParameters(project, factory, this, substitutor)
return TemplateContext(project, factory, target, this, guesser, javaUsage?.context)
}
protected fun TemplateBuilderImpl.setupInput(input: AccessorTemplateData): RangeExpression {
val templateTypeElement = createTemplateContext().setupTypeElement(input.typeElement, expectedTypes)
val typeExpression = RangeExpression(targetDocument, templateTypeElement.textRange)
val fieldExpression = FieldExpression(project, target, suggestedFieldName) { typeExpression.text }
replaceElement(input.fieldRef, FIELD_VARIABLE, fieldExpression, true)
input.endElement?.let(::setEndVariableAfter)
return typeExpression
}
protected fun TemplateBuilderImpl.setupSetterParameter(data: SetterTemplateData) {
val suggestedNameInfo = codeStyleManager.suggestVariableName(VariableKind.PARAMETER, propertyName, null, null)
val setterParameterExpression = ParameterNameExpression(suggestedNameInfo.names)
replaceElement(data.parameterName, SETTER_PARAM_NAME, setterParameterExpression, true)
replaceElement(data.parameterRef, VariableNode(SETTER_PARAM_NAME, null), false) // copy setter parameter name to mirror
}
}

View File

@@ -86,3 +86,10 @@ internal inline fun extractNames(suggestedNames: SuggestedNameInfo?, defaultName
}
internal fun PsiType.toExpectedType() = createInfo(this, ExpectedTypeInfo.TYPE_STRICTLY, this, TailType.NONE)
internal fun List<ExpectedTypeInfo>.orObject(context: PsiElement): List<ExpectedTypeInfo> {
if (isEmpty() || get(0).type == PsiType.VOID) {
return listOf(PsiType.getJavaLangObject(context.manager, context.resolveScope).toExpectedType())
}
return this
}

View File

@@ -4,6 +4,7 @@ package com.intellij.lang.java.actions
import com.intellij.psi.*
internal const val FIELD_VARIABLE = "FIELD_NAME_VARIABLE"
internal const val SETTER_PARAM_NAME = "SETTER_PARAM_NAME"
internal interface AccessorTemplateData {
val fieldRef: PsiElement

View File

@@ -1,12 +1,11 @@
// "Create Getter" "true"
public class Test {
Integer field;
public foo() {
getField();
}
public Integer getField() {
public Object getField() {
return field;
}
}

View File

@@ -8,10 +8,7 @@ import com.intellij.openapi.util.Pair as JBPair
operator fun <A> JBPair<A, *>.component1(): A = first
operator fun <A> JBPair<*, A>.component2(): A = second
// This function helps to get rid of platform types.
fun <A : Any, B : Any> JBPair<A?, B?>.toNotNull(): JBPair<A, B> {
requireNotNull(first)
requireNotNull(second)
@Suppress("UNCHECKED_CAST")
return this as JBPair<A, B>
// This function helps to get rid of platform types
fun <A : Any, B : Any> JBPair<A?, B?>.toNotNull(): Pair<A, B> {
return requireNotNull(first) to requireNotNull(second)
}