blob: d1a6031c8bf8eda5d225bef81a772d691c4b2495 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.generation.GenerateMembersUtil;
import com.intellij.codeInsight.intention.HighPriorityAction;
import com.intellij.codeInsight.intention.impl.TypeExpression;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.template.*;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* @author ven
*/
public class CreatePropertyFromUsageFix extends CreateFromUsageBaseFix implements HighPriorityAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.CreatePropertyFromUsageFix");
@NonNls private static final String FIELD_VARIABLE = "FIELD_NAME_VARIABLE";
@NonNls private static final String TYPE_VARIABLE = "FIELD_TYPE_VARIABLE";
@NonNls private static final String GET_PREFIX = "get";
@NonNls private static final String IS_PREFIX = "is";
@NonNls private static final String SET_PREFIX = "set";
public CreatePropertyFromUsageFix(@NotNull PsiMethodCallExpression methodCall) {
myMethodCall = methodCall;
}
protected final PsiMethodCallExpression myMethodCall;
@Override
@NotNull
public String getFamilyName() {
return QuickFixBundle.message("create.property.from.usage.family");
}
@Override
protected PsiElement getElement() {
if (!myMethodCall.isValid() || !myMethodCall.getManager().isInProject(myMethodCall)) return null;
return myMethodCall;
}
@Override
protected boolean isAvailableImpl(int offset) {
if (CreateMethodFromUsageFix.hasErrorsInArgumentList(myMethodCall)) return false;
PsiReferenceExpression ref = myMethodCall.getMethodExpression();
String methodName = myMethodCall.getMethodExpression().getReferenceName();
LOG.assertTrue(methodName != null);
String propertyName = PropertyUtil.getPropertyName(methodName);
if (propertyName == null || propertyName.isEmpty()) return false;
String getterOrSetter = null;
if (methodName.startsWith(GET_PREFIX) || methodName.startsWith(IS_PREFIX)) {
if (myMethodCall.getArgumentList().getExpressions().length != 0) return false;
getterOrSetter = QuickFixBundle.message("create.getter");
}
else if (methodName.startsWith(SET_PREFIX)) {
if (myMethodCall.getArgumentList().getExpressions().length != 1) return false;
getterOrSetter = QuickFixBundle.message("create.setter");
}
else {
LOG.error("Internal error in create property intention");
}
List<PsiClass> classes = getTargetClasses(myMethodCall);
if (classes.isEmpty()) return false;
if (!checkTargetClasses(classes, methodName)) return false;
for (PsiClass aClass : classes) {
if (!aClass.isInterface()) {
setText(getterOrSetter);
return true;
}
}
return false;
}
protected boolean checkTargetClasses(List<PsiClass> classes, String methodName) {
return true;
}
static class FieldExpression extends Expression {
private final String myDefaultFieldName;
private final PsiField myField;
private final PsiClass myClass;
private final PsiType[] myExpectedTypes;
public FieldExpression(PsiField field, PsiClass aClass, PsiType[] expectedTypes) {
myField = field;
myClass = aClass;
myExpectedTypes = expectedTypes;
myDefaultFieldName = field.getName();
}
@Override
public Result calculateResult(ExpressionContext context) {
return new TextResult(myDefaultFieldName);
}
@Override
public Result calculateQuickResult(ExpressionContext context) {
return new TextResult(myDefaultFieldName);
}
@Override
public LookupElement[] calculateLookupItems(ExpressionContext context) {
Set<LookupElement> set = new LinkedHashSet<LookupElement>();
set.add(JavaLookupElementBuilder.forField(myField).withTypeText(myField.getType().getPresentableText()));
PsiField[] fields = myClass.getFields();
for (PsiField otherField : fields) {
if (!myDefaultFieldName.equals(otherField.getName())) {
PsiType otherType = otherField.getType();
for (PsiType type : myExpectedTypes) {
if (type.equals(otherType)) {
set.add(JavaLookupElementBuilder.forField(otherField).withTypeText(otherType.getPresentableText()));
}
}
}
}
if (set.size() < 2) return null;
return set.toArray(new LookupElement[set.size()]);
}
}
@Override
@NotNull
protected List<PsiClass> getTargetClasses(PsiElement element) {
List<PsiClass> all = super.getTargetClasses(element);
if (all.isEmpty()) return all;
List<PsiClass> nonInterfaces = new ArrayList<PsiClass>();
for (PsiClass aClass : all) {
if (!aClass.isInterface()) nonInterfaces.add(aClass);
}
return nonInterfaces;
}
@Override
protected void invokeImpl(PsiClass targetClass) {
PsiManager manager = myMethodCall.getManager();
final Project project = manager.getProject();
PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
boolean isStatic = false;
PsiExpression qualifierExpression = myMethodCall.getMethodExpression().getQualifierExpression();
if (qualifierExpression != null) {
PsiReference reference = qualifierExpression.getReference();
if (reference != null) {
isStatic = reference.resolve() instanceof PsiClass;
}
}
else {
PsiMethod method = PsiTreeUtil.getParentOfType(myMethodCall, PsiMethod.class);
if (method != null) {
isStatic = method.hasModifierProperty(PsiModifier.STATIC);
}
}
String fieldName = getVariableName(myMethodCall, isStatic);
LOG.assertTrue(fieldName != null);
String callText = myMethodCall.getMethodExpression().getReferenceName();
LOG.assertTrue(callText != null, myMethodCall.getMethodExpression());
PsiType[] expectedTypes;
PsiType type;
PsiField field = targetClass.findFieldByName(fieldName, true);
if (callText.startsWith(GET_PREFIX)) {
expectedTypes = field != null ? new PsiType[]{field.getType()} : CreateFromUsageUtils.guessType(myMethodCall, false);
type = expectedTypes[0];
}
else if (callText.startsWith(IS_PREFIX)) {
type = PsiType.BOOLEAN;
expectedTypes = new PsiType[]{type};
}
else {
type = myMethodCall.getArgumentList().getExpressions()[0].getType();
if (type == null || PsiType.NULL.equals(type)) type = PsiType.getJavaLangObject(manager, myMethodCall.getResolveScope());
expectedTypes = new PsiType[]{type};
}
positionCursor(project, targetClass.getContainingFile(), targetClass);
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
if (field == null) {
field = factory.createField(fieldName, type);
PsiUtil.setModifierProperty(field, PsiModifier.STATIC, isStatic);
}
PsiMethod accessor;
PsiElement fieldReference;
PsiElement typeReference;
PsiCodeBlock body;
if (callText.startsWith(GET_PREFIX) || callText.startsWith(IS_PREFIX)) {
accessor = (PsiMethod)targetClass.add(GenerateMembersUtil.generateGetterPrototype(field));
body = accessor.getBody();
LOG.assertTrue(body != null, accessor.getText());
fieldReference = ((PsiReturnStatement)body.getStatements()[0]).getReturnValue();
typeReference = accessor.getReturnTypeElement();
}
else {
accessor = (PsiMethod)targetClass.add(PropertyUtil.generateSetterPrototype(field, targetClass));
body = accessor.getBody();
LOG.assertTrue(body != null, accessor.getText());
PsiAssignmentExpression expr = (PsiAssignmentExpression)((PsiExpressionStatement)body.getStatements()[0]).getExpression();
fieldReference = ((PsiReferenceExpression)expr.getLExpression()).getReferenceNameElement();
typeReference = accessor.getParameterList().getParameters()[0].getTypeElement();
}
accessor.setName(callText);
PsiUtil.setModifierProperty(accessor, PsiModifier.STATIC, isStatic);
TemplateBuilderImpl builder = new TemplateBuilderImpl(accessor);
builder.replaceElement(typeReference, TYPE_VARIABLE, new TypeExpression(project, expectedTypes), true);
builder.replaceElement(fieldReference, FIELD_VARIABLE, new FieldExpression(field, targetClass, expectedTypes), true);
builder.setEndVariableAfter(body.getLBrace());
accessor = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(accessor);
LOG.assertTrue(accessor != null);
targetClass = accessor.getContainingClass();
LOG.assertTrue(targetClass != null);
Template template = builder.buildTemplate();
TextRange textRange = accessor.getTextRange();
final PsiFile file = targetClass.getContainingFile();
final Editor editor = positionCursor(project, targetClass.getContainingFile(), accessor);
if (editor == null) return;
editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset());
editor.getCaretModel().moveToOffset(textRange.getStartOffset());
final boolean isStatic1 = isStatic;
startTemplate(editor, template, project, new TemplateEditingAdapter() {
@Override
public void beforeTemplateFinished(final TemplateState state, Template template) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
String fieldName = state.getVariableValue(FIELD_VARIABLE).getText();
if (!PsiNameHelper.getInstance(project).isIdentifier(fieldName)) return;
String fieldType = state.getVariableValue(TYPE_VARIABLE).getText();
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
PsiClass aClass = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (aClass == null) return;
PsiField field = aClass.findFieldByName(fieldName, true);
if (field != null){
CreatePropertyFromUsageFix.this.beforeTemplateFinished(aClass, field);
return;
}
PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
try {
PsiType type = factory.createTypeFromText(fieldType, aClass);
try {
field = factory.createField(fieldName, type);
field = (PsiField)aClass.add(field);
PsiUtil.setModifierProperty(field, PsiModifier.STATIC, isStatic1);
CreatePropertyFromUsageFix.this.beforeTemplateFinished(aClass, field);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
catch (IncorrectOperationException e) {
}
}
});
}
@Override
public void templateFinished(Template template, boolean brokenOff) {
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
final int offset = editor.getCaretModel().getOffset();
final PsiMethod generatedMethod = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethod.class, false);
if (generatedMethod != null) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
CodeStyleManager.getInstance(project).reformat(generatedMethod);
}
});
}
}
});
}
protected void beforeTemplateFinished(PsiClass aClass, PsiField field) {
if (myMethodCall.isValid()) {
positionCursor(myMethodCall.getProject(), myMethodCall.getContainingFile(), myMethodCall);
}
}
private static String getVariableName(PsiMethodCallExpression methodCall, boolean isStatic) {
JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(methodCall.getProject());
String methodName = methodCall.getMethodExpression().getReferenceName();
String propertyName = PropertyUtil.getPropertyName(methodName);
if (propertyName != null && !propertyName.isEmpty()) {
VariableKind kind = isStatic ? VariableKind.STATIC_FIELD : VariableKind.FIELD;
return codeStyleManager.propertyNameToVariableName(propertyName, kind);
}
return null;
}
@Override
protected boolean isValidElement(PsiElement element) {
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)element;
return methodCall.getMethodExpression().resolve() != null;
}
}