"initialize variable" & "initialize in constructor" suggests default constructors (with inheritor's if collection) (IDEA-145431)

This commit is contained in:
Dmitry Batkovich
2015-11-06 15:56:50 +03:00
parent fb32abd2fe
commit e8805cb2cc
8 changed files with 171 additions and 77 deletions
@@ -221,19 +221,26 @@ public class JavaInheritorsGetter extends CompletionProvider<CompletionParameter
public static void processInheritors(final CompletionParameters parameters,
final Collection<PsiClassType> expectedClassTypes,
final PrefixMatcher matcher, final Consumer<PsiType> consumer) {
processInheritors(parameters.getOriginalFile(), parameters.getPosition(), expectedClassTypes, matcher, consumer);
}
public static void processInheritors(final PsiFile file,
final PsiElement context,
final Collection<PsiClassType> expectedClassTypes,
final PrefixMatcher matcher, final Consumer<PsiType> consumer) {
//quick
if (!processMostProbableInheritors(parameters, expectedClassTypes, consumer)) return;
if (!processMostProbableInheritors(file, context, expectedClassTypes, consumer)) return;
//long
for (final PsiClassType type : expectedClassTypes) {
CodeInsightUtil.processSubTypes(type, parameters.getPosition(), false, matcher, consumer);
CodeInsightUtil.processSubTypes(type, context, false, matcher, consumer);
}
}
private static boolean processMostProbableInheritors(CompletionParameters parameters,
private static boolean processMostProbableInheritors(PsiFile contextFile,
PsiElement context,
Collection<PsiClassType> expectedClassTypes,
Consumer<PsiType> consumer) {
PsiFile file = parameters.getOriginalFile();
for (final PsiClassType type : expectedClassTypes) {
consumer.consume(type);
@@ -243,15 +250,15 @@ public class JavaInheritorsGetter extends CompletionProvider<CompletionParameter
final PsiSubstitutor baseSubstitutor = baseResult.getSubstitutor();
final Processor<PsiClass> processor = CodeInsightUtil.createInheritorsProcessor(parameters.getPosition(), type, 0, false,
final Processor<PsiClass> processor = CodeInsightUtil.createInheritorsProcessor(context, type, 0, false,
consumer, baseClass, baseSubstitutor);
final StatisticsInfo[] stats = StatisticsManager.getInstance().getAllValues(JavaStatisticsManager.getAfterNewKey(type));
for (final StatisticsInfo statisticsInfo : stats) {
final String value = statisticsInfo.getValue();
if (value.startsWith(JavaStatisticsManager.CLASS_PREFIX)) {
final String qname = value.substring(JavaStatisticsManager.CLASS_PREFIX.length());
final PsiClass psiClass = JavaPsiFacade.getInstance(file.getProject()).findClass(qname, file.getResolveScope());
if (psiClass != null && !PsiTreeUtil.isAncestor(file, psiClass, true) && !processor.process(psiClass)) break;
final PsiClass psiClass = JavaPsiFacade.getInstance(contextFile.getProject()).findClass(qname, contextFile.getResolveScope());
if (psiClass != null && !PsiTreeUtil.isAncestor(contextFile, psiClass, true) && !processor.process(psiClass)) break;
}
}
}
@@ -16,18 +16,33 @@
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.lookup.*;
import com.intellij.codeInsight.template.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.intellij.util.containers.ContainerUtil.*;
public class AddVariableInitializerFix implements IntentionAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.AddReturnFix");
@@ -62,9 +77,10 @@ public class AddVariableInitializerFix implements IntentionAction {
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
if (!FileModificationService.getInstance().prepareFileForWrite(myVariable.getContainingFile())) return;
String initializerText = suggestInitializer();
PsiElementFactory factory = JavaPsiFacade.getInstance(myVariable.getProject()).getElementFactory();
PsiExpression initializer = factory.createExpressionFromText(initializerText, myVariable);
final LookupElement[] suggestedInitializers = suggestInitializer(myVariable);
LOG.assertTrue(suggestedInitializers.length > 0);
LOG.assertTrue(suggestedInitializers[0] instanceof ExpressionLookupItem);
final PsiExpression initializer = (PsiExpression) suggestedInitializers[0].getObject();
if (myVariable instanceof PsiLocalVariable) {
((PsiLocalVariable)myVariable).setInitializer(initializer);
}
@@ -74,17 +90,119 @@ public class AddVariableInitializerFix implements IntentionAction {
else {
LOG.error("Unknown variable type: "+myVariable);
}
PsiVariable var = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(myVariable);
TextRange range = var.getInitializer().getTextRange();
int offset = range.getStartOffset();
editor.getCaretModel().moveToOffset(offset);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
runAssignmentTemplate(Collections.singletonList(myVariable.getInitializer()), suggestedInitializers, editor);
}
private String suggestInitializer() {
PsiType type = myVariable.getType();
return PsiTypesUtil.getDefaultValueOfType(type);
public static void runAssignmentTemplate(@NotNull final List<PsiExpression> initializers,
@NotNull final LookupElement[] suggestedInitializers,
@Nullable Editor editor) {
if (editor == null) return;
LOG.assertTrue(!initializers.isEmpty());
final PsiExpression initializer = ContainerUtil.getFirstItem(initializers);
PsiElement context = initializers.size() == 1 ? initializer : PsiTreeUtil.findCommonParent(initializers);
PsiDocumentManager.getInstance(initializer.getProject()).doPostponedOperationsAndUnblockDocument(editor.getDocument());
final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(context);
for (PsiExpression e : initializers) {
builder.replaceElement(e, new Expression() {
@Nullable
@Override
public Result calculateResult(ExpressionContext context1) {
return calculateQuickResult(context1);
}
@Nullable
@Override
public Result calculateQuickResult(ExpressionContext context1) {
return new PsiElementResult(suggestedInitializers[0].getPsiElement());
}
@Nullable
@Override
public LookupElement[] calculateLookupItems(ExpressionContext context1) {
return suggestedInitializers;
}
});
}
builder.run(editor, false);
}
@NotNull
public static LookupElement[] suggestInitializer(final PsiVariable variable) {
PsiType type = variable.getType();
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(variable.getProject());
if (type instanceof PsiClassType) {
final PsiClass aClass = PsiTypesUtil.getPsiClass(type);
if (aClass != null) {
final LookupElement nullLookupItem = new ExpressionLookupItem(elementFactory.createExpressionFromText(PsiKeyword.NULL, variable));
if (InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_LANG_ITERABLE) ||
InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_MAP)) {
final List<PsiType> suggestedTypes = new SmartList<PsiType>();
JavaInheritorsGetter.processInheritors(variable.getContainingFile(), variable, Collections.singleton((PsiClassType) type), PrefixMatcher.ALWAYS_TRUE, new Consumer<PsiType>() {
@Override
public void consume(PsiType type) {
LOG.assertTrue(type instanceof PsiClassType);
final PsiClass psiClass = PsiTypesUtil.getPsiClass(type);
if (psiClass != null &&
!psiClass.isInterface() &&
!psiClass.hasModifierProperty(PsiModifier.ABSTRACT) &&
PsiUtil.hasDefaultConstructor(psiClass)) {
suggestedTypes.add(type);
}
}
});
List<LookupElement> sortedLookups = map(sorted(map(suggestedTypes, new Function<PsiType, LookupElement>() {
@Override
public LookupElement fun(PsiType type) {
return PsiTypeLookupItem.createLookupItem(type, variable);
}
}), new Comparator<LookupElement>() {
@Override
public int compare(LookupElement o1, LookupElement o2) {
final int count1 = StatisticsWeigher.getBaseStatisticsInfo(o1, null).getUseCount();
final int count2 = StatisticsWeigher.getBaseStatisticsInfo(o2, null).getUseCount();
return count2 - count1;
}
}), new Function<LookupElement, LookupElement>() {
@Override
public LookupElement fun(LookupElement element) {
final LookupElementDecorator<LookupElement> constructorLookupElement =
LookupElementDecorator.withInsertHandler(element, ConstructorInsertHandler.BASIC_INSTANCE);
return new LookupElementDecorator<LookupElement>(constructorLookupElement) {
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setTailText("");
presentation.setItemText(PsiKeyword.NEW + " " + presentation.getItemText() + "()");
}
@Override
public void handleInsert(InsertionContext context) {
super.handleInsert(context);
context.getDocument().insertString(context.getStartOffset(), PsiKeyword.NEW + " ");
}
};
}
});
LookupElement[] result = new LookupElement[sortedLookups.size() + 1];
result[0] = nullLookupItem;
for (int i = 0; i < sortedLookups.size(); i++) {
LookupElement lookup = sortedLookups.get(i);
result[i + 1] = lookup;
}
return result;
} else {
if (PsiUtil.hasDefaultConstructor(aClass)) {
final PsiExpression newExpression = elementFactory
.createExpressionFromText(PsiKeyword.NEW + " " + type.getCanonicalText(false) + "()", variable);
return new LookupElement[]{nullLookupItem, new ExpressionLookupItem(newExpression)};
}
}
}
}
final String defaultValue = PsiTypesUtil.getDefaultValueOfType(type);
final PsiExpression expression = elementFactory.createExpressionFromText(defaultValue, variable);
return new LookupElement[] {new ExpressionLookupItem(expression)};
}
@Override
@@ -15,23 +15,20 @@
*/
package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.CodeInsightUtilCore;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.generation.PsiMethodMember;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.ide.util.MemberChooser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
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.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
@@ -93,59 +90,34 @@ public class InitializeFinalFieldInConstructorFix implements IntentionAction {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final List<PsiExpressionStatement> statements = addFieldInitialization(constructors, myField, project);
final PsiExpressionStatement highestStatement = getHighestElement(statements);
if (highestStatement == null) return;
final PsiAssignmentExpression expression = (PsiAssignmentExpression)highestStatement.getExpression();
final PsiElement rightExpression = expression.getRExpression();
final TextRange expressionRange = rightExpression.getTextRange();
editor.getCaretModel().moveToOffset(expressionRange.getStartOffset());
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().setSelection(expressionRange.getStartOffset(), expressionRange.getEndOffset());
addFieldInitialization(constructors, myField, project, editor);
}
});
}
@Nullable
private static <T extends PsiElement> T getHighestElement(@NotNull List<T> elements) {
T highest = null;
int highestTextOffset = Integer.MAX_VALUE;
for (T element : elements) {
final T forcedElem = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element);
final int startOffset = forcedElem.getTextOffset();
if (startOffset < highestTextOffset) {
highest = forcedElem;
highestTextOffset = startOffset;
}
}
return highest;
}
private static void addFieldInitialization(@NotNull List<PsiMethod> constructors,
@NotNull PsiField field,
@NotNull Project project,
@Nullable Editor editor) {
final List<PsiExpression> rExpressions = new ArrayList<PsiExpression>(constructors.size());
final LookupElement[] suggestedInitializers = AddVariableInitializerFix.suggestInitializer(field);
@NotNull
private static List<PsiExpressionStatement> addFieldInitialization(@NotNull List<PsiMethod> constructors,
@NotNull PsiField field,
@NotNull Project project) {
final List<PsiExpressionStatement> statements = new ArrayList<PsiExpressionStatement>();
for (PsiMethod constructor : constructors) {
final PsiExpressionStatement statement = addFieldInitialization(constructor, field, project);
if (statement != null) {
statements.add(statement);
}
rExpressions.add(addFieldInitialization(constructor, suggestedInitializers, field, project));
}
return statements;
AddVariableInitializerFix.runAssignmentTemplate(rExpressions, suggestedInitializers, editor);
}
@Nullable
private static PsiExpressionStatement addFieldInitialization(@NotNull PsiMethod constructor,
@NotNull PsiField field,
@NotNull Project project) {
private static PsiExpression addFieldInitialization(@NotNull PsiMethod constructor,
@NotNull LookupElement[] suggestedInitializers,
@NotNull PsiField field,
@NotNull Project project) {
PsiCodeBlock methodBody = constructor.getBody();
if (methodBody == null) return null;
final String fieldName = field.getName();
String stmtText = fieldName + " = " + suggestInitValue(field) + ";";
String stmtText = fieldName + " = " + suggestedInitializers[0].getPsiElement().getText() + ";";
if (methodContainsParameterWithName(constructor, fieldName)) {
stmtText = "this." + stmtText;
}
@@ -154,7 +126,9 @@ public class InitializeFinalFieldInConstructorFix implements IntentionAction {
final PsiElementFactory factory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory();
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
return (PsiExpressionStatement)methodBody.add(codeStyleManager.reformat(factory.createStatementFromText(stmtText, methodBody)));
final PsiExpressionStatement addedStatement = (PsiExpressionStatement)methodBody.add(codeStyleManager
.reformat(factory.createStatementFromText(stmtText, methodBody)));
return ((PsiAssignmentExpression)addedStatement.getExpression()).getRExpression();
}
private static boolean methodContainsParameterWithName(@NotNull PsiMethod constructor, @NotNull String name) {
@@ -228,11 +202,6 @@ public class InitializeFinalFieldInConstructorFix implements IntentionAction {
return result.toArray(new PsiMethod[result.size()]);
}
private static String suggestInitValue(@NotNull PsiField field) {
PsiType type = field.getType();
return PsiTypesUtil.getDefaultValueOfType(type);
}
@Override
public boolean startInWriteAction() {
return false;
@@ -3,7 +3,7 @@
class AClass
{
int f() {
int i = <caret><selection>0</selection>;
int i = 0<caret>;
return i;
}
}
@@ -2,6 +2,6 @@
class AClass
{
final AClass f = <caret><selection>null</selection>;
final AClass f = null<caret>;
}
@@ -3,10 +3,10 @@ class A {
private final int var;
private Main() {
var = <caret><selection>0</selection>;
var = 0;
}
private Main(int var) {
this.var = 0;
}
}
}<caret>
@@ -3,6 +3,6 @@ class A {
private final int var;
private Main(int var) {
this.var = <caret><selection>0</selection>;
this.var = 0<caret>;
}
}
@@ -3,6 +3,6 @@ class A {
private final int var;
private Main() {
var = <caret><selection>0</selection>;
var = 0<caret>;
}
}