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

This commit is contained in:
Daniil Ovchinnikov
2017-08-28 13:33:37 +03:00
parent dc1d54803f
commit b8153ebd95
11 changed files with 390 additions and 12 deletions
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* 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.
@@ -112,7 +112,7 @@ public class CreateFieldFromUsageFix extends CreateVarFromUsageFix {
public static void createFieldFromUsageTemplate(final PsiClass targetClass,
final Project project,
final ExpectedTypeInfo[] expectedTypes,
final Object expectedTypes,
final PsiField field,
final boolean createConstantField,
final PsiElement context) {
@@ -138,11 +138,11 @@ public class CreateFieldFromUsageFix extends CreateVarFromUsageFix {
});
}
private static boolean shouldCreateFinalMember(@NotNull PsiReferenceExpression ref, @NotNull PsiClass targetClass) {
public static boolean shouldCreateFinalMember(@NotNull PsiReferenceExpression ref, @NotNull PsiClass targetClass) {
if (!PsiTreeUtil.isAncestor(targetClass, ref, true)) {
return false;
}
final PsiElement element = PsiTreeUtil.getParentOfType(ref, PsiClassInitializer.class, PsiMethod.class);
final PsiElement element = PsiTreeUtil.getParentOfType(ref, PsiClassInitializer.class, PsiMethod.class, PsiField.class);
if (element instanceof PsiClassInitializer) {
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* 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.
@@ -153,7 +153,7 @@ public abstract class CreateFromUsageBaseFix extends BaseIntentionAction {
}
@Nullable("null means unable to open the editor")
protected static Editor positionCursor(@NotNull Project project, @NotNull PsiFile targetFile, @NotNull PsiElement element) {
public static Editor positionCursor(@NotNull Project project, @NotNull PsiFile targetFile, @NotNull PsiElement element) {
TextRange range = element.getTextRange();
LOG.assertTrue(range != null, element.getClass());
int textOffset = range.getStartOffset();
@@ -193,7 +193,7 @@ public abstract class CreateFromUsageBaseFix extends BaseIntentionAction {
}
}
protected static boolean shouldCreateStaticMember(PsiReferenceExpression ref, PsiClass targetClass) {
public static boolean shouldCreateStaticMember(PsiReferenceExpression ref, PsiClass targetClass) {
PsiExpression qualifierExpression = ref.getQualifierExpression();
while (qualifierExpression instanceof PsiParenthesizedExpression) {
@@ -253,7 +253,7 @@ public abstract class CreateFromUsageBaseFix extends BaseIntentionAction {
return null;
}
protected static PsiSubstitutor getTargetSubstitutor (PsiElement element) {
public static PsiSubstitutor getTargetSubstitutor (PsiElement element) {
if (element instanceof PsiNewExpression) {
JavaResolveResult result = ((PsiNewExpression)element).getClassOrAnonymousClassReference().advancedResolve(false);
PsiSubstitutor substitutor = result.getSubstitutor();
@@ -408,7 +408,7 @@ public abstract class CreateFromUsageBaseFix extends BaseIntentionAction {
return psiClass.getManager().isInProject(psiClass);
}
protected static void startTemplate (@NotNull Editor editor, final Template template, @NotNull final Project project) {
public static void startTemplate (@NotNull Editor editor, final Template template, @NotNull final Project project) {
startTemplate(editor, template, project, null);
}
@@ -658,7 +658,7 @@ public class CreateFromUsageUtils {
}
@NotNull
static ExpectedTypeInfo[] guessExpectedTypes(@NotNull PsiExpression expression, boolean allowVoidType) {
public static ExpectedTypeInfo[] guessExpectedTypes(@NotNull PsiExpression expression, boolean allowVoidType) {
PsiManager manager = expression.getManager();
GlobalSearchScope resolveScope = expression.getResolveScope();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
* 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.
@@ -72,7 +72,7 @@ public class JavaCreateFieldFromUsageHelper extends CreateFieldFromUsageHelper {
}
@Override
public PsiField insertFieldImpl(@NotNull PsiClass targetClass, @NotNull PsiField field, @NotNull PsiElement place) {
public PsiField insertFieldImpl(@NotNull PsiClass targetClass, @NotNull PsiField field, @Nullable PsiElement place) {
PsiMember enclosingContext = null;
PsiClass parentClass;
do {
@@ -20,7 +20,11 @@ import com.intellij.codeInsight.daemon.impl.quickfix.AddConstructorFix
import com.intellij.codeInsight.daemon.impl.quickfix.ModifierFix
import com.intellij.codeInsight.intention.AbstractIntentionAction
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.lang.java.actions.CreateEnumConstantAction
import com.intellij.lang.java.actions.CreateFieldAction
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.JvmElementActionsFactory
import com.intellij.lang.jvm.actions.MemberRequest
import com.intellij.lang.jvm.types.JvmType
@@ -124,6 +128,14 @@ class JavaElementActionsFactory(
}
}
override fun createAddFieldActions(targetClass: JvmClass, request: CreateFieldRequest): List<IntentionAction> {
val javaClass = targetClass.toJavaClassOrNull() ?: return emptyList()
return listOf(
CreateFieldAction(javaClass, request, false),
CreateFieldAction(javaClass, request, true),
CreateEnumConstantAction(javaClass, request)
)
}
}
class JavaElementRenderer {
@@ -0,0 +1,100 @@
/*
* 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.ExpectedTypeUtil
import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.positionCursor
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix.startTemplate
import com.intellij.codeInsight.daemon.impl.quickfix.EmptyExpression
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.lang.java.actions.Workaround.extractExpectedTypes
import com.intellij.lang.jvm.actions.CreateFieldRequest
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiEnumConstant
import com.intellij.psi.PsiFile
class CreateEnumConstantAction(targetClass: PsiClass, request: CreateFieldRequest) : CreateFieldActionBase(targetClass, request) {
private val myData: EnumConstantData?
get() {
val targetClass = myTargetClass.element
if (targetClass == null || !request.isValid) return null
return extractRenderData(targetClass, request)
}
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
val data = myData ?: return false
text = QuickFixBundle.message("create.enum.constant.from.usage.text", data.constantName)
return true
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val data = myData ?: return
val name = data.constantName
val targetClass = data.targetClass
val elementFactory = JavaPsiFacade.getElementFactory(project)!!
// add constant
var enumConstant: PsiEnumConstant
enumConstant = elementFactory.createEnumConstantFromText(name, null)
enumConstant = targetClass.add(enumConstant) as PsiEnumConstant
// start template
val constructor = targetClass.constructors.firstOrNull() ?: return
val parameters = constructor.parameterList.parameters
if (parameters.isEmpty()) return
val paramString = parameters.joinToString(",") { it.name ?: "" }
enumConstant = enumConstant.replace(elementFactory.createEnumConstantFromText("$name($paramString)", null)) as PsiEnumConstant
val builder = TemplateBuilderImpl(enumConstant)
val argumentList = enumConstant.argumentList!!
for (expression in argumentList.expressions) {
builder.replaceElement(expression, EmptyExpression())
}
enumConstant = forcePsiPostprocessAndRestoreElement(enumConstant)
val template = builder.buildTemplate()
val newEditor = positionCursor(project, targetClass.containingFile, enumConstant) ?: return
val range = enumConstant.textRange
newEditor.document.deleteString(range.startOffset, range.endOffset)
startTemplate(newEditor, template, project)
}
}
private class EnumConstantData(
val targetClass: PsiClass,
val constantName: String
)
private fun extractRenderData(targetClass: PsiClass, request: CreateFieldRequest): EnumConstantData? {
if (!targetClass.isEnum) return null
if (!checkExpectedTypes(request.fieldType, targetClass, targetClass.project)) return null
return EnumConstantData(targetClass, request.fieldName)
}
private fun checkExpectedTypes(types: Any?, targetClass: PsiClass, project: Project): Boolean {
val typeInfos = extractExpectedTypes(types) ?: return true
val enumType = JavaPsiFacade.getElementFactory(project).createType(targetClass)
return typeInfos.any {
ExpectedTypeUtil.matches(enumType, it)
}
}
@@ -0,0 +1,145 @@
/*
* 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.daemon.QuickFixBundle.message
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFieldFromUsageFix
import com.intellij.codeInsight.daemon.impl.quickfix.JavaCreateFieldFromUsageHelper
import com.intellij.lang.java.request.CreateFieldFromJavaUsageRequest
import com.intellij.lang.jvm.JvmModifier
import com.intellij.lang.jvm.actions.CreateFieldRequest
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.presentation.java.ClassPresentationUtil.getNameForClass
import com.intellij.psi.util.PsiUtil
internal class CreateFieldAction(
targetClass: PsiClass,
request: CreateFieldRequest,
private val constantField: Boolean
) : CreateFieldActionBase(targetClass, request) {
private val myData: FieldData?
get() {
val targetClass = myTargetClass.element
if (targetClass == null || !request.isValid) return null
return extractRenderData(targetClass, request, constantField)
}
override fun isAvailable(project: Project, editor: Editor?, file: PsiFile?): Boolean {
val data = myData ?: return false
val className = getNameForClass(data.targetClass, false)
text = if (constantField) {
message("create.constant.from.usage.full.text", data.fieldName, className)
}
else {
message("create.field.from.usage.full.text", data.fieldName, className)
}
return true
}
override fun invoke(project: Project, editor: Editor?, file: PsiFile?) {
val data = myData ?: return
val targetClass = data.targetClass
// render template field
val renderedField = JavaPsiFacade.getElementFactory(project).createField(data.fieldName, PsiType.INT)
// clean template modifiers
renderedField.modifierList?.let { list ->
list.firstChild?.let {
list.deleteChildRange(it, list.lastChild)
}
}
// setup actual modifiers
for (modifier in data.modifiers) {
PsiUtil.setModifierProperty(renderedField, modifier, true)
}
// insert field
val javaField = JavaCreateFieldFromUsageHelper().insertFieldImpl(targetClass, renderedField, data.anchor)
// setup template
CreateFieldFromUsageFix.createFieldFromUsageTemplate(
targetClass, project, data.fieldType, javaField, data.constant, data.reference
)
}
}
private class FieldData(
val targetClass: PsiClass,
val fieldName: String,
val fieldType: Any?,
val reference: PsiReferenceExpression?,
val anchor: PsiElement?,
val modifiers: Collection<String>,
val constant: Boolean
)
private fun extractRenderData(targetClass: PsiClass, request: CreateFieldRequest, renderConstant: Boolean): FieldData? {
val fieldName = request.fieldName
val requestedModifiers = request.modifiers
val canRender = run {
val constantRequested = request.constant || targetClass.isInterface || requestedModifiers.containsAll(constantModifiers)
if (renderConstant) {
constantRequested || fieldName.toUpperCase() == fieldName
}
else {
!constantRequested
}
}
if (!canRender) return null
val modifiersToRender = if (renderConstant) {
if (targetClass.isInterface) {
// interface fields are public static final implicitly, so modifiers don't have to be rendered
requestedModifiers - constantModifiers - visibilityModifiers
}
else {
// render static final explicitly
requestedModifiers + constantModifiers
}
}
else {
// render as is
requestedModifiers
}
val javaUsage = request as? CreateFieldFromJavaUsageRequest
return FieldData(
targetClass,
fieldName,
request.fieldType,
javaUsage?.reference,
javaUsage?.anchor,
modifiersToRender.map(JvmModifier::toPsi),
renderConstant
)
}
private val visibilityModifiers = setOf(
JvmModifier.PUBLIC,
JvmModifier.PROTECTED,
JvmModifier.PACKAGE_LOCAL,
JvmModifier.PRIVATE
)
private val constantModifiers = setOf(
JvmModifier.STATIC,
JvmModifier.FINAL
)
@@ -0,0 +1,35 @@
/*
* 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.daemon.QuickFixBundle.message
import com.intellij.codeInsight.intention.impl.BaseIntentionAction
import com.intellij.lang.jvm.actions.CreateFieldRequest
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.createSmartPointer
abstract class CreateFieldActionBase(targetClass: PsiClass, protected val request: CreateFieldRequest) : BaseIntentionAction() {
protected val myTargetClass = targetClass.createSmartPointer()
override fun getFamilyName(): String = message("create.field.from.usage.family")
override fun getElementToMakeWritable(currentFile: PsiFile): PsiElement? = myTargetClass.element
override fun startInWriteAction(): Boolean = true
}
@@ -0,0 +1,28 @@
/*
* 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.ExpectedTypeInfo;
import org.jetbrains.annotations.Nullable;
// workaround for KT-19388
class Workaround {
@Nullable
static ExpectedTypeInfo[] extractExpectedTypes(@Nullable Object types) {
return types instanceof ExpectedTypeInfo[] ? (ExpectedTypeInfo[])types : null;
}
}
@@ -0,0 +1,56 @@
/*
* 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.lang.java.JavaLanguage
import com.intellij.lang.jvm.JvmClass
import com.intellij.lang.jvm.JvmModifier
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifier.ModifierConstant
import com.intellij.psi.PsiTypeParameter
import com.intellij.psi.impl.compiled.ClsClassImpl
@ModifierConstant
fun JvmModifier.toPsi(): String = when (this) {
JvmModifier.PUBLIC -> PsiModifier.PUBLIC
JvmModifier.PROTECTED -> PsiModifier.PROTECTED
JvmModifier.PRIVATE -> PsiModifier.PRIVATE
JvmModifier.PACKAGE_LOCAL -> PsiModifier.PACKAGE_LOCAL
JvmModifier.STATIC -> PsiModifier.STATIC
JvmModifier.ABSTRACT -> PsiModifier.ABSTRACT
JvmModifier.FINAL -> PsiModifier.FINAL
JvmModifier.DEFAULT -> PsiModifier.DEFAULT
JvmModifier.NATIVE -> PsiModifier.NATIVE
JvmModifier.SYNCHRONIZED -> PsiModifier.NATIVE
JvmModifier.STRICTFP -> PsiModifier.STRICTFP
JvmModifier.TRANSIENT -> PsiModifier.TRANSIENT
JvmModifier.VOLATILE -> PsiModifier.VOLATILE
JvmModifier.TRANSITIVE -> PsiModifier.TRANSITIVE
}
/**
* Compiled classes, type parameters are not considered classes.
*
* @return Java PsiClass or `null` if the receiver is not a Java PsiClass
*/
fun JvmClass.toJavaClassOrNull(): PsiClass? {
if (this !is PsiClass) return null
if (this is PsiTypeParameter) return null
if (this is ClsClassImpl) return null
if (this.language != JavaLanguage.INSTANCE) return null
return this
}
@@ -60,6 +60,7 @@ create.class.from.usage.text=Create {0} ''{1}''
create.inner.class.from.usage.text=Create inner {0} ''{1}''
create.constant.from.usage.family=Create Constant From Usage
create.constant.from.usage.text=Create constant field ''{0}''
create.constant.from.usage.full.text=Create constant field ''{0}'' in ''{1}''
create.enum.constant.from.usage.text=Create enum constant ''{0}''
create.constructor.from.new.family=Create Constructor from New
create.constructor.from.new.text=Create constructor
@@ -70,6 +71,7 @@ create.constructor.matching.super=Create constructor matching super
super.class.constructors.chooser.title=Choose Super Class Constructors
create.field.from.usage.family=Create field from Usage
create.field.from.usage.text=Create field ''{0}''
create.field.from.usage.full.text=Create field ''{0}'' in ''{1}''
target.class.chooser.title=Choose Target Class
create.class.title=Create {0}
create.enum=enum