mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-21 14:37:45 +07:00
1158 lines
54 KiB
Java
1158 lines
54 KiB
Java
// 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.refactoring.introduceVariable;
|
|
|
|
import com.intellij.codeInsight.CodeInsightUtil;
|
|
import com.intellij.codeInsight.completion.JavaCompletionUtil;
|
|
import com.intellij.codeInsight.highlighting.HighlightManager;
|
|
import com.intellij.codeInsight.lookup.LookupManager;
|
|
import com.intellij.codeInsight.unwrap.ScopeHighlighter;
|
|
import com.intellij.codeInspection.RemoveRedundantTypeArgumentsUtil;
|
|
import com.intellij.featureStatistics.FeatureUsageTracker;
|
|
import com.intellij.featureStatistics.ProductivityFeatureNames;
|
|
import com.intellij.ide.util.PropertiesComponent;
|
|
import com.intellij.lang.LanguageRefactoringSupport;
|
|
import com.intellij.lang.injection.InjectedLanguageManager;
|
|
import com.intellij.lang.refactoring.RefactoringSupportProvider;
|
|
import com.intellij.openapi.actionSystem.DataContext;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.command.CommandProcessor;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Document;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.editor.RangeMarker;
|
|
import com.intellij.openapi.editor.SelectionModel;
|
|
import com.intellij.openapi.editor.colors.EditorColors;
|
|
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
|
import com.intellij.openapi.editor.markup.TextAttributes;
|
|
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.Comparing;
|
|
import com.intellij.openapi.util.Key;
|
|
import com.intellij.openapi.util.Pass;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.openapi.util.registry.Registry;
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
import com.intellij.openapi.wm.WindowManager;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
|
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
|
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
|
import com.intellij.psi.codeStyle.VariableKind;
|
|
import com.intellij.psi.impl.PsiDiamondTypeUtil;
|
|
import com.intellij.psi.impl.source.jsp.jspJava.JspCodeBlock;
|
|
import com.intellij.psi.impl.source.jsp.jspJava.JspHolderMethod;
|
|
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
|
|
import com.intellij.psi.impl.source.tree.java.ReplaceExpressionUtil;
|
|
import com.intellij.psi.search.searches.ReferencesSearch;
|
|
import com.intellij.psi.util.*;
|
|
import com.intellij.refactoring.*;
|
|
import com.intellij.refactoring.chainCall.ChainCallExtractor;
|
|
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer;
|
|
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
|
|
import com.intellij.refactoring.introduceField.ElementToWorkOn;
|
|
import com.intellij.refactoring.listeners.RefactoringEventData;
|
|
import com.intellij.refactoring.listeners.RefactoringEventListener;
|
|
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
|
|
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
|
import com.intellij.refactoring.util.RefactoringUIUtil;
|
|
import com.intellij.refactoring.util.RefactoringUtil;
|
|
import com.intellij.refactoring.util.occurrences.ExpressionOccurrenceManager;
|
|
import com.intellij.refactoring.util.occurrences.NotInSuperCallOccurrenceFilter;
|
|
import com.intellij.util.IncorrectOperationException;
|
|
import com.intellij.util.Processor;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.containers.MultiMap;
|
|
import org.jetbrains.annotations.NonNls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.text.MessageFormat;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* @author dsl
|
|
*/
|
|
public abstract class IntroduceVariableBase extends IntroduceHandlerBase {
|
|
public static class JavaReplaceChoice implements OccurrencesChooser.BaseReplaceChoice {
|
|
public static final JavaReplaceChoice NO = new JavaReplaceChoice(OccurrencesChooser.ReplaceChoice.NO);
|
|
public static final JavaReplaceChoice NO_WRITE = new JavaReplaceChoice(OccurrencesChooser.ReplaceChoice.NO_WRITE);
|
|
public static final JavaReplaceChoice ALL = new JavaReplaceChoice(OccurrencesChooser.ReplaceChoice.ALL);
|
|
public static final JavaReplaceChoice NO_CHAIN = new JavaReplaceChoice("Create variable inside current lambda", false, false, false);
|
|
public static final JavaReplaceChoice CHAIN = new JavaReplaceChoice("Extract as a separate operation", false, false, true);
|
|
public static final JavaReplaceChoice CHAIN_ALL =
|
|
new JavaReplaceChoice("Replace all occurrences and extract as a separate operation", true, true, true);
|
|
|
|
private final String myDescription;
|
|
private final boolean myAll, myMultiple, myChain;
|
|
|
|
JavaReplaceChoice(OccurrencesChooser.ReplaceChoice choice) {
|
|
this(choice.getDescription(), choice.isAll(), choice.isMultiple(), false);
|
|
}
|
|
|
|
JavaReplaceChoice(String description, boolean all, boolean multiple, boolean chain) {
|
|
myDescription = description;
|
|
myAll = all;
|
|
myMultiple = multiple;
|
|
myChain = chain;
|
|
}
|
|
|
|
public String getDescription() {
|
|
return myDescription;
|
|
}
|
|
|
|
@Override
|
|
public boolean isMultiple() {
|
|
return myMultiple;
|
|
}
|
|
|
|
@Override
|
|
public boolean isAll() {
|
|
return myAll;
|
|
}
|
|
|
|
public boolean isChain() {
|
|
return myChain;
|
|
}
|
|
|
|
@Override
|
|
public String formatDescription(int occurrencesCount) {
|
|
return MessageFormat.format(getDescription(), occurrencesCount);
|
|
}
|
|
}
|
|
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase");
|
|
@NonNls private static final String PREFER_STATEMENTS_OPTION = "introduce.variable.prefer.statements";
|
|
@NonNls private static final String REFACTORING_ID = "refactoring.extractVariable";
|
|
|
|
protected static final String REFACTORING_NAME = RefactoringBundle.message("introduce.variable.title");
|
|
public static final Key<Boolean> NEED_PARENTHESIS = Key.create("NEED_PARENTHESIS");
|
|
private JavaVariableInplaceIntroducer myInplaceIntroducer;
|
|
|
|
public static SuggestedNameInfo getSuggestedName(@Nullable PsiType type, @NotNull final PsiExpression expression) {
|
|
return getSuggestedName(type, expression, expression);
|
|
}
|
|
|
|
public static SuggestedNameInfo getSuggestedName(@Nullable PsiType type,
|
|
@NotNull final PsiExpression expression,
|
|
final PsiElement anchor) {
|
|
final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(expression.getProject());
|
|
final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName(VariableKind.LOCAL_VARIABLE, null, expression, type);
|
|
final String[] strings = JavaCompletionUtil
|
|
.completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo);
|
|
final SuggestedNameInfo.Delegate delegate = new SuggestedNameInfo.Delegate(strings, nameInfo);
|
|
return codeStyleManager.suggestUniqueVariableName(delegate, anchor, true);
|
|
}
|
|
|
|
@Override
|
|
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) {
|
|
final SelectionModel selectionModel = editor.getSelectionModel();
|
|
if (!selectionModel.hasSelection()) {
|
|
final int offset = editor.getCaretModel().getOffset();
|
|
final PsiElement[] statementsInRange = findStatementsAtOffset(editor, file, offset);
|
|
|
|
//try line selection
|
|
if (statementsInRange.length == 1 && selectLineAtCaret(offset, statementsInRange)) {
|
|
selectionModel.selectLineAtCaret();
|
|
final PsiExpression expressionInRange =
|
|
findExpressionInRange(project, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
|
|
if (expressionInRange == null || getErrorMessage(expressionInRange) != null) {
|
|
selectionModel.removeSelection();
|
|
}
|
|
}
|
|
|
|
if (!selectionModel.hasSelection()) {
|
|
final List<PsiExpression> expressions = ContainerUtil
|
|
.filter(collectExpressions(file, editor, offset), expression -> RefactoringUtil.getParentStatement(expression, false) != null);
|
|
if (expressions.isEmpty()) {
|
|
selectionModel.selectLineAtCaret();
|
|
} else if (!isChooserNeeded(expressions)) {
|
|
final TextRange textRange = expressions.get(0).getTextRange();
|
|
selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
|
|
}
|
|
else {
|
|
IntroduceTargetChooser.showChooser(editor, expressions,
|
|
new Pass<PsiExpression>(){
|
|
@Override
|
|
public void pass(final PsiExpression selectedValue) {
|
|
invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset());
|
|
}
|
|
},
|
|
new PsiExpressionTrimRenderer.RenderFunction(), "Expressions", preferredSelection(statementsInRange, expressions), ScopeHighlighter.NATURAL_RANGER);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()) &&
|
|
LookupManager.getActiveLookup(editor) == null) {
|
|
selectionModel.removeSelection();
|
|
}
|
|
}
|
|
|
|
public static boolean isChooserNeeded(List<? extends PsiExpression> expressions) {
|
|
if (expressions.size() == 1) {
|
|
final PsiExpression expression = expressions.get(0);
|
|
return expression instanceof PsiNewExpression && ((PsiNewExpression)expression).getAnonymousClass() != null;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static boolean selectLineAtCaret(int offset, PsiElement[] statementsInRange) {
|
|
TextRange range = statementsInRange[0].getTextRange();
|
|
if (statementsInRange[0] instanceof PsiExpressionStatement) {
|
|
range = ((PsiExpressionStatement)statementsInRange[0]).getExpression().getTextRange();
|
|
}
|
|
|
|
return range.getStartOffset() > offset ||
|
|
range.getEndOffset() <= offset ||
|
|
isPreferStatements();
|
|
}
|
|
|
|
public static int preferredSelection(PsiElement[] statementsInRange, List<? extends PsiExpression> expressions) {
|
|
int selection;
|
|
if (statementsInRange.length == 1 &&
|
|
statementsInRange[0] instanceof PsiExpressionStatement &&
|
|
PsiUtilCore.hasErrorElementChild(statementsInRange[0])) {
|
|
selection = expressions.indexOf(((PsiExpressionStatement)statementsInRange[0]).getExpression());
|
|
} else {
|
|
PsiExpression expression = expressions.get(0);
|
|
if (expression instanceof PsiReferenceExpression && ((PsiReferenceExpression)expression).resolve() instanceof PsiLocalVariable) {
|
|
selection = 1;
|
|
}
|
|
else {
|
|
selection = -1;
|
|
}
|
|
}
|
|
return selection;
|
|
}
|
|
|
|
public static boolean isPreferStatements() {
|
|
return Boolean.valueOf(PropertiesComponent.getInstance().getBoolean(PREFER_STATEMENTS_OPTION)) || Registry.is(PREFER_STATEMENTS_OPTION, false);
|
|
}
|
|
|
|
public static List<PsiExpression> collectExpressions(final PsiFile file,
|
|
final Editor editor,
|
|
final int offset) {
|
|
return collectExpressions(file, editor, offset, false);
|
|
}
|
|
|
|
public static List<PsiExpression> collectExpressions(final PsiFile file,
|
|
final Editor editor,
|
|
final int offset,
|
|
boolean acceptVoid) {
|
|
return collectExpressions(file, editor.getDocument(), offset, acceptVoid);
|
|
}
|
|
|
|
public static List<PsiExpression> collectExpressions(final PsiFile file,
|
|
final Document document,
|
|
final int offset,
|
|
boolean acceptVoid) {
|
|
CharSequence text = document.getCharsSequence();
|
|
int correctedOffset = offset;
|
|
int textLength = document.getTextLength();
|
|
if (offset >= textLength) {
|
|
correctedOffset = textLength - 1;
|
|
}
|
|
else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
|
|
correctedOffset--;
|
|
}
|
|
if (correctedOffset < 0) {
|
|
correctedOffset = offset;
|
|
}
|
|
else if (!Character.isJavaIdentifierPart(text.charAt(correctedOffset))) {
|
|
if (text.charAt(correctedOffset) == ';') {//initially caret on the end of line
|
|
correctedOffset--;
|
|
}
|
|
if (correctedOffset < 0 || text.charAt(correctedOffset) != ')' && text.charAt(correctedOffset) != '.') {
|
|
correctedOffset = offset;
|
|
}
|
|
}
|
|
final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
|
|
final List<PsiExpression> expressions = new ArrayList<>();
|
|
/*for (PsiElement element : statementsInRange) {
|
|
if (element instanceof PsiExpressionStatement) {
|
|
final PsiExpression expression = ((PsiExpressionStatement)element).getExpression();
|
|
if (expression.getType() != PsiType.VOID) {
|
|
expressions.add(expression);
|
|
}
|
|
}
|
|
}*/
|
|
PsiExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, PsiExpression.class);
|
|
while (expression != null) {
|
|
if (!expressions.contains(expression) && !(expression instanceof PsiParenthesizedExpression) && !(expression instanceof PsiSuperExpression) &&
|
|
(acceptVoid || !PsiType.VOID.equals(expression.getType()))) {
|
|
if (expression instanceof PsiMethodReferenceExpression) {
|
|
expressions.add(expression);
|
|
}
|
|
else if (!(expression instanceof PsiAssignmentExpression)) {
|
|
if (!(expression instanceof PsiReferenceExpression)) {
|
|
expressions.add(expression);
|
|
}
|
|
else {
|
|
if (!(expression.getParent() instanceof PsiMethodCallExpression)) {
|
|
final PsiElement resolve = ((PsiReferenceExpression)expression).resolve();
|
|
if (!(resolve instanceof PsiClass) && !(resolve instanceof PsiPackage)) {
|
|
expressions.add(expression);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
expression = PsiTreeUtil.getParentOfType(expression, PsiExpression.class);
|
|
}
|
|
return expressions;
|
|
}
|
|
|
|
public static PsiElement[] findStatementsAtOffset(final Editor editor, final PsiFile file, final int offset) {
|
|
final Document document = editor.getDocument();
|
|
final int lineNumber = document.getLineNumber(offset);
|
|
final int lineStart = document.getLineStartOffset(lineNumber);
|
|
final int lineEnd = document.getLineEndOffset(lineNumber);
|
|
|
|
return CodeInsightUtil.findStatementsInRange(file, lineStart, lineEnd);
|
|
}
|
|
|
|
private boolean invoke(final Project project, final Editor editor, PsiFile file, int startOffset, int endOffset) {
|
|
FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.REFACTORING_INTRODUCE_VARIABLE);
|
|
PsiDocumentManager.getInstance(project).commitAllDocuments();
|
|
|
|
|
|
return invokeImpl(project, findExpressionInRange(project, file, startOffset, endOffset), editor);
|
|
}
|
|
|
|
private static PsiExpression findExpressionInRange(Project project, PsiFile file, int startOffset, int endOffset) {
|
|
PsiExpression tempExpr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset);
|
|
if (tempExpr == null) {
|
|
PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset);
|
|
if (statements.length == 1) {
|
|
if (statements[0] instanceof PsiExpressionStatement) {
|
|
tempExpr = ((PsiExpressionStatement) statements[0]).getExpression();
|
|
} else if (statements[0] instanceof PsiReturnStatement) {
|
|
tempExpr = ((PsiReturnStatement)statements[0]).getReturnValue();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tempExpr == null) {
|
|
tempExpr = getSelectedExpression(project, file, startOffset, endOffset);
|
|
}
|
|
return tempExpr;
|
|
}
|
|
|
|
/**
|
|
* @return can return NotNull value although extraction will fail: reason could be retrieved from {@link #getErrorMessage(PsiExpression)}
|
|
*/
|
|
public static PsiExpression getSelectedExpression(final Project project, PsiFile file, int startOffset, int endOffset) {
|
|
final InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(project);
|
|
PsiElement elementAtStart = file.findElementAt(startOffset);
|
|
if (elementAtStart == null || elementAtStart instanceof PsiWhiteSpace || elementAtStart instanceof PsiComment) {
|
|
final PsiElement element = PsiTreeUtil.skipWhitespacesAndCommentsForward(elementAtStart);
|
|
if (element != null) {
|
|
startOffset = element.getTextOffset();
|
|
elementAtStart = file.findElementAt(startOffset);
|
|
}
|
|
if (elementAtStart == null) {
|
|
if (injectedLanguageManager.isInjectedFragment(file)) {
|
|
return getSelectionFromInjectedHost(project, file, injectedLanguageManager, startOffset, endOffset);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
startOffset = elementAtStart.getTextOffset();
|
|
}
|
|
PsiElement elementAtEnd = file.findElementAt(endOffset - 1);
|
|
if (elementAtEnd == null || elementAtEnd instanceof PsiWhiteSpace || elementAtEnd instanceof PsiComment) {
|
|
elementAtEnd = PsiTreeUtil.skipWhitespacesAndCommentsBackward(elementAtEnd);
|
|
if (elementAtEnd == null) return null;
|
|
endOffset = elementAtEnd.getTextRange().getEndOffset();
|
|
}
|
|
|
|
if (endOffset <= startOffset) return null;
|
|
|
|
PsiElement elementAt = PsiTreeUtil.findCommonParent(elementAtStart, elementAtEnd);
|
|
if (elementAt instanceof PsiExpressionStatement) {
|
|
return ((PsiExpressionStatement)elementAt).getExpression();
|
|
}
|
|
final PsiExpression containingExpression = PsiTreeUtil.getParentOfType(elementAt, PsiExpression.class, false);
|
|
|
|
if (containingExpression != null && containingExpression == elementAtEnd && startOffset == containingExpression.getTextOffset()) {
|
|
return containingExpression;
|
|
}
|
|
|
|
if (containingExpression == null || containingExpression instanceof PsiLambdaExpression) {
|
|
if (injectedLanguageManager.isInjectedFragment(file)) {
|
|
return getSelectionFromInjectedHost(project, file, injectedLanguageManager, startOffset, endOffset);
|
|
}
|
|
elementAt = null;
|
|
}
|
|
final PsiLiteralExpression literalExpression = PsiTreeUtil.getParentOfType(elementAt, PsiLiteralExpression.class);
|
|
|
|
final PsiLiteralExpression startLiteralExpression = PsiTreeUtil.getParentOfType(elementAtStart, PsiLiteralExpression.class);
|
|
final PsiLiteralExpression endLiteralExpression = PsiTreeUtil.getParentOfType(file.findElementAt(endOffset), PsiLiteralExpression.class);
|
|
|
|
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
|
|
String text = null;
|
|
PsiExpression tempExpr;
|
|
try {
|
|
text = file.getText().subSequence(startOffset, endOffset).toString();
|
|
String prefix = null;
|
|
if (startLiteralExpression != null) {
|
|
final int startExpressionOffset = startLiteralExpression.getTextOffset();
|
|
if (startOffset == startExpressionOffset + 1) {
|
|
text = "\"" + text;
|
|
} else if (startOffset > startExpressionOffset + 1){
|
|
prefix = "\" + ";
|
|
text = "\"" + text;
|
|
}
|
|
}
|
|
|
|
String suffix = null;
|
|
if (endLiteralExpression != null) {
|
|
final int endExpressionOffset = endLiteralExpression.getTextOffset() + endLiteralExpression.getTextLength();
|
|
if (endOffset == endExpressionOffset - 1) {
|
|
text += "\"";
|
|
} else if (endOffset < endExpressionOffset - 1) {
|
|
suffix = " + \"";
|
|
text += "\"";
|
|
}
|
|
}
|
|
|
|
if (literalExpression != null && text.equals(literalExpression.getText())) return literalExpression;
|
|
|
|
final PsiElement parent = literalExpression != null ? literalExpression : elementAt;
|
|
tempExpr = elementFactory.createExpressionFromText(text, parent);
|
|
|
|
final boolean [] hasErrors = new boolean[1];
|
|
final JavaRecursiveElementWalkingVisitor errorsVisitor = new JavaRecursiveElementWalkingVisitor() {
|
|
@Override
|
|
public void visitElement(final PsiElement element) {
|
|
if (hasErrors[0]) {
|
|
return;
|
|
}
|
|
super.visitElement(element);
|
|
}
|
|
|
|
@Override
|
|
public void visitErrorElement(final PsiErrorElement element) {
|
|
hasErrors[0] = true;
|
|
}
|
|
};
|
|
tempExpr.accept(errorsVisitor);
|
|
if (hasErrors[0]) return null;
|
|
|
|
tempExpr.putUserData(ElementToWorkOn.PREFIX, prefix);
|
|
tempExpr.putUserData(ElementToWorkOn.SUFFIX, suffix);
|
|
|
|
final RangeMarker rangeMarker =
|
|
FileDocumentManager.getInstance().getDocument(file.getVirtualFile()).createRangeMarker(startOffset, endOffset);
|
|
tempExpr.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker);
|
|
|
|
if (parent != null) {
|
|
tempExpr.putUserData(ElementToWorkOn.PARENT, parent);
|
|
}
|
|
else {
|
|
PsiErrorElement errorElement = elementAtStart instanceof PsiErrorElement
|
|
? (PsiErrorElement)elementAtStart
|
|
: PsiTreeUtil.getNextSiblingOfType(elementAtStart, PsiErrorElement.class);
|
|
if (errorElement == null) {
|
|
errorElement = PsiTreeUtil.getParentOfType(elementAtStart, PsiErrorElement.class);
|
|
}
|
|
if (errorElement == null) return null;
|
|
if (!(errorElement.getParent() instanceof PsiClass)) return null;
|
|
tempExpr.putUserData(ElementToWorkOn.PARENT, errorElement);
|
|
tempExpr.putUserData(ElementToWorkOn.OUT_OF_CODE_BLOCK, Boolean.TRUE);
|
|
}
|
|
|
|
final String fakeInitializer = "intellijidearulezzz";
|
|
final int[] refIdx = new int[1];
|
|
final PsiElement toBeExpression = createReplacement(fakeInitializer, project, prefix, suffix, parent, rangeMarker, refIdx);
|
|
toBeExpression.accept(errorsVisitor);
|
|
if (hasErrors[0]) return null;
|
|
if (literalExpression != null && toBeExpression instanceof PsiExpression) {
|
|
PsiType type = ((PsiExpression)toBeExpression).getType();
|
|
if (type != null && !type.equals(literalExpression.getType())) {
|
|
return null;
|
|
}
|
|
}
|
|
else if (containingExpression != null && !Comparing.equal(containingExpression.getType(), tempExpr.getType())){
|
|
return null;
|
|
}
|
|
|
|
final PsiReferenceExpression refExpr = PsiTreeUtil.getParentOfType(toBeExpression.findElementAt(refIdx[0]), PsiReferenceExpression.class);
|
|
if (refExpr == null) return null;
|
|
if (toBeExpression == refExpr && refIdx[0] > 0) {
|
|
return null;
|
|
}
|
|
if (ReplaceExpressionUtil.isNeedParenthesis(refExpr.getNode(), tempExpr.getNode())) {
|
|
tempExpr.putCopyableUserData(NEED_PARENTHESIS, Boolean.TRUE);
|
|
return tempExpr;
|
|
}
|
|
}
|
|
catch (IncorrectOperationException e) {
|
|
if (elementAt instanceof PsiExpressionList) {
|
|
final PsiElement parent = elementAt.getParent();
|
|
return parent instanceof PsiCallExpression ? createArrayCreationExpression(text, startOffset, endOffset, (PsiCallExpression)parent) : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
return tempExpr;
|
|
}
|
|
|
|
private static PsiExpression getSelectionFromInjectedHost(Project project,
|
|
PsiFile file,
|
|
InjectedLanguageManager injectedLanguageManager, int startOffset, int endOffset) {
|
|
final PsiLanguageInjectionHost injectionHost = injectedLanguageManager.getInjectionHost(file);
|
|
return getSelectedExpression(project, injectionHost.getContainingFile(), injectedLanguageManager.injectedToHost(file, startOffset), injectedLanguageManager.injectedToHost(file, endOffset));
|
|
}
|
|
|
|
@Nullable
|
|
public static String getErrorMessage(PsiExpression expr) {
|
|
final Boolean needParenthesis = expr.getCopyableUserData(NEED_PARENTHESIS);
|
|
if (needParenthesis != null && needParenthesis.booleanValue()) {
|
|
return "Extracting selected expression would change the semantic of the whole expression.";
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private static PsiExpression createArrayCreationExpression(String text, int startOffset, int endOffset, PsiCallExpression parent) {
|
|
if (text == null || parent == null) return null;
|
|
if (text.contains(",")) {
|
|
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(parent.getProject());
|
|
final JavaResolveResult resolveResult = parent.resolveMethodGenerics();
|
|
final PsiMethod psiMethod = (PsiMethod)resolveResult.getElement();
|
|
if (psiMethod == null || !psiMethod.isVarArgs()) return null;
|
|
final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
|
final PsiParameter varargParameter = parameters[parameters.length - 1];
|
|
final PsiType type = varargParameter.getType();
|
|
LOG.assertTrue(type instanceof PsiEllipsisType);
|
|
final PsiArrayType psiType = (PsiArrayType)((PsiEllipsisType)type).toArrayType();
|
|
final PsiExpression[] args = parent.getArgumentList().getExpressions();
|
|
final PsiSubstitutor psiSubstitutor = resolveResult.getSubstitutor();
|
|
|
|
if (args.length < parameters.length || startOffset < args[parameters.length - 1].getTextRange().getStartOffset()) return null;
|
|
|
|
final PsiFile containingFile = parent.getContainingFile();
|
|
|
|
PsiElement startElement = containingFile.findElementAt(startOffset);
|
|
while (startElement != null && startElement.getParent() != parent.getArgumentList()) {
|
|
startElement = startElement.getParent();
|
|
}
|
|
if (!(startElement instanceof PsiExpression) || startOffset > startElement.getTextOffset()) return null;
|
|
|
|
PsiElement endElement = containingFile.findElementAt(endOffset - 1);
|
|
while (endElement != null && endElement.getParent() != parent.getArgumentList()) {
|
|
endElement = endElement.getParent();
|
|
}
|
|
if (!(endElement instanceof PsiExpression) || endOffset < endElement.getTextRange().getEndOffset()) return null;
|
|
|
|
final PsiType componentType = TypeConversionUtil.erasure(psiSubstitutor.substitute(psiType.getComponentType()));
|
|
try {
|
|
final PsiExpression expressionFromText =
|
|
elementFactory.createExpressionFromText("new " + componentType.getCanonicalText() + "[]{" + text + "}", parent);
|
|
final RangeMarker rangeMarker =
|
|
FileDocumentManager.getInstance().getDocument(containingFile.getVirtualFile()).createRangeMarker(startOffset, endOffset);
|
|
expressionFromText.putUserData(ElementToWorkOn.TEXT_RANGE, rangeMarker);
|
|
expressionFromText.putUserData(ElementToWorkOn.PARENT, parent);
|
|
return expressionFromText;
|
|
}
|
|
catch (IncorrectOperationException e) {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected boolean invokeImpl(final Project project, final PsiExpression expr,
|
|
final Editor editor) {
|
|
if (expr != null) {
|
|
final String errorMessage = getErrorMessage(expr);
|
|
if (errorMessage != null) {
|
|
showErrorMessage(project, editor, RefactoringBundle.getCannotRefactorMessage(errorMessage));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (expr != null && expr.getParent() instanceof PsiExpressionStatement) {
|
|
FeatureUsageTracker.getInstance().triggerFeatureUsed("refactoring.introduceVariable.incompleteStatement");
|
|
}
|
|
if (LOG.isDebugEnabled()) {
|
|
LOG.debug("expression:" + expr);
|
|
}
|
|
|
|
if (expr == null || !expr.isPhysical()) {
|
|
if (ReassignVariableUtil.reassign(editor)) return false;
|
|
if (expr == null) {
|
|
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression"));
|
|
showErrorMessage(project, editor, message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
final PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expr);
|
|
if (originalType == null || LambdaUtil.notInferredType(originalType)) {
|
|
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type"));
|
|
showErrorMessage(project, editor, message);
|
|
return false;
|
|
}
|
|
|
|
if (PsiType.VOID.equals(originalType)) {
|
|
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type"));
|
|
showErrorMessage(project, editor, message);
|
|
return false;
|
|
}
|
|
|
|
|
|
final PsiElement physicalElement = expr.getUserData(ElementToWorkOn.PARENT);
|
|
|
|
final PsiElement anchorStatement = RefactoringUtil.getParentStatement(physicalElement != null ? physicalElement : expr, false);
|
|
|
|
PsiElement tempContainer = checkAnchorStatement(project, editor, anchorStatement);
|
|
if (tempContainer == null) return false;
|
|
|
|
final PsiFile file = anchorStatement.getContainingFile();
|
|
LOG.assertTrue(file != null, "expr.getContainingFile() == null");
|
|
final PsiElement nameSuggestionContext = editor == null ? null : file.findElementAt(editor.getCaretModel().getOffset());
|
|
final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(expr.getLanguage());
|
|
final boolean isInplaceAvailableOnDataContext =
|
|
supportProvider != null &&
|
|
editor.getSettings().isVariableInplaceRenameEnabled() &&
|
|
supportProvider.isInplaceIntroduceAvailable(expr, nameSuggestionContext) &&
|
|
(!ApplicationManager.getApplication().isUnitTestMode() || isInplaceAvailableInTestMode()) &&
|
|
!isInJspHolderMethod(expr);
|
|
|
|
if (isInplaceAvailableOnDataContext) {
|
|
final MultiMap<PsiElement, String> conflicts = new MultiMap<>();
|
|
checkInLoopCondition(expr, conflicts);
|
|
if (!conflicts.isEmpty()) {
|
|
showErrorMessage(project, editor, StringUtil.join(conflicts.values(), "<br>"));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
final ExpressionOccurrenceManager occurrenceManager = createOccurrenceManager(expr, tempContainer);
|
|
final PsiExpression[] occurrences = occurrenceManager.getOccurrences();
|
|
final PsiElement anchorStatementIfAll = occurrenceManager.getAnchorStatementForAll();
|
|
|
|
|
|
OccurrencesInfo occurrencesInfo = new OccurrencesInfo(occurrences);
|
|
|
|
if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) return false;
|
|
|
|
final LinkedHashMap<JavaReplaceChoice, List<PsiExpression>> occurrencesMap = occurrencesInfo.buildOccurrencesMap(expr);
|
|
|
|
final boolean inFinalContext = occurrenceManager.isInFinalContext();
|
|
final InputValidator validator = new InputValidator(this, project, anchorStatementIfAll, anchorStatement, occurrenceManager);
|
|
final TypeSelectorManagerImpl typeSelectorManager = new TypeSelectorManagerImpl(project, originalType, expr, occurrences);
|
|
final boolean[] wasSucceed = new boolean[]{true};
|
|
final Pass<JavaReplaceChoice> callback = new Pass<JavaReplaceChoice>() {
|
|
@Override
|
|
public void pass(final JavaReplaceChoice choice) {
|
|
boolean hasWriteAccess = occurrencesInfo.myHasWriteAccess;
|
|
List<PsiExpression> nonWrite = occurrencesInfo.myNonWrite;
|
|
if (choice != null) {
|
|
final boolean noWriteChoice = choice == JavaReplaceChoice.NO_WRITE;
|
|
final boolean allChoice = choice.isAll();
|
|
final boolean replaceAll = allChoice || noWriteChoice;
|
|
typeSelectorManager.setAllOccurrences(replaceAll);
|
|
|
|
final PsiElement chosenAnchor = chooseAnchor(replaceAll, noWriteChoice, nonWrite, anchorStatementIfAll, anchorStatement);
|
|
final IntroduceVariableSettings settings =
|
|
getSettings(project, editor, expr, occurrences, typeSelectorManager, inFinalContext, hasWriteAccess, validator, chosenAnchor,
|
|
choice);
|
|
|
|
final boolean cantChangeFinalModifier = (hasWriteAccess || inFinalContext) && allChoice;
|
|
|
|
PsiExpression[] allOccurrences = Arrays.stream(occurrences)
|
|
.filter(occurrence -> allChoice || (noWriteChoice && !PsiUtil.isAccessedForWriting(occurrence)) || expr.equals(occurrence))
|
|
.toArray(PsiExpression[]::new);
|
|
if (choice.isChain()) {
|
|
myInplaceIntroducer = new ChainCallInplaceIntroducer(project,
|
|
settings,
|
|
chosenAnchor,
|
|
editor, expr,
|
|
allOccurrences,
|
|
typeSelectorManager,
|
|
REFACTORING_NAME);
|
|
}
|
|
else {
|
|
myInplaceIntroducer = new JavaVariableInplaceIntroducer(project,
|
|
settings,
|
|
chosenAnchor,
|
|
editor, expr, cantChangeFinalModifier,
|
|
allOccurrences,
|
|
typeSelectorManager,
|
|
REFACTORING_NAME);
|
|
}
|
|
if (myInplaceIntroducer.startInplaceIntroduceTemplate()) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
CommandProcessor.getInstance().executeCommand(
|
|
project,
|
|
() -> {
|
|
if (!anchorStatement.isValid()) {
|
|
return;
|
|
}
|
|
final Editor topLevelEditor ;
|
|
if (!InjectedLanguageManager.getInstance(project).isInjectedFragment(anchorStatement.getContainingFile())) {
|
|
topLevelEditor = InjectedLanguageUtil.getTopLevelEditor(editor);
|
|
} else {
|
|
topLevelEditor = editor;
|
|
}
|
|
|
|
PsiVariable variable = null;
|
|
try {
|
|
final IntroduceVariableSettings settings =
|
|
getSettings(project, topLevelEditor, expr, occurrences, typeSelectorManager, inFinalContext, hasWriteAccess, validator,
|
|
anchorStatement, choice);
|
|
if (!settings.isOK()) {
|
|
wasSucceed[0] = false;
|
|
return;
|
|
}
|
|
|
|
final RefactoringEventData beforeData = new RefactoringEventData();
|
|
beforeData.addElement(expr);
|
|
project.getMessageBus()
|
|
.syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted(REFACTORING_ID, beforeData);
|
|
|
|
final PsiElement chosenAnchor =
|
|
chooseAnchor(settings.isReplaceAllOccurrences(), hasWriteAccess, nonWrite, anchorStatementIfAll, anchorStatement);
|
|
|
|
variable = VariableExtractor.introduce(project, expr, topLevelEditor, chosenAnchor, occurrences, settings);
|
|
}
|
|
finally {
|
|
final RefactoringEventData afterData = new RefactoringEventData();
|
|
afterData.addElement(variable);
|
|
project.getMessageBus()
|
|
.syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(REFACTORING_ID, afterData);
|
|
}
|
|
}, REFACTORING_NAME, null);
|
|
}
|
|
};
|
|
|
|
if (!isInplaceAvailableOnDataContext) {
|
|
callback.pass(null);
|
|
}
|
|
else {
|
|
JavaReplaceChoice choice = getOccurrencesChoice();
|
|
if (choice != null) {
|
|
callback.pass(choice);
|
|
} else {
|
|
String title = occurrencesInfo.myChainMethodName != null && occurrences.length == 1
|
|
? "Lambda chain detected"
|
|
: OccurrencesChooser.DEFAULT_CHOOSER_TITLE;
|
|
OccurrencesChooser.<PsiExpression>simpleChooser(editor).showChooser(callback, occurrencesMap, title);
|
|
}
|
|
}
|
|
return wasSucceed[0];
|
|
}
|
|
|
|
protected PsiElement checkAnchorStatement(Project project, Editor editor, PsiElement anchorStatement) {
|
|
if (anchorStatement == null) {
|
|
String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME);
|
|
showErrorMessage(project, editor, message);
|
|
return null;
|
|
}
|
|
if (checkAnchorBeforeThisOrSuper(project, editor, anchorStatement, REFACTORING_NAME, HelpID.INTRODUCE_VARIABLE)) return null;
|
|
|
|
final PsiElement tempContainer = anchorStatement.getParent();
|
|
|
|
if (!(tempContainer instanceof PsiCodeBlock) && !RefactoringUtil.isLoopOrIf(tempContainer) && !(tempContainer instanceof PsiLambdaExpression) && (tempContainer.getParent() instanceof PsiLambdaExpression)) {
|
|
String message = RefactoringBundle.message("refactoring.is.not.supported.in.the.current.context", REFACTORING_NAME);
|
|
showErrorMessage(project, editor, message);
|
|
return null;
|
|
}
|
|
return tempContainer;
|
|
}
|
|
|
|
protected JavaReplaceChoice getOccurrencesChoice() {
|
|
return null;
|
|
}
|
|
|
|
protected static PsiElement chooseAnchor(boolean allOccurrences,
|
|
boolean hasWriteAccess,
|
|
List<PsiExpression> nonWrite,
|
|
PsiElement anchorStatementIfAll,
|
|
PsiElement anchorStatement) {
|
|
if (allOccurrences) {
|
|
if (hasWriteAccess) {
|
|
return RefactoringUtil.getAnchorElementForMultipleExpressions(nonWrite.toArray(PsiExpression.EMPTY_ARRAY), null);
|
|
}
|
|
else {
|
|
return anchorStatementIfAll;
|
|
}
|
|
}
|
|
else {
|
|
return anchorStatement;
|
|
}
|
|
}
|
|
|
|
protected boolean isInplaceAvailableInTestMode() {
|
|
return false;
|
|
}
|
|
|
|
private static ExpressionOccurrenceManager createOccurrenceManager(PsiExpression expr, PsiElement tempContainer) {
|
|
Set<PsiVariable> vars = new HashSet<>();
|
|
SyntaxTraverser.psiTraverser().withRoot(expr)
|
|
.filter(element -> element instanceof PsiReferenceExpression)
|
|
.forEach(element -> {
|
|
final PsiElement resolve = ((PsiReferenceExpression)element).resolve();
|
|
if (resolve instanceof PsiVariable) {
|
|
vars.add((PsiVariable)resolve);
|
|
}
|
|
});
|
|
|
|
PsiElement containerParent = tempContainer;
|
|
PsiElement lastScope = tempContainer;
|
|
while (true) {
|
|
if (containerParent instanceof PsiFile) break;
|
|
if (containerParent instanceof PsiMethod) {
|
|
PsiClass containingClass = ((PsiMethod)containerParent).getContainingClass();
|
|
if (containingClass == null || !PsiUtil.isLocalOrAnonymousClass(containingClass)) break;
|
|
if (vars.stream().anyMatch(variable -> PsiTreeUtil.isAncestor(containingClass, variable, true))) {
|
|
break;
|
|
}
|
|
}
|
|
if (containerParent instanceof PsiLambdaExpression) {
|
|
PsiParameter[] parameters = ((PsiLambdaExpression)containerParent).getParameterList().getParameters();
|
|
if (Arrays.stream(parameters).anyMatch(vars::contains)) {
|
|
break;
|
|
}
|
|
}
|
|
if (containerParent instanceof PsiForStatement) {
|
|
PsiForStatement forStatement = (PsiForStatement)containerParent;
|
|
if (vars.stream().anyMatch(variable -> PsiTreeUtil.isAncestor(forStatement.getInitialization(), variable, true))) {
|
|
break;
|
|
}
|
|
}
|
|
containerParent = containerParent.getParent();
|
|
if (containerParent instanceof PsiCodeBlock) {
|
|
lastScope = containerParent;
|
|
}
|
|
}
|
|
return new ExpressionOccurrenceManager(expr, lastScope, NotInSuperCallOccurrenceFilter.INSTANCE);
|
|
}
|
|
|
|
private static boolean isInJspHolderMethod(PsiExpression expr) {
|
|
final PsiElement parent1 = expr.getParent();
|
|
if (parent1 == null) {
|
|
return false;
|
|
}
|
|
final PsiElement parent2 = parent1.getParent();
|
|
if (!(parent2 instanceof JspCodeBlock)) return false;
|
|
final PsiElement parent3 = parent2.getParent();
|
|
return parent3 instanceof JspHolderMethod;
|
|
}
|
|
|
|
static boolean isFinalVariableOnLHS(PsiExpression expr) {
|
|
if (expr instanceof PsiReferenceExpression && RefactoringUtil.isAssignmentLHS(expr)) {
|
|
final PsiElement resolve = ((PsiReferenceExpression)expr).resolve();
|
|
if (resolve instanceof PsiVariable &&
|
|
((PsiVariable)resolve).hasModifierProperty(PsiModifier.FINAL)) { //should be inserted after assignment
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static PsiExpression simplifyVariableInitializer(final PsiExpression initializer,
|
|
final PsiType expectedType) {
|
|
return simplifyVariableInitializer(initializer, expectedType, true);
|
|
}
|
|
|
|
public static PsiExpression simplifyVariableInitializer(final PsiExpression initializer,
|
|
final PsiType expectedType,
|
|
final boolean inDeclaration) {
|
|
|
|
if (initializer instanceof PsiTypeCastExpression) {
|
|
PsiExpression operand = ((PsiTypeCastExpression)initializer).getOperand();
|
|
if (operand != null) {
|
|
PsiType operandType = operand.getType();
|
|
if (operandType != null && TypeConversionUtil.isAssignable(expectedType, operandType)) {
|
|
return operand;
|
|
}
|
|
}
|
|
}
|
|
else if (initializer instanceof PsiNewExpression) {
|
|
final PsiNewExpression newExpression = (PsiNewExpression)initializer;
|
|
if (newExpression.getArrayInitializer() != null) {
|
|
if (inDeclaration) {
|
|
return newExpression.getArrayInitializer();
|
|
}
|
|
}
|
|
else {
|
|
final PsiExpression tryToDetectDiamondNewExpr = ((PsiVariable)JavaPsiFacade.getElementFactory(initializer.getProject())
|
|
.createVariableDeclarationStatement("x", expectedType, initializer, initializer).getDeclaredElements()[0])
|
|
.getInitializer();
|
|
if (tryToDetectDiamondNewExpr instanceof PsiNewExpression &&
|
|
PsiDiamondTypeUtil.canCollapseToDiamond((PsiNewExpression)tryToDetectDiamondNewExpr,
|
|
(PsiNewExpression)tryToDetectDiamondNewExpr,
|
|
expectedType)) {
|
|
final PsiElement paramList = RemoveRedundantTypeArgumentsUtil
|
|
.replaceExplicitWithDiamond(newExpression.getClassOrAnonymousClassReference().getParameterList());
|
|
return PsiTreeUtil.getParentOfType(paramList, PsiNewExpression.class);
|
|
}
|
|
}
|
|
}
|
|
return initializer;
|
|
}
|
|
|
|
public static PsiElement replace(final PsiExpression expr1, final PsiExpression ref, final Project project)
|
|
throws IncorrectOperationException {
|
|
final PsiExpression expr2;
|
|
if (expr1 instanceof PsiArrayInitializerExpression &&
|
|
expr1.getParent() instanceof PsiNewExpression) {
|
|
expr2 = (PsiNewExpression) expr1.getParent();
|
|
} else {
|
|
expr2 = RefactoringUtil.outermostParenthesizedExpression(expr1);
|
|
}
|
|
if (expr2.isPhysical() || expr1.getUserData(ElementToWorkOn.REPLACE_NON_PHYSICAL) != null) {
|
|
return expr2.replace(ref);
|
|
}
|
|
else {
|
|
final String prefix = expr1.getUserData(ElementToWorkOn.PREFIX);
|
|
final String suffix = expr1.getUserData(ElementToWorkOn.SUFFIX);
|
|
final PsiElement parent = expr1.getUserData(ElementToWorkOn.PARENT);
|
|
final RangeMarker rangeMarker = expr1.getUserData(ElementToWorkOn.TEXT_RANGE);
|
|
|
|
LOG.assertTrue(parent != null, expr1);
|
|
return parent.replace(createReplacement(ref.getText(), project, prefix, suffix, parent, rangeMarker, new int[1]));
|
|
}
|
|
}
|
|
|
|
private static PsiElement createReplacement(final String refText, final Project project,
|
|
final String prefix,
|
|
final String suffix,
|
|
final PsiElement parent, final RangeMarker rangeMarker, int[] refIdx) {
|
|
String text = refText;
|
|
if (parent != null) {
|
|
final String allText = parent.getContainingFile().getText();
|
|
final TextRange parentRange = parent.getTextRange();
|
|
|
|
LOG.assertTrue(parentRange.getStartOffset() <= rangeMarker.getStartOffset(), parent + "; prefix:" + prefix + "; suffix:" + suffix);
|
|
String beg = allText.substring(parentRange.getStartOffset(), rangeMarker.getStartOffset());
|
|
//noinspection SSBasedInspection (suggested replacement breaks behavior)
|
|
if (StringUtil.stripQuotesAroundValue(beg).trim().isEmpty() && prefix == null) beg = "";
|
|
|
|
LOG.assertTrue(rangeMarker.getEndOffset() <= parentRange.getEndOffset(), parent + "; prefix:" + prefix + "; suffix:" + suffix);
|
|
String end = allText.substring(rangeMarker.getEndOffset(), parentRange.getEndOffset());
|
|
//noinspection SSBasedInspection (suggested replacement breaks behavior)
|
|
if (StringUtil.stripQuotesAroundValue(end).trim().isEmpty() && suffix == null) end = "";
|
|
|
|
final String start = beg + (prefix != null ? prefix : "");
|
|
refIdx[0] = start.length();
|
|
text = start + refText + (suffix != null ? suffix : "") + end;
|
|
}
|
|
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
|
return parent instanceof PsiStatement ? factory.createStatementFromText(text, parent) :
|
|
parent instanceof PsiCodeBlock ? factory.createCodeBlockFromText(text, parent)
|
|
: factory.createExpressionFromText(text, parent);
|
|
}
|
|
|
|
@Override
|
|
protected boolean invokeImpl(Project project, PsiLocalVariable localVariable, Editor editor) {
|
|
throw new UnsupportedOperationException();
|
|
}
|
|
|
|
protected static void highlightReplacedOccurrences(Project project, Editor editor, PsiElement[] replacedOccurrences){
|
|
if (editor == null) return;
|
|
if (ApplicationManager.getApplication().isUnitTestMode()) return;
|
|
HighlightManager highlightManager = HighlightManager.getInstance(project);
|
|
EditorColorsManager colorsManager = EditorColorsManager.getInstance();
|
|
TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
|
|
highlightManager.addOccurrenceHighlights(editor, replacedOccurrences, attributes, true, null);
|
|
WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting"));
|
|
}
|
|
|
|
protected abstract void showErrorMessage(Project project, Editor editor, String message);
|
|
|
|
|
|
protected boolean reportConflicts(MultiMap<PsiElement,String> conflicts, Project project, IntroduceVariableSettings settings){
|
|
return false;
|
|
}
|
|
|
|
public IntroduceVariableSettings getSettings(Project project, Editor editor,
|
|
PsiExpression expr, PsiExpression[] occurrences,
|
|
final TypeSelectorManagerImpl typeSelectorManager,
|
|
boolean declareFinalIfAll,
|
|
boolean anyAssignmentLHS,
|
|
final InputValidator validator,
|
|
PsiElement anchor,
|
|
final JavaReplaceChoice replaceChoice) {
|
|
final boolean replaceAll = replaceChoice.isMultiple();
|
|
final SuggestedNameInfo suggestedName = getSuggestedName(typeSelectorManager.getDefaultType(), expr, anchor);
|
|
final String variableName = suggestedName.names.length > 0 ? suggestedName.names[0] : "";
|
|
final boolean declareFinal = replaceAll && declareFinalIfAll || !anyAssignmentLHS && createFinals(anchor.getContainingFile());
|
|
final boolean replaceWrite = anyAssignmentLHS && replaceChoice.isAll();
|
|
return new IntroduceVariableSettings() {
|
|
@Override
|
|
public String getEnteredName() {
|
|
return variableName;
|
|
}
|
|
|
|
@Override
|
|
public boolean isReplaceAllOccurrences() {
|
|
return replaceAll;
|
|
}
|
|
|
|
@Override
|
|
public boolean isDeclareFinal() {
|
|
return declareFinal;
|
|
}
|
|
|
|
@Override
|
|
public boolean isReplaceLValues() {
|
|
return replaceWrite;
|
|
}
|
|
|
|
@Override
|
|
public PsiType getSelectedType() {
|
|
final PsiType selectedType = typeSelectorManager.getTypeSelector().getSelectedType();
|
|
return selectedType != null ? selectedType : typeSelectorManager.getDefaultType();
|
|
}
|
|
|
|
@Override
|
|
public boolean isOK() {
|
|
return true;
|
|
}
|
|
};
|
|
}
|
|
|
|
public static boolean createFinals(@NotNull PsiFile file) {
|
|
final Boolean createFinals = JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS;
|
|
return createFinals == null ?
|
|
JavaCodeStyleSettings.getInstance(file).GENERATE_FINAL_LOCALS :
|
|
createFinals.booleanValue();
|
|
}
|
|
|
|
public static boolean checkAnchorBeforeThisOrSuper(final Project project,
|
|
final Editor editor,
|
|
final PsiElement tempAnchorElement,
|
|
final String refactoringName,
|
|
final String helpID) {
|
|
if (tempAnchorElement instanceof PsiExpressionStatement) {
|
|
PsiExpression enclosingExpr = ((PsiExpressionStatement)tempAnchorElement).getExpression();
|
|
if (enclosingExpr instanceof PsiMethodCallExpression) {
|
|
PsiMethod method = ((PsiMethodCallExpression)enclosingExpr).resolveMethod();
|
|
if (method != null && method.isConstructor()) {
|
|
//This is either 'this' or 'super', both must be the first in the respective constructor
|
|
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("invalid.expression.context"));
|
|
CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpID);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public interface Validator {
|
|
boolean isOK(IntroduceVariableSettings dialog);
|
|
}
|
|
|
|
public static void checkInLoopCondition(PsiExpression occurence, MultiMap<PsiElement, String> conflicts) {
|
|
final PsiElement loopForLoopCondition = RefactoringUtil.getLoopForLoopCondition(occurence);
|
|
if (loopForLoopCondition == null || loopForLoopCondition instanceof PsiWhileStatement) return;
|
|
final List<PsiVariable> referencedVariables = RefactoringUtil.collectReferencedVariables(occurence);
|
|
final List<PsiVariable> modifiedInBody = new ArrayList<>();
|
|
for (PsiVariable psiVariable : referencedVariables) {
|
|
if (RefactoringUtil.isModifiedInScope(psiVariable, loopForLoopCondition)) {
|
|
modifiedInBody.add(psiVariable);
|
|
}
|
|
}
|
|
|
|
if (!modifiedInBody.isEmpty()) {
|
|
for (PsiVariable variable : modifiedInBody) {
|
|
final String message = RefactoringBundle.message("is.modified.in.loop.body", RefactoringUIUtil.getDescription(variable, false));
|
|
conflicts.putValue(variable, CommonRefactoringUtil.capitalize(message));
|
|
}
|
|
conflicts.putValue(occurence, RefactoringBundle.message("introducing.variable.may.break.code.logic"));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public AbstractInplaceIntroducer getInplaceIntroducer() {
|
|
return myInplaceIntroducer;
|
|
}
|
|
|
|
static class OccurrencesInfo {
|
|
List<PsiExpression> myOccurrences;
|
|
List<PsiExpression> myNonWrite;
|
|
boolean myCantReplaceAll;
|
|
boolean myCantReplaceAllButWrite;
|
|
boolean myHasWriteAccess;
|
|
final String myChainMethodName;
|
|
|
|
OccurrencesInfo(PsiExpression[] occurrences) {
|
|
myOccurrences = Arrays.asList(occurrences);
|
|
myNonWrite = new ArrayList<>();
|
|
myCantReplaceAll = false;
|
|
myCantReplaceAllButWrite = false;
|
|
for (PsiExpression occurrence : myOccurrences) {
|
|
if (!RefactoringUtil.isAssignmentLHS(occurrence)) {
|
|
myNonWrite.add(occurrence);
|
|
} else if (isFinalVariableOnLHS(occurrence)) {
|
|
myCantReplaceAll = true;
|
|
} else if (!myNonWrite.isEmpty()){
|
|
myCantReplaceAllButWrite = true;
|
|
myCantReplaceAll = true;
|
|
}
|
|
}
|
|
myHasWriteAccess = myOccurrences.size() > myNonWrite.size() && myOccurrences.size() > 1;
|
|
myChainMethodName = getChainCallExtractor();
|
|
}
|
|
|
|
private String getChainCallExtractor() {
|
|
if (myHasWriteAccess || myOccurrences.isEmpty()) return null;
|
|
PsiExpression expression = myOccurrences.get(0);
|
|
// The whole lambda body selected
|
|
if (myOccurrences.size() == 1 && expression.getParent() instanceof PsiLambdaExpression) return null;
|
|
PsiElement parent = PsiTreeUtil.findCommonParent(myOccurrences);
|
|
if (parent == null) return null;
|
|
PsiType type = expression.getType();
|
|
PsiLambdaExpression lambda = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, true, PsiStatement.class);
|
|
ChainCallExtractor extractor = ChainCallExtractor.findExtractor(lambda, expression, type);
|
|
if (extractor == null) return null;
|
|
PsiParameter parameter = lambda.getParameterList().getParameters()[0];
|
|
if (!ReferencesSearch.search(parameter).forEach((Processor<PsiReference>)ref ->
|
|
myOccurrences.stream().anyMatch(expr -> PsiTreeUtil.isAncestor(expr, ref.getElement(), false)))) {
|
|
return null;
|
|
}
|
|
return extractor.getMethodName(parameter, expression, type);
|
|
}
|
|
|
|
@NotNull
|
|
LinkedHashMap<JavaReplaceChoice, List<PsiExpression>> buildOccurrencesMap(PsiExpression expr) {
|
|
final LinkedHashMap<JavaReplaceChoice, List<PsiExpression>> occurrencesMap = ContainerUtil.newLinkedHashMap();
|
|
if (myChainMethodName != null) {
|
|
if (myOccurrences.size() > 1 && !myCantReplaceAll) {
|
|
occurrencesMap.put(JavaReplaceChoice.NO, Collections.singletonList(expr));
|
|
occurrencesMap.put(JavaReplaceChoice.ALL, myOccurrences);
|
|
occurrencesMap.put(
|
|
new JavaReplaceChoice("Replace all {0} occurrences and extract as ''" + myChainMethodName + "'' operation", true, true, true),
|
|
myOccurrences);
|
|
} else {
|
|
occurrencesMap.put(JavaReplaceChoice.NO_CHAIN, Collections.singletonList(expr));
|
|
occurrencesMap.put(new JavaReplaceChoice("Extract as ''" + myChainMethodName + "'' operation", false, false, true),
|
|
Collections.singletonList(expr));
|
|
}
|
|
} else {
|
|
occurrencesMap.put(JavaReplaceChoice.NO, Collections.singletonList(expr));
|
|
if (myHasWriteAccess && !myCantReplaceAllButWrite) {
|
|
occurrencesMap.put(JavaReplaceChoice.NO_WRITE, myNonWrite);
|
|
}
|
|
|
|
if (myOccurrences.size() > 1 && !myCantReplaceAll) {
|
|
JavaReplaceChoice choice = occurrencesMap.containsKey(JavaReplaceChoice.NO_WRITE)
|
|
? new JavaReplaceChoice("Replace read and write occurrences (will change semantics!)", true, true, false)
|
|
: JavaReplaceChoice.ALL;
|
|
occurrencesMap.put(choice, myOccurrences);
|
|
}
|
|
}
|
|
return occurrencesMap;
|
|
}
|
|
}
|
|
}
|