[jvm-lang] render Create Method requests in Java sources

This commit is contained in:
Daniil Ovchinnikov
2017-09-06 21:24:13 +03:00
parent 9e797ba058
commit 6d4b68c6df
4 changed files with 240 additions and 3 deletions
@@ -98,7 +98,7 @@ public class CreateFromUsageUtils {
return true;
}
static boolean isValidMethodReference(PsiReference reference, PsiMethodCallExpression call) {
public static boolean isValidMethodReference(PsiReference reference, PsiMethodCallExpression call) {
if (!(reference instanceof PsiJavaReference)) return false;
try {
JavaResolveResult candidate = ((PsiJavaReference) reference).advancedResolve(true);
@@ -985,10 +985,10 @@ public class CreateFromUsageUtils {
member.hasModifierProperty(PsiModifier.STATIC) == staticAccess).booleanValue();
}
private static class ParameterNameExpression extends Expression {
public static class ParameterNameExpression extends Expression {
private final String[] myNames;
private ParameterNameExpression(String[] names) {
public ParameterNameExpression(String[] names) {
myNames = names;
}
@@ -23,9 +23,11 @@ import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.java.JavaLanguage
import com.intellij.lang.java.actions.CreateEnumConstantAction
import com.intellij.lang.java.actions.CreateFieldAction
import com.intellij.lang.java.actions.CreateMethodAction
import com.intellij.lang.java.actions.toJavaClassOrNull
import com.intellij.lang.jvm.*
import com.intellij.lang.jvm.actions.CreateFieldRequest
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.lang.jvm.actions.JvmElementActionsFactory
import com.intellij.lang.jvm.actions.MemberRequest
import com.intellij.lang.jvm.types.JvmType
@@ -138,6 +140,14 @@ class JavaElementActionsFactory(
CreateEnumConstantAction(javaClass, request)
)
}
override fun createAddMethodActions(targetClass: JvmClass, request: CreateMethodRequest): List<IntentionAction> {
val javaClass = targetClass.toJavaClassOrNull() ?: return emptyList()
return listOf(
CreateMethodAction(javaClass, false, request),
CreateMethodAction(javaClass, true, request)
)
}
}
class JavaElementRenderer {
@@ -0,0 +1,225 @@
/*
* Copyright 2000-2017 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 com.intellij.lang.java.actions
import com.intellij.codeInsight.CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement
import com.intellij.codeInsight.daemon.QuickFixBundle.message
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.positionCursor
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils.*
import com.intellij.codeInsight.daemon.impl.quickfix.GuessTypeParameters
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateBuilder
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.lang.java.actions.Workaround.extractExpectedTypes
import com.intellij.lang.java.request.CreateMethodFromJavaUsageRequest
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateMethodRequest
import com.intellij.openapi.command.WriteCommandAction.runWriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.psi.*
import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.codeStyle.SuggestedNameInfo
import com.intellij.psi.presentation.java.ClassPresentationUtil.getNameForClass
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.PsiUtil
import com.intellij.psi.util.PsiUtil.setModifierProperty
import com.intellij.psi.util.createSmartPointer
/**
* @param abstract whether this action creates a method with explicit abstract modifier
*/
internal class CreateMethodAction(
targetClass: PsiClass,
private val abstract: Boolean,
private val myRequest: CreateMethodRequest
) : BaseIntentionAction() {
override fun getFamilyName(): String = message("create.method.from.usage.family")
override fun getElementToMakeWritable(currentFile: PsiFile): PsiElement? = myTargetClass.element
override fun startInWriteAction(): Boolean = true
private val myTargetClass = targetClass.createSmartPointer()
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
val targetClass = myTargetClass.element ?: return false
if (!myRequest.isValid) return false
val name = myRequest.methodName
if (!PsiNameHelper.getInstance(targetClass.project).isIdentifier(name)) return false
val requestedModifiers = myRequest.modifiers
val static = JvmModifier.STATIC in requestedModifiers
if (abstract && static) return false
if (abstract) {
if (!targetClass.hasModifierProperty(PsiModifier.ABSTRACT)) return false
if (targetClass.isInterface) return false
}
else if (static) {
// static method in interfaces are allowed starting with Java 8
if (targetClass.isInterface && !PsiUtil.isLanguageLevel8OrHigher(targetClass)) return false
// static methods in inner classes are disallowed JLS §8.1.3
if (targetClass.containingClass != null && !targetClass.hasModifierProperty(PsiModifier.STATIC)) return false
}
val className = getNameForClass(targetClass, false)
text = if (abstract) {
message("create.abstract.method.from.usage.full.text", name, className)
}
else {
message("create.method.from.usage.full.text", name, className)
}
return true
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val targetClass = myTargetClass.element ?: error("Target class was invalidated between isAvailable() and invoke()")
assert(myRequest.isValid) { "Request was invalidates between isAvailable() and invoke()" }
JavaMethodRenderer(project, abstract, targetClass, myRequest).doMagic()
}
}
private class JavaMethodRenderer(
val project: Project,
val abstract: Boolean,
val targetClass: PsiClass,
val request: CreateMethodRequest
) {
val factory = JavaPsiFacade.getElementFactory(project)!!
val requestedModifiers = request.modifiers
val javaUsage = request as? CreateMethodFromJavaUsageRequest
val withoutBody = abstract || targetClass.isInterface && JvmModifier.STATIC !in requestedModifiers
fun doMagic() {
var method = renderMethod()
method = insertMethod(method)
method = forcePsiPostprocessAndRestoreElement(method) ?: return
val builder = setupTemplate(method)
method = forcePsiPostprocessAndRestoreElement(method) ?: return
val template = builder.buildInlineTemplate()
startTemplate(method, template)
}
private fun renderMethod(): PsiMethod {
val method = factory.createMethod(request.methodName, PsiType.VOID)
var modifiersToRender = requestedModifiers
if (targetClass.isInterface) {
modifiersToRender -= (visibilityModifiers + JvmModifier.ABSTRACT)
}
else if (abstract) {
modifiersToRender += JvmModifier.ABSTRACT
}
for (modifier in modifiersToRender) {
setModifierProperty(method, modifier.toPsi(), true)
}
if (withoutBody) method.body?.delete()
return method
}
private fun insertMethod(method: PsiMethod): PsiMethod {
val anchor = javaUsage?.getAnchor(targetClass)
val inserted = if (anchor == null) {
targetClass.add(method)
}
else {
targetClass.addAfter(method, anchor)
}
return inserted as PsiMethod
}
private fun setupTemplate(method: PsiMethod): TemplateBuilderImpl {
val builder = TemplateBuilderImpl(method)
val guesser = GuessTypeParameters(project, factory, builder, javaUsage?.targetSubstitutor)
setupTypeElement(guesser, method.returnTypeElement, request.returnType)
builder.setupParameters(guesser, method)
builder.setEndVariableAfter(method.body ?: method)
return builder
}
private fun setupTypeElement(guesser: GuessTypeParameters, typeElement: PsiTypeElement?, types: Any?) {
typeElement ?: return
val expectedTypes = extractExpectedTypes(types) ?: emptyArray()
guesser.setupTypeElement(typeElement, expectedTypes, javaUsage?.context, targetClass)
}
private fun TemplateBuilder.setupParameters(guesser: GuessTypeParameters, method: PsiMethod) {
val parameters = request.parameters
if (parameters.isEmpty()) return
val codeStyleManager = CodeStyleManager.getInstance(project)!!
val parameterList = method.parameterList
val isInterface = targetClass.isInterface
//255 is the maximum number of method parameters
for (i in 0 until minOf(parameters.size, 255)) {
val parameterInfo = parameters[i]
val names = extractNames(parameterInfo.first) { "p" + i }
val dummyParameter = factory.createParameter(names.first(), PsiType.INT)
if (isInterface) {
setModifierProperty(dummyParameter, PsiModifier.FINAL, false)
}
val parameter = codeStyleManager.performActionWithFormatterDisabled(Computable {
parameterList.add(dummyParameter)
}) as PsiParameter
setupTypeElement(guesser, parameter.typeElement, parameterInfo.second)
setupParameterName(parameter, names)
}
}
private fun startTemplate(method: PsiMethod, template: Template) {
val targetFile = targetClass.containingFile
val newEditor = positionCursor(project, targetFile, method) ?: return
val templateListener = if (withoutBody) null else MyMethodBodyListener(project, newEditor, targetFile)
CreateFromUsageBaseFix.startTemplate(newEditor, template, project, templateListener, null)
}
}
private inline fun extractNames(suggestedNames: SuggestedNameInfo?, defaultName: () -> String): Array<out String> {
val names = (suggestedNames ?: SuggestedNameInfo.NULL_INFO).names
return if (names.isEmpty()) arrayOf(defaultName()) else names
}
private fun TemplateBuilder.setupParameterName(parameter: PsiParameter, names: Array<out String>) {
val nameIdentifier = parameter.nameIdentifier ?: return
val expression = ParameterNameExpression(names)
replaceElement(nameIdentifier, expression)
}
private class MyMethodBodyListener(val project: Project, val editor: Editor, val file: PsiFile) : TemplateEditingAdapter() {
override fun templateFinished(template: Template, brokenOff: Boolean) {
if (brokenOff) return
runWriteCommandAction(project) {
PsiDocumentManager.getInstance(project).commitDocument(editor.document)
val offset = editor.caretModel.offset
PsiTreeUtil.findElementOfClassAtOffset(file, offset - 1, PsiMethod::class.java, false)?.let { method ->
setupMethodBody(method)
setupEditor(method, editor)
}
}
}
}
@@ -93,7 +93,9 @@ create.local.from.instanceof.usage.family=Create Local Var from instanceof Usage
create.local.from.instanceof.usage.text=Insert ''({0}){1}'' declaration
create.method.from.usage.family=Create method from usage
create.method.from.usage.text=Create method ''{0}''
create.method.from.usage.full.text=Create method ''{0}'' in ''{1}''
create.abstract.method.from.usage.text=Create abstract method ''{0}''
create.abstract.method.from.usage.full.text=Create abstract method ''{0}'' in ''{1}''
create.parameter.from.usage.family=Create parameter from Usage
create.parameter.from.usage.text=Create parameter ''{0}''
create.property.from.usage.family=Create property From Usage