mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
Java: Reusable implementation of "Extract Parameters to Replace Duplicates" (IDEA-179924)
This commit is contained in:
@@ -49,20 +49,20 @@ public class DuplicatesFinder {
|
||||
private final List<PsiElement> myPatternAsList;
|
||||
private boolean myMultipleExitPoints;
|
||||
@Nullable private final ReturnValue myReturnValue;
|
||||
private final boolean myWithAdditionalParameters;
|
||||
private final boolean myWithExtractedParameters;
|
||||
|
||||
public DuplicatesFinder(@NotNull PsiElement[] pattern,
|
||||
InputVariables parameters,
|
||||
@Nullable ReturnValue returnValue,
|
||||
@NotNull List<? extends PsiVariable> outputParameters,
|
||||
boolean withAdditionalParameters) {
|
||||
boolean withExtractedParameters) {
|
||||
myReturnValue = returnValue;
|
||||
LOG.assertTrue(pattern.length > 0);
|
||||
myPattern = pattern;
|
||||
myPatternAsList = Arrays.asList(myPattern);
|
||||
myParameters = parameters;
|
||||
myOutputParameters = outputParameters;
|
||||
myWithAdditionalParameters = withAdditionalParameters;
|
||||
myWithExtractedParameters = withExtractedParameters;
|
||||
|
||||
final PsiElement codeFragment = ControlFlowUtil.findCodeFragment(pattern[0]);
|
||||
try {
|
||||
@@ -324,6 +324,9 @@ public class DuplicatesFinder {
|
||||
return match.putParameter(parameter, candidate);
|
||||
}
|
||||
|
||||
Boolean matchedExtractablePart = matchExtractableExpression(pattern, candidate, candidates, match);
|
||||
if (matchedExtractablePart != null) return matchedExtractablePart;
|
||||
|
||||
if (!canBeEquivalent(pattern, candidate)) return false; // Q : is it correct to check implementation classes?
|
||||
|
||||
if (pattern instanceof PsiExpressionList && candidate instanceof PsiExpressionList) { //check varargs
|
||||
@@ -372,10 +375,7 @@ public class DuplicatesFinder {
|
||||
}
|
||||
final PsiElement qualifier2 = ((PsiJavaCodeReferenceElement)candidate).getQualifier();
|
||||
if (!equivalentResolve(resolveResult1, resolveResult2, qualifier2)) {
|
||||
if (myWithAdditionalParameters) {
|
||||
return match.putAdditionalParameter(pattern, candidate);
|
||||
}
|
||||
return false;
|
||||
return matchExtractableVariable(pattern, candidate, match);
|
||||
}
|
||||
PsiElement qualifier1 = ((PsiJavaCodeReferenceElement)pattern).getQualifier();
|
||||
if (qualifier1 instanceof PsiReferenceExpression && qualifier2 instanceof PsiReferenceExpression &&
|
||||
@@ -552,6 +552,45 @@ public class DuplicatesFinder {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Boolean matchExtractableExpression(PsiElement pattern, PsiElement candidate, List<PsiElement> candidates, Match match) {
|
||||
if (!(pattern instanceof PsiExpression) || !(candidate instanceof PsiExpression)) {
|
||||
return null;
|
||||
}
|
||||
ExtractableExpressionPart part1 = ExtractableExpressionPart.match((PsiExpression)pattern, myPatternAsList);
|
||||
if (part1 == null) {
|
||||
return null;
|
||||
}
|
||||
ExtractableExpressionPart part2 = ExtractableExpressionPart.match((PsiExpression)candidate, candidates);
|
||||
if (part2 == null) {
|
||||
return null;
|
||||
}
|
||||
if (part1.myValue != null && part2.myValue != null && part1.myValue.equals(part2.myValue)) {
|
||||
return true;
|
||||
}
|
||||
if (part1.myVariable == null || part2.myVariable == null) {
|
||||
return myWithExtractedParameters &&
|
||||
match.putExtractedParameter(part1, part2);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean matchExtractableVariable(PsiElement pattern, PsiElement candidate, Match match) {
|
||||
if (!myWithExtractedParameters || !(pattern instanceof PsiReferenceExpression) || !(candidate instanceof PsiReferenceExpression)) {
|
||||
return false;
|
||||
}
|
||||
ExtractableExpressionPart part1 = ExtractableExpressionPart.matchVariable((PsiReferenceExpression)pattern, null);
|
||||
if (part1 == null || part1.myVariable == null) {
|
||||
return false;
|
||||
}
|
||||
ExtractableExpressionPart part2 = ExtractableExpressionPart.matchVariable((PsiReferenceExpression)candidate, null);
|
||||
if (part2 == null || part2.myVariable == null) {
|
||||
return false;
|
||||
}
|
||||
return match.putExtractedParameter(part1, part2);
|
||||
}
|
||||
|
||||
|
||||
private static boolean matchModifierList(PsiModifierList modifierList1, PsiModifierList modifierList2) {
|
||||
if (!(modifierList1.getParent() instanceof PsiLocalVariable)) {
|
||||
// local variables can only have a final modifier, and are considered equivalent with or without it.
|
||||
@@ -676,7 +715,7 @@ public class DuplicatesFinder {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isUnder(PsiElement element, List<PsiElement> parents) {
|
||||
static boolean isUnder(@Nullable PsiElement element, @NotNull List<PsiElement> parents) {
|
||||
if (element == null) return false;
|
||||
for (final PsiElement parent : parents) {
|
||||
if (PsiTreeUtil.isAncestor(parent, element, false)) return true;
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.refactoring.util.duplicates;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class ExtractableExpressionPart {
|
||||
final PsiExpression myUsage;
|
||||
final PsiVariable myVariable;
|
||||
final Object myValue;
|
||||
final PsiType myType;
|
||||
|
||||
private ExtractableExpressionPart(@NotNull PsiExpression usage, PsiVariable variable, Object value, @NotNull PsiType type) {
|
||||
myUsage = usage;
|
||||
myVariable = variable;
|
||||
myValue = value;
|
||||
myType = type;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static PsiType commonType(@NotNull ExtractableExpressionPart part1, @NotNull ExtractableExpressionPart part2) {
|
||||
return commonType(part1.myType, part2.myType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PsiType commonType(@NotNull PsiType type1, @NotNull PsiType type2) {
|
||||
if (type1.isAssignableFrom(type2)) {
|
||||
return type1;
|
||||
}
|
||||
if (type2.isAssignableFrom(type1)) {
|
||||
return type2;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
boolean isEquivalent(@NotNull ExtractableExpressionPart part) {
|
||||
if (myVariable != null && myVariable.equals(part.myVariable)) {
|
||||
return true;
|
||||
}
|
||||
if (myValue != null && myValue.equals(part.myValue)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ExtractableExpressionPart match(@NotNull PsiExpression expression, @Nullable List<PsiElement> scope) {
|
||||
if (PsiUtil.isConstantExpression(expression)) {
|
||||
if (PsiTreeUtil.findChildOfType(expression, PsiJavaCodeReferenceElement.class) != null) {
|
||||
return null;
|
||||
}
|
||||
return matchConstant(expression);
|
||||
}
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
return matchVariable((PsiReferenceExpression)expression, scope);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static ExtractableExpressionPart matchConstant(@NotNull PsiExpression expression) {
|
||||
PsiConstantEvaluationHelper constantHelper = JavaPsiFacade.getInstance(expression.getProject()).getConstantEvaluationHelper();
|
||||
Object value = constantHelper.computeConstantExpression(expression, false);
|
||||
if (value != null) {
|
||||
PsiType type = expression.getType();
|
||||
if (type != null) {
|
||||
return new ExtractableExpressionPart(expression, null, value, type);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static ExtractableExpressionPart matchVariable(@NotNull PsiReferenceExpression expression, @Nullable List<PsiElement> scope) {
|
||||
PsiElement resolved = expression.resolve();
|
||||
if (resolved instanceof PsiVariable && (scope == null || !DuplicatesFinder.isUnder(resolved, scope))) {
|
||||
PsiVariable variable = (PsiVariable)resolved;
|
||||
return new ExtractableExpressionPart(expression, variable, null, variable.getType());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiExpression getUsage() {
|
||||
return myUsage;
|
||||
}
|
||||
}
|
||||
@@ -22,89 +22,96 @@ import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class ExtractedParameter {
|
||||
@NotNull public final List<PsiReferenceExpression> myPatternUsages = new ArrayList<>();
|
||||
@NotNull public final PsiVariable myPatternVariable;
|
||||
@NotNull public final PsiReferenceExpression myCandidateUsage;
|
||||
@NotNull public final PsiVariable myCandidateVariable;
|
||||
@NotNull public final PsiType myType;
|
||||
@NotNull public final ExtractableExpressionPart myPattern;
|
||||
@NotNull public final ExtractableExpressionPart myCandidate;
|
||||
@NotNull public final Map<PsiExpression, PsiExpression> myUsages = new HashMap<>();
|
||||
|
||||
ExtractedParameter(@NotNull PsiVariable patternVariable,
|
||||
@NotNull PsiReferenceExpression patternUsage,
|
||||
@NotNull PsiVariable candidateVariable,
|
||||
@NotNull PsiReferenceExpression candidateUsage,
|
||||
@NotNull PsiType type) {
|
||||
myPatternVariable = patternVariable;
|
||||
myPatternUsages.add(patternUsage);
|
||||
myCandidateVariable = candidateVariable;
|
||||
myCandidateUsage = candidateUsage;
|
||||
public ExtractedParameter(@NotNull ExtractableExpressionPart patternPart,
|
||||
@NotNull ExtractableExpressionPart candidatePart,
|
||||
@NotNull PsiType type) {
|
||||
myType = type;
|
||||
myPattern = patternPart;
|
||||
myCandidate = candidatePart;
|
||||
addUsages(patternPart, candidatePart);
|
||||
}
|
||||
|
||||
public static boolean match(PsiElement pattern, PsiElement candidate, @NotNull List<ExtractedParameter> parameters) {
|
||||
if (pattern instanceof PsiReferenceExpression && candidate instanceof PsiReferenceExpression) {
|
||||
PsiReferenceExpression patternUsage = (PsiReferenceExpression)pattern;
|
||||
PsiReferenceExpression candidateUsage = (PsiReferenceExpression)candidate;
|
||||
PsiElement resolvedPattern = patternUsage.resolve();
|
||||
PsiElement resolvedCandidate = candidateUsage.resolve();
|
||||
if (resolvedPattern instanceof PsiVariable && resolvedCandidate instanceof PsiVariable) {
|
||||
PsiVariable patternVariable = (PsiVariable)resolvedPattern;
|
||||
PsiVariable candidateVariable = (PsiVariable)resolvedCandidate;
|
||||
if (isStaticOrLocal(patternVariable) && isStaticOrLocal(candidateVariable)) {
|
||||
|
||||
for (ExtractedParameter parameter : parameters) {
|
||||
boolean samePattern = resolvedPattern.equals(parameter.myPatternVariable);
|
||||
boolean sameCandidate = resolvedCandidate.equals(parameter.myCandidateVariable);
|
||||
if (samePattern && sameCandidate) {
|
||||
parameter.myPatternUsages.add(patternUsage);
|
||||
return true;
|
||||
}
|
||||
if (samePattern || sameCandidate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
PsiType type = getParameterType(patternVariable, candidateVariable);
|
||||
if (type != null) {
|
||||
parameters.add(new ExtractedParameter(patternVariable, patternUsage, candidateVariable, candidateUsage, type));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public static boolean match(@NotNull ExtractableExpressionPart patternPart,
|
||||
@NotNull ExtractableExpressionPart candidatePart,
|
||||
@NotNull List<ExtractedParameter> parameters) {
|
||||
PsiType type = ExtractableExpressionPart.commonType(patternPart, candidatePart);
|
||||
if (type == null) {
|
||||
return false;
|
||||
}
|
||||
if (patternPart.myVariable != null && !isStaticOrLocal(patternPart.myVariable)) {
|
||||
return false;
|
||||
}
|
||||
if (candidatePart.myVariable != null && !isStaticOrLocal(candidatePart.myVariable)) {
|
||||
return false;
|
||||
}
|
||||
for (ExtractedParameter parameter : parameters) {
|
||||
boolean samePattern = parameter.samePattern(patternPart);
|
||||
boolean sameCandidate = parameter.sameCandidate(candidatePart);
|
||||
if (samePattern && sameCandidate) {
|
||||
parameter.addUsages(patternPart, candidatePart);
|
||||
return true;
|
||||
}
|
||||
if (samePattern || sameCandidate) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
parameters.add(new ExtractedParameter(patternPart, candidatePart, type));
|
||||
return true;
|
||||
}
|
||||
|
||||
public static List<Match> getCompatibleMatches(List<Match> matches, PsiElement[] pattern) {
|
||||
Set<PsiVariable> patternVariables = null;
|
||||
private void addUsages(ExtractableExpressionPart patternPart, ExtractableExpressionPart candidatePart) {
|
||||
myUsages.put(patternPart.getUsage(), candidatePart.getUsage());
|
||||
}
|
||||
|
||||
private boolean sameCandidate(ExtractableExpressionPart part) {
|
||||
return myCandidate.isEquivalent(part);
|
||||
}
|
||||
|
||||
private boolean samePattern(ExtractableExpressionPart part) {
|
||||
return myPattern.isEquivalent(part);
|
||||
}
|
||||
|
||||
public static List<Match> getCompatibleMatches(List<Match> matches,
|
||||
PsiElement[] pattern,
|
||||
List<PsiElement[]> candidates) {
|
||||
List<Match> result = new ArrayList<>();
|
||||
Set<PsiExpression> firstUsages = null;
|
||||
for (Match match : matches) {
|
||||
List<ExtractedParameter> parameters = match.getExtractedParameters();
|
||||
if (patternVariables == null) {
|
||||
patternVariables = getPatternVariables(parameters);
|
||||
PsiElement[] candidateElements = ContainerUtil.find(candidates, elements -> match.getMatchStart() == elements[0]);
|
||||
Set<PsiVariable> candidateVariables = ContainerUtil.map2SetNotNull(parameters, parameter -> parameter.myCandidate.myVariable);
|
||||
if (candidateElements == null || containsModifiedField(candidateElements, candidateVariables)) {
|
||||
continue;
|
||||
}
|
||||
Set<PsiExpression> patternUsages = StreamEx.of(parameters).map(p -> p.myPattern.getUsage()).toSet();
|
||||
if (firstUsages == null) {
|
||||
Set<PsiVariable> patternVariables = ContainerUtil.map2SetNotNull(parameters, parameter -> parameter.myPattern.myVariable);
|
||||
if (containsModifiedField(pattern, patternVariables)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
firstUsages = patternUsages;
|
||||
result.add(match);
|
||||
}
|
||||
else if (patternVariables.equals(getPatternVariables(parameters))) {
|
||||
else if (firstUsages.equals(patternUsages)) {
|
||||
result.add(match);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean containsModifiedField(PsiElement[] pattern, Set<PsiVariable> variables) {
|
||||
private static boolean containsModifiedField(@NotNull PsiElement[] elements, Set<PsiVariable> variables) {
|
||||
Set<PsiField> fields = StreamEx.of(variables)
|
||||
.select(PsiField.class)
|
||||
.filter(field -> !field.hasModifierProperty(PsiModifier.FINAL))
|
||||
@@ -112,7 +119,7 @@ public class ExtractedParameter {
|
||||
|
||||
if (!fields.isEmpty()) {
|
||||
FieldModificationVisitor visitor = new FieldModificationVisitor(fields);
|
||||
for (PsiElement element : pattern) {
|
||||
for (PsiElement element : elements) {
|
||||
element.accept(visitor);
|
||||
if (visitor.myModified) {
|
||||
return true;
|
||||
@@ -122,14 +129,6 @@ public class ExtractedParameter {
|
||||
return false;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static Set<PsiVariable> getPatternVariables(@Nullable List<ExtractedParameter> parameters) {
|
||||
if (parameters != null) {
|
||||
return ContainerUtil.map2Set(parameters, parameter -> parameter.myPatternVariable);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
static boolean isStaticOrLocal(@NotNull PsiVariable variable) {
|
||||
if (variable instanceof PsiField && variable.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
return true;
|
||||
@@ -137,19 +136,6 @@ public class ExtractedParameter {
|
||||
return variable instanceof PsiLocalVariable || variable instanceof PsiParameter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static PsiType getParameterType(@NotNull PsiVariable patternVariable, @NotNull PsiVariable candidateVariable) {
|
||||
PsiType patternType = patternVariable.getType();
|
||||
PsiType candidateType = candidateVariable.getType();
|
||||
if (patternType.isAssignableFrom(candidateType)) {
|
||||
return patternType;
|
||||
}
|
||||
if (candidateType.isAssignableFrom(patternType)) {
|
||||
return candidateType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class FieldModificationVisitor extends JavaRecursiveElementWalkingVisitor {
|
||||
private final Set<PsiField> myFields;
|
||||
private boolean myModified;
|
||||
@@ -166,8 +152,18 @@ public class ExtractedParameter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitUnaryExpression(PsiUnaryExpression expression) {
|
||||
super.visitUnaryExpression(expression);
|
||||
public void visitPrefixExpression(PsiPrefixExpression expression) {
|
||||
super.visitPrefixExpression(expression);
|
||||
|
||||
IElementType op = expression.getOperationTokenType();
|
||||
if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) {
|
||||
visitModifiedExpression(expression.getOperand());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPostfixExpression(PsiPostfixExpression expression) {
|
||||
super.visitPostfixExpression(expression);
|
||||
|
||||
IElementType op = expression.getOperationTokenType();
|
||||
if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) {
|
||||
|
||||
@@ -424,10 +424,11 @@ public final class Match {
|
||||
return getMatchStart().getContainingFile();
|
||||
}
|
||||
|
||||
public boolean putAdditionalParameter(@NotNull PsiElement pattern, @NotNull PsiElement candidate) {
|
||||
return ExtractedParameter.match(pattern, candidate, myExtractedParameters);
|
||||
public boolean putExtractedParameter(@NotNull ExtractableExpressionPart patternPart, @NotNull ExtractableExpressionPart candidatePart) {
|
||||
return ExtractedParameter.match(patternPart, candidatePart, myExtractedParameters);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ExtractedParameter> getExtractedParameters() {
|
||||
return myExtractedParameters;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import com.intellij.ide.DataManager;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.ide.util.PsiClassListCellRenderer;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.LogicalPosition;
|
||||
@@ -73,6 +74,7 @@ import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.VisibilityUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -89,7 +91,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
protected final PsiElement[] myElements;
|
||||
private final PsiBlockStatement myEnclosingBlockStatement;
|
||||
private final PsiType myForcedReturnType;
|
||||
private final String myRefactoringName;
|
||||
protected final String myRefactoringName;
|
||||
protected final String myInitialMethodName;
|
||||
private final String myHelpId;
|
||||
|
||||
@@ -128,6 +130,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
protected boolean myCanBeChainedConstructor;
|
||||
protected boolean myIsChainedConstructor;
|
||||
private List<Match> myDuplicates;
|
||||
private ParametrizedDuplicates myParametrizedDuplicates;
|
||||
@PsiModifier.ModifierConstant protected String myMethodVisibility = PsiModifier.PRIVATE;
|
||||
protected boolean myGenerateConditionalExit;
|
||||
protected PsiStatement myFirstExitStatementCopy;
|
||||
@@ -135,7 +138,7 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
private PsiMethodCallExpression myMethodCall;
|
||||
protected boolean myNullConditionalCheck;
|
||||
protected boolean myNotNullConditionalCheck;
|
||||
private Nullness myNullness;
|
||||
protected Nullness myNullness;
|
||||
|
||||
public ExtractMethodProcessor(Project project,
|
||||
Editor editor,
|
||||
@@ -861,25 +864,23 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
|
||||
@Nullable
|
||||
private DuplicatesFinder initDuplicates() {
|
||||
List<PsiElement> elements = new ArrayList<>();
|
||||
for (PsiElement element : myElements) {
|
||||
if (!(element instanceof PsiWhiteSpace || element instanceof PsiComment)) {
|
||||
elements.add(element);
|
||||
}
|
||||
}
|
||||
PsiElement[] elements = StreamEx.of(myElements)
|
||||
.filter(element -> !(element instanceof PsiWhiteSpace || element instanceof PsiComment))
|
||||
.toArray(PsiElement[]::new);
|
||||
|
||||
if (myExpression != null) {
|
||||
DuplicatesFinder finder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(),
|
||||
new ArrayList<>());
|
||||
DuplicatesFinder finder = new DuplicatesFinder(elements, myInputVariables.copy(), Collections.emptyList());
|
||||
myDuplicates = finder.findDuplicates(myTargetClass);
|
||||
myParametrizedDuplicates = ParametrizedDuplicates.findDuplicates(this);
|
||||
return finder;
|
||||
}
|
||||
else if (elements.size() > 0){
|
||||
DuplicatesFinder myDuplicatesFinder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(),
|
||||
myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null,
|
||||
Arrays.asList(myOutputVariables));
|
||||
myDuplicates = myDuplicatesFinder.findDuplicates(myTargetClass);
|
||||
return myDuplicatesFinder;
|
||||
else if (elements.length != 0) {
|
||||
DuplicatesFinder finder = new DuplicatesFinder(elements, myInputVariables.copy(),
|
||||
myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null,
|
||||
Arrays.asList(myOutputVariables));
|
||||
myDuplicates = finder.findDuplicates(myTargetClass);
|
||||
myParametrizedDuplicates = ParametrizedDuplicates.findDuplicates(this);
|
||||
return finder;
|
||||
} else {
|
||||
myDuplicates = new ArrayList<>();
|
||||
}
|
||||
@@ -1960,30 +1961,18 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (myExtractedMethod != null) {
|
||||
final ExtractMethodSignatureSuggester suggester = new ExtractMethodSignatureSuggester(myProject, myExtractedMethod, myMethodCall, myVariableDatum);
|
||||
duplicates = suggester.getDuplicates(myExtractedMethod, myMethodCall, myInputVariables.getFolding());
|
||||
if (duplicates != null && !duplicates.isEmpty()) {
|
||||
myDuplicates = duplicates;
|
||||
myExtractedMethod = suggester.getExtractedMethod();
|
||||
myMethodCall = suggester.getMethodCall();
|
||||
myVariableDatum = suggester.getVariableData();
|
||||
|
||||
final List<PsiVariable> outputVariables = new ArrayList<>();
|
||||
for (PsiReturnStatement statement : PsiUtil.findReturnStatements(myExtractedMethod)) {
|
||||
final PsiExpression returnValue = statement.getReturnValue();
|
||||
if (returnValue instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolve = ((PsiReferenceExpression)returnValue).resolve();
|
||||
if (resolve instanceof PsiLocalVariable) {
|
||||
outputVariables.add((PsiVariable)resolve);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outputVariables.size() == 1) {
|
||||
myOutputVariable = outputVariables.get(0);
|
||||
}
|
||||
if (myExtractedMethod != null && myParametrizedDuplicates != null) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode() ||
|
||||
new ExtractMethodSignatureSuggester.PreviewDialog(myExtractedMethod, myParametrizedDuplicates.getParametrizedMethod(),
|
||||
myMethodCall, myParametrizedDuplicates.getParametrizedCall(),
|
||||
myParametrizedDuplicates.getSize()).showAndGet()) {
|
||||
|
||||
myDuplicates = myParametrizedDuplicates.getDuplicates();
|
||||
WriteCommandAction.runWriteCommandAction(myProject, () -> {
|
||||
myExtractedMethod = myParametrizedDuplicates.replaceMethod(myExtractedMethod);
|
||||
myMethodCall = myParametrizedDuplicates.replaceCall(myMethodCall);
|
||||
});
|
||||
myVariableDatum = myParametrizedDuplicates.getVariableData();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2018,6 +2007,31 @@ public class ExtractMethodProcessor implements MatchProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public UniqueNameGenerator getParameterNameGenerator(PsiElement scopeElement) {
|
||||
UniqueNameGenerator uniqueNameGenerator = new UniqueNameGenerator();
|
||||
for (VariableData data : myInputVariables.getInputVariables()) {
|
||||
if (data.variable != null) {
|
||||
String name = data.variable.getName();
|
||||
if (name != null) uniqueNameGenerator.addExistingName(name);
|
||||
}
|
||||
}
|
||||
for (PsiVariable variable : myOutputVariables) {
|
||||
String name = variable.getName();
|
||||
if (name != null) uniqueNameGenerator.addExistingName(name);
|
||||
}
|
||||
PsiElement superParent = PsiTreeUtil.getParentOfType(scopeElement, PsiMember.class, PsiLambdaExpression.class);
|
||||
if (superParent != null) {
|
||||
SyntaxTraverser.psiTraverser().withRoot(superParent)
|
||||
.filter(element -> element instanceof PsiVariable)
|
||||
.forEach(element -> {
|
||||
String name = ((PsiVariable)element).getName();
|
||||
if (name != null) uniqueNameGenerator.addExistingName(name);
|
||||
});
|
||||
}
|
||||
return uniqueNameGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getReplaceDuplicatesTitle(int idx, int size) {
|
||||
return RefactoringBundle.message("process.duplicates.title", idx, size);
|
||||
|
||||
@@ -15,364 +15,27 @@
|
||||
*/
|
||||
package com.intellij.refactoring.extractMethod;
|
||||
|
||||
import com.intellij.codeInsight.JavaPsiEquivalenceUtil;
|
||||
import com.intellij.diff.DiffContentFactory;
|
||||
import com.intellij.diff.DiffManager;
|
||||
import com.intellij.diff.DiffRequestPanel;
|
||||
import com.intellij.diff.contents.DocumentContent;
|
||||
import com.intellij.diff.requests.SimpleDiffRequest;
|
||||
import com.intellij.diff.util.DiffUserDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.refactoring.util.RefactoringUtil;
|
||||
import com.intellij.refactoring.util.VariableData;
|
||||
import com.intellij.refactoring.util.duplicates.DuplicatesFinder;
|
||||
import com.intellij.refactoring.util.duplicates.Match;
|
||||
import com.intellij.refactoring.util.duplicates.MethodDuplicatesHandler;
|
||||
import com.intellij.ui.IdeBorderFactory;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.THashSet;
|
||||
import gnu.trove.TObjectHashingStrategy;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtractMethodSignatureSuggester {
|
||||
private static final Logger LOG = Logger.getInstance(ExtractMethodSignatureSuggester.class);
|
||||
private static final TObjectHashingStrategy<PsiExpression> ourEquivalenceStrategy = new TObjectHashingStrategy<PsiExpression>() {
|
||||
@Override
|
||||
public int computeHashCode(PsiExpression object) {
|
||||
return RefactoringUtil.unparenthesizeExpression(object).getClass().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(PsiExpression o1, PsiExpression o2) {
|
||||
return JavaPsiEquivalenceUtil
|
||||
.areExpressionsEquivalent(RefactoringUtil.unparenthesizeExpression(o1), RefactoringUtil.unparenthesizeExpression(o2));
|
||||
}
|
||||
};
|
||||
|
||||
private final Project myProject;
|
||||
private final PsiElementFactory myElementFactory;
|
||||
|
||||
private PsiMethod myExtractedMethod;
|
||||
private PsiMethodCallExpression myMethodCall;
|
||||
private VariableData[] myVariableData;
|
||||
|
||||
public ExtractMethodSignatureSuggester(Project project,
|
||||
PsiMethod extractedMethod,
|
||||
PsiMethodCallExpression methodCall,
|
||||
VariableData[] variableDatum) {
|
||||
myProject = project;
|
||||
myElementFactory = JavaPsiFacade.getElementFactory(project);
|
||||
|
||||
final PsiClass containingClass = extractedMethod.getContainingClass();
|
||||
LOG.assertTrue(containingClass != null);
|
||||
myExtractedMethod = myElementFactory.createMethodFromText(extractedMethod.getText(), containingClass.getLBrace());
|
||||
myMethodCall = methodCall;
|
||||
myVariableData = variableDatum;
|
||||
}
|
||||
|
||||
public List<Match> getDuplicates(final PsiMethod method, final PsiMethodCallExpression methodCall, ParametersFolder folder) {
|
||||
final List<Match> duplicates = findDuplicatesSignature(method, folder);
|
||||
if (duplicates != null && !duplicates.isEmpty()) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode() ||
|
||||
new PreviewDialog(method, myExtractedMethod, methodCall, myMethodCall, duplicates.size()).showAndGet()) {
|
||||
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
||||
WriteCommandAction.runWriteCommandAction(myProject, () -> {
|
||||
myMethodCall = (PsiMethodCallExpression)methodCall.replace(myMethodCall);
|
||||
myExtractedMethod = (PsiMethod)method.replace(myExtractedMethod);
|
||||
});
|
||||
|
||||
final DuplicatesFinder finder = MethodDuplicatesHandler.createDuplicatesFinder(myExtractedMethod);
|
||||
if (finder != null) {
|
||||
final List<VariableData> datas = finder.getParameters().getInputVariables();
|
||||
myVariableData = datas.toArray(new VariableData[datas.size()]);
|
||||
return finder.findDuplicates(myExtractedMethod.getContainingClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public PsiMethod getExtractedMethod() {
|
||||
return myExtractedMethod;
|
||||
}
|
||||
|
||||
public PsiMethodCallExpression getMethodCall() {
|
||||
return myMethodCall;
|
||||
}
|
||||
|
||||
public VariableData[] getVariableData() {
|
||||
return myVariableData;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public List<Match> findDuplicatesSignature(final PsiMethod method, ParametersFolder folder) {
|
||||
final List<PsiExpression> copies = new ArrayList<>();
|
||||
final InputVariables variables = detectTopLevelExpressionsToReplaceWithParameters(copies);
|
||||
if (variables == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DuplicatesFinder defaultFinder = MethodDuplicatesHandler.createDuplicatesFinder(myExtractedMethod);
|
||||
if (defaultFinder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DuplicatesFinder finder = new DuplicatesFinder(defaultFinder.getPattern(), variables, defaultFinder.getReturnValue(),
|
||||
new ArrayList<>()) {
|
||||
@Override
|
||||
protected boolean isSelf(PsiElement candidate) {
|
||||
return PsiTreeUtil.isAncestor(method, candidate, true);
|
||||
}
|
||||
};
|
||||
List<Match> duplicates = finder.findDuplicates(method.getContainingClass());
|
||||
|
||||
if (duplicates != null && !duplicates.isEmpty()) {
|
||||
restoreRenamedParams(copies, folder);
|
||||
if (!myMethodCall.isValid()) {
|
||||
return null;
|
||||
}
|
||||
myMethodCall = (PsiMethodCallExpression)myMethodCall.copy();
|
||||
inlineSameArguments(method, copies, variables, duplicates);
|
||||
for (PsiExpression expression : copies) {
|
||||
myMethodCall.getArgumentList().add(expression);
|
||||
}
|
||||
return duplicates;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void inlineSameArguments(PsiMethod method, List<PsiExpression> copies, InputVariables variables, List<Match> duplicates) {
|
||||
final List<VariableData> variableDatum = variables.getInputVariables();
|
||||
final Map<PsiVariable, PsiExpression> toInline = new HashMap<>();
|
||||
final int strongParamsCound = method.getParameterList().getParametersCount();
|
||||
for (int i = strongParamsCound; i < variableDatum.size(); i++) {
|
||||
VariableData variableData = variableDatum.get(i);
|
||||
final THashSet<PsiExpression> map = new THashSet<>(ourEquivalenceStrategy);
|
||||
if (!collectParamValues(duplicates, variableData, map)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final PsiExpression currentExpression = copies.get(i - strongParamsCound);
|
||||
map.add(currentExpression);
|
||||
|
||||
if (map.size() == 1) {
|
||||
toInline.put(variableData.variable, currentExpression);
|
||||
}
|
||||
}
|
||||
|
||||
if (!toInline.isEmpty()) {
|
||||
copies.removeAll(toInline.values());
|
||||
inlineArgumentsInMethodBody(toInline);
|
||||
removeRedundantParametersFromMethodSignature(toInline);
|
||||
}
|
||||
|
||||
removeUnusedStongParams(strongParamsCound);
|
||||
}
|
||||
|
||||
private void removeUnusedStongParams(int strongParamsCound) {
|
||||
final PsiExpression[] expressions = myMethodCall.getArgumentList().getExpressions();
|
||||
final PsiParameter[] parameters = myExtractedMethod.getParameterList().getParameters();
|
||||
final PsiCodeBlock body = myExtractedMethod.getBody();
|
||||
if (body != null) {
|
||||
final LocalSearchScope scope = new LocalSearchScope(body);
|
||||
for(int i = strongParamsCound - 1; i >= 0; i--) {
|
||||
final PsiParameter parameter = parameters[i];
|
||||
if (ReferencesSearch.search(parameter, scope).findFirst() == null) {
|
||||
parameter.delete();
|
||||
expressions[i].delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void removeRedundantParametersFromMethodSignature(Map<PsiVariable, PsiExpression> param2ExprMap) {
|
||||
for (PsiParameter parameter : myExtractedMethod.getParameterList().getParameters()) {
|
||||
if (param2ExprMap.containsKey(parameter)) {
|
||||
parameter.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void inlineArgumentsInMethodBody(final Map<PsiVariable, PsiExpression> param2ExprMap) {
|
||||
final Map<PsiExpression, PsiExpression> replacement = new HashMap<>();
|
||||
myExtractedMethod.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
super.visitReferenceExpression(expression);
|
||||
final PsiElement resolve = expression.resolve();
|
||||
if (resolve instanceof PsiVariable) {
|
||||
final PsiExpression toInlineExpr = param2ExprMap.get((PsiVariable)resolve);
|
||||
if (toInlineExpr != null) {
|
||||
replacement.put(expression, toInlineExpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
for (PsiExpression expression : replacement.keySet()) {
|
||||
expression.replace(replacement.get(expression));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean collectParamValues(List<Match> duplicates, VariableData variableData, THashSet<PsiExpression> map) {
|
||||
for (Match duplicate : duplicates) {
|
||||
final List<PsiElement> values = duplicate.getParameterValues(variableData.variable);
|
||||
if (values == null || values.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
boolean found = false;
|
||||
for (PsiElement value : values) {
|
||||
if (value instanceof PsiExpression) {
|
||||
map.add((PsiExpression)value);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void restoreRenamedParams(List<PsiExpression> copies, ParametersFolder folder) {
|
||||
final Map<String, String> renameMap = new HashMap<>();
|
||||
for (VariableData data : myVariableData) {
|
||||
final String replacement = folder.getGeneratedCallArgument(data);
|
||||
if (!data.name.equals(replacement)) {
|
||||
renameMap.put(data.name, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
if (!renameMap.isEmpty()) {
|
||||
for (PsiExpression currentExpression : copies) {
|
||||
final Map<PsiReferenceExpression, String> params = new HashMap<>();
|
||||
currentExpression.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
super.visitReferenceExpression(expression);
|
||||
final PsiElement resolve = expression.resolve();
|
||||
if (resolve instanceof PsiParameter && myExtractedMethod.equals(((PsiParameter)resolve).getDeclarationScope())) {
|
||||
final String name = ((PsiParameter)resolve).getName();
|
||||
final String variable = renameMap.get(name);
|
||||
if (renameMap.containsKey(name)) {
|
||||
params.put(expression, variable);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
for (PsiReferenceExpression expression : params.keySet()) {
|
||||
final String var = params.get(expression);
|
||||
expression.replace(myElementFactory.createExpressionFromText(var, expression));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private InputVariables detectTopLevelExpressionsToReplaceWithParameters(List<PsiExpression> copies) {
|
||||
final PsiParameter[] parameters = myExtractedMethod.getParameterList().getParameters();
|
||||
final List<PsiVariable> inputVariables = new ArrayList<>(Arrays.asList(parameters));
|
||||
final PsiCodeBlock body = myExtractedMethod.getBody();
|
||||
LOG.assertTrue(body != null);
|
||||
final PsiStatement[] pattern = body.getStatements();
|
||||
final List<PsiExpression> exprs = new ArrayList<>();
|
||||
for (PsiStatement statement : pattern) {
|
||||
if (statement instanceof PsiExpressionStatement) {
|
||||
final PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
|
||||
if (expression instanceof PsiIfStatement || expression instanceof PsiLoopStatement) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
statement.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitCallExpression(PsiCallExpression callExpression) {
|
||||
final PsiExpressionList list = callExpression.getArgumentList();
|
||||
if (list != null) {
|
||||
for (PsiExpression expression : list.getExpressions()) {
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolve = ((PsiReferenceExpression)expression).resolve();
|
||||
if (resolve instanceof PsiField) {
|
||||
exprs.add(expression);
|
||||
}
|
||||
} else {
|
||||
exprs.add(expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (exprs.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final UniqueNameGenerator uniqueNameGenerator = new UniqueNameGenerator();
|
||||
for (PsiParameter parameter : parameters) {
|
||||
uniqueNameGenerator.addExistingName(parameter.getName());
|
||||
}
|
||||
|
||||
SyntaxTraverser.psiTraverser().withRoot(myExtractedMethod.getBody())
|
||||
.filter(element -> element instanceof PsiVariable)
|
||||
.forEach(element -> uniqueNameGenerator.addExistingName(((PsiVariable)element).getName()));
|
||||
|
||||
|
||||
final THashMap<PsiExpression, String> unique = new THashMap<>(ourEquivalenceStrategy);
|
||||
final Map<PsiExpression, String> replacement = new HashMap<>();
|
||||
for (PsiExpression expr : exprs) {
|
||||
String name = unique.get(expr);
|
||||
if (name == null) {
|
||||
|
||||
final PsiType type = GenericsUtil.getVariableTypeByExpressionType(expr.getType());
|
||||
if (type == null ||
|
||||
type == PsiType.NULL ||
|
||||
PsiUtil.resolveClassInType(type) instanceof PsiAnonymousClass ||
|
||||
LambdaUtil.notInferredType(type)) return null;
|
||||
|
||||
copies.add(myElementFactory.createExpressionFromText(expr.getText(), body));
|
||||
|
||||
final SuggestedNameInfo info = JavaCodeStyleManager.getInstance(myProject).suggestVariableName(VariableKind.PARAMETER, null, expr, null);
|
||||
final String paramName = info.names.length > 0 ? info.names[0] : "p";
|
||||
name = uniqueNameGenerator.generateUniqueName(paramName);
|
||||
|
||||
final PsiParameter parameter = (PsiParameter)myExtractedMethod.getParameterList().add(myElementFactory.createParameter(name, type));
|
||||
inputVariables.add(parameter);
|
||||
unique.put(expr, name);
|
||||
}
|
||||
replacement.put(expr, name);
|
||||
}
|
||||
|
||||
for (PsiExpression expression : replacement.keySet()) {
|
||||
expression.replace(myElementFactory.createExpressionFromText(replacement.get(expression), null));
|
||||
}
|
||||
|
||||
return new InputVariables(inputVariables, myExtractedMethod.getProject(), new LocalSearchScope(myExtractedMethod), false);
|
||||
}
|
||||
|
||||
private static class PreviewDialog extends DialogWrapper {
|
||||
static class PreviewDialog extends DialogWrapper {
|
||||
private final PsiMethod myOldMethod;
|
||||
private final PsiMethod myNewMethod;
|
||||
private final PsiMethodCallExpression myOldCall;
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright 2000-2017 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.extractMethod;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.HelpID;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.refactoring.util.VariableData;
|
||||
import com.intellij.refactoring.util.duplicates.*;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class JavaDuplicatesExtractMethodProcessor extends ExtractMethodProcessor {
|
||||
private static final Logger LOG = Logger.getInstance(JavaDuplicatesExtractMethodProcessor.class);
|
||||
|
||||
public JavaDuplicatesExtractMethodProcessor(PsiElement[] elements, String refactoringName) {
|
||||
super(elements[0].getProject(), null, elements, null, refactoringName, "", HelpID.EXTRACT_METHOD);
|
||||
}
|
||||
|
||||
public void applyFrom(@NotNull ExtractMethodProcessor from, @NotNull Map<PsiVariable, PsiVariable> variablesMapping) {
|
||||
myMethodName = from.myMethodName;
|
||||
myStatic = from.myStatic;
|
||||
myIsChainedConstructor = from.myIsChainedConstructor;
|
||||
myMethodVisibility = from.myMethodVisibility;
|
||||
myNullness = from.myNullness;
|
||||
myReturnType = from.myReturnType;
|
||||
myOutputVariables = Arrays.stream(from.myOutputVariables)
|
||||
.map(variable -> variablesMapping.getOrDefault(variable, variable))
|
||||
.toArray(PsiVariable[]::new);
|
||||
myOutputVariable = ArrayUtil.getFirstElement(myOutputVariables);
|
||||
myArtificialOutputVariable = variablesMapping.getOrDefault(from.myArtificialOutputVariable, from.myArtificialOutputVariable);
|
||||
|
||||
int parameterCount = Math.max(myInputVariables.getInputVariables().size(), from.myVariableDatum.length);
|
||||
myVariableDatum = new VariableData[parameterCount];
|
||||
for (int i = 0; i < from.myVariableDatum.length; i++) {
|
||||
VariableData fromData = from.myVariableDatum[i];
|
||||
PsiVariable mappedVariable = variablesMapping.get(fromData.variable);
|
||||
if (mappedVariable == null) {
|
||||
myVariableDatum[i] = fromData;
|
||||
}
|
||||
else {
|
||||
myVariableDatum[i] = new VariableData(mappedVariable, fromData.type);
|
||||
myVariableDatum[i].name = fromData.name;
|
||||
myVariableDatum[i].originalName = fromData.originalName;
|
||||
myVariableDatum[i].passAsParameter = fromData.passAsParameter;
|
||||
}
|
||||
}
|
||||
for (int i = from.myVariableDatum.length; i < myVariableDatum.length; i++) {
|
||||
myVariableDatum[i] = myInputVariables.getInputVariables().get(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void applyDefaults(@NotNull String methodName, @PsiModifier.ModifierConstant @NotNull String visibility) {
|
||||
myMethodName = methodName;
|
||||
myVariableDatum = getInputVariables().getInputVariables().toArray(new VariableData[0]);
|
||||
myMethodVisibility = visibility;
|
||||
|
||||
myArtificialOutputVariable = PsiType.VOID.equals(myReturnType) ? getArtificialOutputVariable() : null;
|
||||
final PsiType returnType = myArtificialOutputVariable != null ? myArtificialOutputVariable.getType() : myReturnType;
|
||||
|
||||
if (returnType != null) {
|
||||
myReturnType = returnType;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doExtract() {
|
||||
super.chooseAnchor();
|
||||
super.doExtract();
|
||||
}
|
||||
|
||||
public void updateStaticModifier(List<Match> matches) {
|
||||
if (!isStatic() && isCanBeStatic()) {
|
||||
for (Match match : matches) {
|
||||
if (!isInSameFile(match) || !isInSameClass(match)) {
|
||||
PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.STATIC, true);
|
||||
myStatic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void putExtractedParameters(Map<PsiLocalVariable, ExtractedParameter> extractedParameters) {
|
||||
for (Map.Entry<PsiLocalVariable, ExtractedParameter> entry : extractedParameters.entrySet()) {
|
||||
myInputVariables.foldExtractedParameter(entry.getKey(), entry.getValue().myPattern.getUsage());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean prepare(boolean showErrorHint) {
|
||||
setShowErrorDialogs(false);
|
||||
try {
|
||||
if (super.prepare()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final String message = RefactoringBundle.getCannotRefactorMessage(
|
||||
RefactoringBundle.message("is.not.supported.in.the.current.context", myRefactoringName));
|
||||
LOG.info(message);
|
||||
if (showErrorHint) {
|
||||
CommonRefactoringUtil.showErrorHint(myProject, null, message, myRefactoringName, HelpID.EXTRACT_METHOD);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
catch (PrepareFailedException e) {
|
||||
LOG.info(e);
|
||||
if (showErrorHint) {
|
||||
CommonRefactoringUtil.showErrorHint(myProject, null, e.getMessage(), myRefactoringName, HelpID.EXTRACT_METHOD);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiElement processMatch(Match match) throws IncorrectOperationException {
|
||||
boolean inSameFile = isInSameFile(match);
|
||||
if (!inSameFile) {
|
||||
relaxMethodVisibility(match);
|
||||
}
|
||||
boolean inSameClass = isInSameClass(match);
|
||||
|
||||
PsiElement element = super.processMatch(match);
|
||||
|
||||
if (!inSameFile || !inSameClass) {
|
||||
PsiMethodCallExpression callExpression = getMatchMethodCallExpression(element);
|
||||
if (callExpression != null) {
|
||||
return updateCallQualifier(callExpression);
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private PsiElement updateCallQualifier(PsiMethodCallExpression callExpression) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(myProject);
|
||||
PsiClass psiClass = myExtractedMethod.getContainingClass();
|
||||
LOG.assertTrue(psiClass != null, "myExtractedMethod.getContainingClass");
|
||||
|
||||
PsiReferenceExpression newQualifier = factory.createReferenceExpression(psiClass);
|
||||
callExpression.getMethodExpression().setQualifierExpression(newQualifier);
|
||||
return JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(callExpression);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public DuplicatesFinder createDuplicatesFinder() {
|
||||
ReturnValue returnValue = myOutputVariables.length == 1 ? new VariableReturnValue(myOutputVariables[0]) : null;
|
||||
|
||||
return new DuplicatesFinder(myElements, myInputVariables, returnValue, Collections.emptyList(), true);
|
||||
}
|
||||
|
||||
private void relaxMethodVisibility(Match match) {
|
||||
if (isInSamePackage(match)) {
|
||||
PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.PRIVATE, false);
|
||||
}
|
||||
else {
|
||||
PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.PUBLIC, true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInSameFile(Match match) {
|
||||
return myExtractedMethod.getContainingFile() == match.getMatchStart().getContainingFile();
|
||||
}
|
||||
|
||||
private boolean isInSamePackage(Match match) {
|
||||
PsiFile psiFile = myExtractedMethod.getContainingFile();
|
||||
PsiFile matchFile = match.getMatchStart().getContainingFile();
|
||||
return psiFile instanceof PsiJavaFile &&
|
||||
matchFile instanceof PsiJavaFile &&
|
||||
Objects.equals(((PsiJavaFile)psiFile).getPackageName(), ((PsiJavaFile)matchFile).getPackageName());
|
||||
}
|
||||
|
||||
private boolean isInSameClass(Match match) {
|
||||
PsiClass matchClass = PsiTreeUtil.getParentOfType(match.getMatchStart(), PsiClass.class);
|
||||
PsiClass psiClass = PsiTreeUtil.getParentOfType(myExtractedMethod, PsiClass.class);
|
||||
return psiClass != null && matchClass != null &&
|
||||
PsiTreeUtil.isAncestor(psiClass, matchClass, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,381 @@
|
||||
/*
|
||||
* 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.refactoring.extractMethod;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.SuggestedNameInfo;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.introduceParameter.IntroduceParameterHandler;
|
||||
import com.intellij.refactoring.util.VariableData;
|
||||
import com.intellij.refactoring.util.duplicates.DuplicatesFinder;
|
||||
import com.intellij.refactoring.util.duplicates.ExtractedParameter;
|
||||
import com.intellij.refactoring.util.duplicates.Match;
|
||||
import com.intellij.refactoring.util.duplicates.VariableReturnValue;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.THashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.refactoring.extractMethod.ExtractMethodHandler.REFACTORING_NAME;
|
||||
|
||||
/**
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class ParametrizedDuplicates {
|
||||
private static final Logger LOG = Logger.getInstance(ParametrizedDuplicates.class);
|
||||
|
||||
private final PsiElement[] myElements;
|
||||
private List<Match> myMatches;
|
||||
private List<Occurrences> myOccurrencesList;
|
||||
private PsiMethod myParametrizedMethod;
|
||||
private PsiMethodCallExpression myParametrizedCall;
|
||||
private VariableData[] myVariableData;
|
||||
|
||||
public ParametrizedDuplicates(PsiElement[] pattern) {
|
||||
if (pattern[0] instanceof PsiStatement) {
|
||||
Project project = pattern[0].getProject();
|
||||
PsiElement[] copy = IntroduceParameterHandler.getElementsInCopy(project, pattern[0].getContainingFile(), pattern);
|
||||
myElements = wrapWithCodeBlock(copy);
|
||||
}
|
||||
else {
|
||||
myElements = PsiElement.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ParametrizedDuplicates findDuplicates(@NotNull ExtractMethodProcessor originalProcessor) {
|
||||
PsiElement[] pattern = originalProcessor.myElements;
|
||||
if (pattern.length == 0) {
|
||||
return null;
|
||||
}
|
||||
List<Match> matches = findOriginalDuplicates(originalProcessor);
|
||||
|
||||
ParametrizedDuplicates duplicates = new ParametrizedDuplicates(pattern);
|
||||
if (!duplicates.initMatches(matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!duplicates.extract(originalProcessor)) {
|
||||
return null;
|
||||
}
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<Match> findOriginalDuplicates(@NotNull ExtractMethodProcessor processor) {
|
||||
PsiElement[] elements = getFilteredElements(processor.myElements);
|
||||
|
||||
DuplicatesFinder finder = new DuplicatesFinder(elements, processor.myInputVariables.copy(),
|
||||
processor.myOutputVariable != null
|
||||
? new VariableReturnValue(processor.myOutputVariable) : null,
|
||||
Arrays.asList(processor.myOutputVariables), true) {
|
||||
@Override
|
||||
protected boolean isSelf(@NotNull PsiElement candidate) {
|
||||
for (PsiElement element : elements) {
|
||||
if (PsiTreeUtil.isAncestor(element, candidate, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
return finder.findDuplicates(processor.myTargetClass);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiMethod replaceMethod(@NotNull PsiMethod originalMethod) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalMethod.getProject());
|
||||
String text = myParametrizedMethod.getText();
|
||||
PsiMethod method = factory.createMethodFromText(text, originalMethod.getParent());
|
||||
return (PsiMethod)originalMethod.replace(method);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiMethodCallExpression replaceCall(@NotNull PsiMethodCallExpression originalCall) {
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalCall.getProject());
|
||||
String text = myParametrizedCall.getText();
|
||||
PsiMethodCallExpression call = (PsiMethodCallExpression)factory.createExpressionFromText(text, originalCall.getParent());
|
||||
return (PsiMethodCallExpression)originalCall.replace(call);
|
||||
}
|
||||
|
||||
private boolean initMatches(@NotNull List<Match> matches) {
|
||||
myOccurrencesList = new ArrayList<>();
|
||||
Map<PsiExpression, Occurrences> occurrencesMap = new THashMap<>();
|
||||
Set<Match> badMatches = new THashSet<>();
|
||||
matches:
|
||||
for (Match match : matches) {
|
||||
List<Occurrences> matchOccurrences = new ArrayList<>();
|
||||
List<ExtractedParameter> parameters = match.getExtractedParameters();
|
||||
for (ExtractedParameter parameter : parameters) {
|
||||
Occurrences occurrences = occurrencesMap.get(parameter.myPattern.getUsage());
|
||||
if (occurrences != null && !occurrences.isEquivalent(parameter) ||
|
||||
occurrences == null && Occurrences.isPresent(occurrencesMap, parameter)) {
|
||||
badMatches.add(match);
|
||||
continue matches;
|
||||
}
|
||||
if (occurrences == null) {
|
||||
matchOccurrences.add(occurrences = new Occurrences(parameter));
|
||||
}
|
||||
occurrences.add(parameter);
|
||||
}
|
||||
for (Occurrences occurrences : matchOccurrences) {
|
||||
myOccurrencesList.add(occurrences);
|
||||
for (PsiExpression expression : occurrences.myPatterns) {
|
||||
occurrencesMap.put(expression, occurrences);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!badMatches.isEmpty()) {
|
||||
matches = new ArrayList<>(matches);
|
||||
matches.removeAll(badMatches);
|
||||
}
|
||||
myMatches = matches;
|
||||
return !myMatches.isEmpty() && !myOccurrencesList.isEmpty();
|
||||
}
|
||||
|
||||
private boolean extract(@NotNull ExtractMethodProcessor originalProcessor) {
|
||||
Map<PsiExpression, PsiExpression> expressionsMapping = new THashMap<>();
|
||||
Map<PsiVariable, PsiVariable> variablesMapping = new THashMap<>();
|
||||
collectCopyMapping(originalProcessor.myElements, myElements, myOccurrencesList, expressionsMapping, variablesMapping);
|
||||
|
||||
Map<PsiLocalVariable, Occurrences> parameterDeclarations = createParameterDeclarations(originalProcessor, expressionsMapping);
|
||||
putMatchParameters(parameterDeclarations);
|
||||
|
||||
JavaDuplicatesExtractMethodProcessor parametrizedProcessor = new JavaDuplicatesExtractMethodProcessor(myElements, REFACTORING_NAME) {
|
||||
@Override
|
||||
protected boolean isFoldingApplicable() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (!parametrizedProcessor.prepare(false)) {
|
||||
return false;
|
||||
}
|
||||
parametrizedProcessor.applyFrom(originalProcessor, variablesMapping);
|
||||
parametrizedProcessor.doExtract();
|
||||
parametrizedProcessor.setDataFromInputVariables();
|
||||
myParametrizedMethod = parametrizedProcessor.getExtractedMethod();
|
||||
myParametrizedCall = parametrizedProcessor.getMethodCall();
|
||||
myVariableData = parametrizedProcessor.myVariableDatum;
|
||||
replaceArguments(parameterDeclarations, myParametrizedCall);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void replaceArguments(Map<PsiLocalVariable, Occurrences> parameterDeclarations, PsiMethodCallExpression parametrizedCall) {
|
||||
PsiExpression[] arguments = parametrizedCall.getArgumentList().getExpressions();
|
||||
for (PsiExpression argument : arguments) {
|
||||
if (argument instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReferenceExpression)argument).resolve();
|
||||
if (resolved instanceof PsiLocalVariable && parameterDeclarations.containsKey(resolved)) {
|
||||
PsiExpression initializer = ((PsiLocalVariable)resolved).getInitializer();
|
||||
if (initializer != null) {
|
||||
argument.replace(initializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void putMatchParameters(Map<PsiLocalVariable, Occurrences> parameterDeclarations) {
|
||||
Map<PsiExpression, PsiLocalVariable> patternUsageToParameter = new THashMap<>();
|
||||
for (Map.Entry<PsiLocalVariable, Occurrences> entry : parameterDeclarations.entrySet()) {
|
||||
PsiExpression usage = entry.getValue().myParameters.get(0).myPattern.getUsage();
|
||||
patternUsageToParameter.put(usage, entry.getKey());
|
||||
}
|
||||
|
||||
for (Match match : myMatches) {
|
||||
List<ExtractedParameter> matchedParameters = match.getExtractedParameters();
|
||||
for (ExtractedParameter matchedParameter : matchedParameters) {
|
||||
PsiLocalVariable localVariable = patternUsageToParameter.get(matchedParameter.myPattern.getUsage());
|
||||
LOG.assertTrue(localVariable != null, "match local variable");
|
||||
boolean ok = match.putParameter(Pair.createNonNull(localVariable, matchedParameter.myType),
|
||||
matchedParameter.myCandidate.getUsage());
|
||||
LOG.assertTrue(ok, "put match parameter");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PsiMethod getParametrizedMethod() {
|
||||
return myParametrizedMethod;
|
||||
}
|
||||
|
||||
public PsiMethodCallExpression getParametrizedCall() {
|
||||
return myParametrizedCall;
|
||||
}
|
||||
|
||||
public VariableData[] getVariableData() {
|
||||
return myVariableData;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return myMatches != null ? myMatches.size() : 0;
|
||||
}
|
||||
|
||||
public List<Match> getDuplicates() {
|
||||
return myMatches;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement[] wrapWithCodeBlock(@NotNull PsiElement[] elements) {
|
||||
PsiElement parent = elements[0].getParent();
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(elements[0].getProject());
|
||||
PsiBlockStatement statement = (PsiBlockStatement)factory.createStatementFromText("{}", parent);
|
||||
statement.getCodeBlock().addRange(elements[0], elements[elements.length - 1]);
|
||||
statement = (PsiBlockStatement)parent.addBefore(statement, elements[0]);
|
||||
parent.deleteChildRange(elements[0], elements[elements.length - 1]);
|
||||
PsiCodeBlock codeBlock = statement.getCodeBlock();
|
||||
PsiElement[] elementsInCopy = codeBlock.getChildren();
|
||||
LOG.assertTrue(elementsInCopy.length >= elements.length + 2, "wrapper block length is too small");
|
||||
return Arrays.copyOfRange(elementsInCopy, 1, elementsInCopy.length - 1);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<PsiLocalVariable, Occurrences> createParameterDeclarations(@NotNull ExtractMethodProcessor originalProcessor,
|
||||
@NotNull Map<PsiExpression, PsiExpression> expressionsMapping) {
|
||||
|
||||
Project project = myElements[0].getProject();
|
||||
Map<PsiLocalVariable, Occurrences> parameterDeclarations = new THashMap<>();
|
||||
UniqueNameGenerator generator = originalProcessor.getParameterNameGenerator(myElements[0]);
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiElement parent = myElements[0].getParent();
|
||||
for (Occurrences occurrences : myOccurrencesList) {
|
||||
ExtractedParameter parameter = occurrences.myParameters.get(0);
|
||||
PsiExpression patternUsage = parameter.myPattern.getUsage();
|
||||
String usageText = patternUsage.getText();
|
||||
PsiExpression exprInCopy = factory.createExpressionFromText(usageText, parent);
|
||||
final SuggestedNameInfo info =
|
||||
JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, null, exprInCopy, null);
|
||||
final String parameterName = generator.generateUniqueName(info.names.length > 0 ? info.names[0] : "p");
|
||||
|
||||
String declarationText = parameter.myType.getCanonicalText() + " " + parameterName + " = " + usageText + ";";
|
||||
PsiDeclarationStatement paramDeclaration = (PsiDeclarationStatement)factory.createStatementFromText(declarationText, parent);
|
||||
paramDeclaration = (PsiDeclarationStatement)parent.addBefore(paramDeclaration, myElements[0]);
|
||||
PsiLocalVariable localVariable = (PsiLocalVariable)paramDeclaration.getDeclaredElements()[0];
|
||||
parameterDeclarations.put(localVariable, occurrences);
|
||||
|
||||
for (PsiExpression expression : parameter.myUsages.keySet()) {
|
||||
PsiExpression mapped = expressionsMapping.get(expression);
|
||||
if (mapped != null) {
|
||||
PsiExpression replacement = factory.createExpressionFromText(parameterName, expression);
|
||||
mapped.replace(replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parameterDeclarations;
|
||||
}
|
||||
|
||||
private static void collectCopyMapping(@NotNull PsiElement[] pattern,
|
||||
@NotNull PsiElement[] copy,
|
||||
@NotNull List<Occurrences> patternUsages,
|
||||
@NotNull Map<PsiExpression, PsiExpression> expressions,
|
||||
@NotNull Map<PsiVariable, PsiVariable> variables) {
|
||||
Set<PsiExpression> patternExpressions = new THashSet<>();
|
||||
for (Occurrences occurrences : patternUsages) {
|
||||
patternExpressions.addAll(occurrences.myPatterns);
|
||||
}
|
||||
|
||||
collectCopyMapping(pattern, copy, patternExpressions, expressions, variables);
|
||||
}
|
||||
|
||||
private static void collectCopyMapping(@NotNull PsiElement[] pattern,
|
||||
@NotNull PsiElement[] copy,
|
||||
@NotNull Set<PsiExpression> replaceablePatterns,
|
||||
@NotNull Map<PsiExpression, PsiExpression> expressions,
|
||||
@NotNull Map<PsiVariable, PsiVariable> variables) {
|
||||
pattern = getFilteredElements(pattern);
|
||||
copy = getFilteredElements(copy);
|
||||
LOG.assertTrue(copy.length == pattern.length, "copy length");
|
||||
for (int i = 0; i < pattern.length; i++) {
|
||||
collectCopyMapping(pattern[i], copy[i], replaceablePatterns, expressions, variables);
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectCopyMapping(PsiElement pattern,
|
||||
PsiElement copy,
|
||||
Set<PsiExpression> replaceablePatterns,
|
||||
Map<PsiExpression, PsiExpression> expressions,
|
||||
Map<PsiVariable, PsiVariable> variables) {
|
||||
if (pattern == copy) return;
|
||||
LOG.assertTrue(pattern != null && copy != null, "null in collectVariablesMapping");
|
||||
if (pattern instanceof PsiExpression && copy instanceof PsiExpression && replaceablePatterns.contains(pattern)) {
|
||||
expressions.put((PsiExpression)pattern, (PsiExpression)copy);
|
||||
}
|
||||
|
||||
if (pattern instanceof PsiReferenceExpression && copy instanceof PsiReferenceExpression) {
|
||||
PsiElement resolvedPattern = ((PsiReferenceExpression)pattern).resolve();
|
||||
PsiElement resolvedCopy = ((PsiReferenceExpression)copy).resolve();
|
||||
if (resolvedPattern != resolvedCopy && resolvedPattern instanceof PsiVariable && resolvedCopy instanceof PsiVariable) {
|
||||
variables.put((PsiVariable)resolvedPattern, (PsiVariable)resolvedCopy);
|
||||
}
|
||||
}
|
||||
else if (pattern instanceof PsiVariable && copy instanceof PsiVariable) {
|
||||
variables.put((PsiVariable)pattern, (PsiVariable)copy);
|
||||
}
|
||||
|
||||
collectCopyMapping(pattern.getChildren(), copy.getChildren(), replaceablePatterns, expressions, variables);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElement[] getFilteredElements(@NotNull PsiElement[] elements) {
|
||||
if (elements.length == 0) {
|
||||
return elements;
|
||||
}
|
||||
ArrayList<PsiElement> result = new ArrayList<>(elements.length);
|
||||
for (PsiElement e : elements) {
|
||||
if (e == null || e instanceof PsiWhiteSpace || e instanceof PsiComment || e instanceof PsiEmptyStatement) {
|
||||
continue;
|
||||
}
|
||||
if (e instanceof PsiParenthesizedExpression) {
|
||||
e = PsiUtil.skipParenthesizedExprDown((PsiParenthesizedExpression)e);
|
||||
}
|
||||
result.add(e);
|
||||
}
|
||||
return result.toArray(PsiElement.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
private static class Occurrences {
|
||||
@NotNull private final Set<PsiExpression> myPatterns;
|
||||
@NotNull private final List<ExtractedParameter> myParameters;
|
||||
|
||||
public Occurrences(ExtractedParameter parameter) {
|
||||
myPatterns = parameter.myUsages.keySet();
|
||||
myParameters = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void add(ExtractedParameter parameter) {
|
||||
myParameters.add(parameter);
|
||||
}
|
||||
|
||||
public boolean isEquivalent(ExtractedParameter parameter) {
|
||||
return myPatterns.equals(parameter.myUsages.keySet());
|
||||
}
|
||||
|
||||
public static boolean isPresent(Map<PsiExpression, Occurrences> usagesMap, @NotNull ExtractedParameter parameter) {
|
||||
return parameter.myUsages.keySet().stream().anyMatch(expression -> usagesMap.get(expression) != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,14 @@
|
||||
public class Test {
|
||||
int method() {
|
||||
try {
|
||||
System.out.println("Text");
|
||||
return 0;
|
||||
return newMethod("Text", 0);
|
||||
} finally {
|
||||
return newMethod();
|
||||
return newMethod("!!!", 1);
|
||||
}
|
||||
}
|
||||
|
||||
private int newMethod() {
|
||||
System.out.println("!!!");
|
||||
return 1;
|
||||
private int newMethod(String s, int i) {
|
||||
System.out.println(s);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ public class Test {
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
newMethod("foo".substring(x));
|
||||
newMethod(x, "foo");
|
||||
|
||||
newMethod("bar".substring(x));
|
||||
newMethod(x, "bar");
|
||||
}
|
||||
|
||||
private void newMethod(String substring) {
|
||||
System.out.println(substring);
|
||||
private void newMethod(int x, String foo) {
|
||||
System.out.println(foo.substring(x));
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ public class Test {
|
||||
int x = 0;
|
||||
|
||||
|
||||
newMethod(x, x + 1);
|
||||
newMethod(x, 1);
|
||||
|
||||
|
||||
newMethod(x, x + 2);
|
||||
newMethod(x, 2);
|
||||
}
|
||||
|
||||
private void newMethod(int p, int i) {
|
||||
System.out.println(p);
|
||||
System.out.println(i);
|
||||
System.out.println(p + i);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
class Test {
|
||||
|
||||
private BigDecimal getRevenue() {
|
||||
private String getRevenue() {
|
||||
<selection>final String query = createNamedQuery("revenues");
|
||||
String revenues = "";
|
||||
final String revenue;
|
||||
@@ -10,7 +10,7 @@ class Test {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public BigDecimal getExpense() {
|
||||
public String getExpense() {
|
||||
final String query = createNamedQuery("expenses");
|
||||
String expenses = "";
|
||||
final String expense;
|
||||
|
||||
@@ -2,7 +2,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class Test {
|
||||
|
||||
private BigDecimal getRevenue() {
|
||||
private String getRevenue() {
|
||||
final String revenue = newMethod("revenues");
|
||||
|
||||
return revenue;
|
||||
@@ -17,7 +17,7 @@ class Test {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public BigDecimal getExpense() {
|
||||
public String getExpense() {
|
||||
final String expense = newMethod("expenses");
|
||||
|
||||
return expense;
|
||||
|
||||
@@ -783,7 +783,7 @@ public class ExtractMethodTest extends LightCodeInsightTestCase {
|
||||
doDuplicatesTest();
|
||||
}
|
||||
|
||||
public void testSuggestChangeSignatureWithFolding() throws Exception {
|
||||
public void _testSuggestChangeSignatureWithFolding() throws Exception {
|
||||
doDuplicatesTest();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user