Java: Recognize multiple parametrized duplicates when extracting a method (IDEA-188243)

This commit is contained in:
Pavel Dolgov
2018-05-10 12:59:45 +03:00
parent 192532ab7d
commit 1f0ed3d0ea
27 changed files with 501 additions and 99 deletions
@@ -284,6 +284,10 @@ public class InputVariables {
return inputVariables;
}
@NotNull
public InputVariables copyWithoutFolding() {
return new InputVariables(myInitialParameters, myProject, myScope, false);
}
public void appendCallArguments(VariableData data, StringBuilder buffer) {
if (myFoldingAvailable) {
@@ -309,8 +313,9 @@ public class InputVariables {
}
public void annotateWithParameter(PsiJavaCodeReferenceElement reference) {
if (myInputVariables.isEmpty()) return;
final PsiElement element = reference.resolve();
for (VariableData data : myInputVariables) {
final PsiElement element = reference.resolve();
if (data.variable.equals(element)) {
PsiType type = data.variable.getType();
final PsiMethodCallExpression methodCallExpression = PsiTreeUtil.getParentOfType(reference, PsiMethodCallExpression.class);
@@ -353,7 +353,7 @@ public class ParametersFolder {
if (psiExpression != null) {
final PsiExpression expression = findEquivalent(psiExpression, element);
if (expression != null) {
expression.putUserData(DuplicatesFinder.PARAMETER, new DuplicatesFinder.Parameter(data.variable, expression.getType()));
expression.putUserData(DuplicatesFinder.PARAMETER, new DuplicatesFinder.Parameter(data.variable, expression.getType(), true));
return true;
}
}
@@ -48,7 +48,7 @@ public class DuplicatesFinder {
private final List<PsiElement> myPatternAsList;
private boolean myMultipleExitPoints;
@Nullable private final ReturnValue myReturnValue;
private final boolean myWithExtractedParameters;
private final MatchType myMatchType;
private final Set<PsiVariable> myEffectivelyLocal;
private ComplexityHolder myPatternComplexityHolder;
private ComplexityHolder myCandidateComplexityHolder;
@@ -57,7 +57,7 @@ public class DuplicatesFinder {
InputVariables parameters,
@Nullable ReturnValue returnValue,
@NotNull List<? extends PsiVariable> outputParameters,
boolean withExtractedParameters,
@NotNull MatchType matchType,
@Nullable Set<PsiVariable> effectivelyLocal) {
myReturnValue = returnValue;
LOG.assertTrue(pattern.length > 0);
@@ -65,7 +65,7 @@ public class DuplicatesFinder {
myPatternAsList = Arrays.asList(myPattern);
myParameters = parameters;
myOutputParameters = outputParameters;
myWithExtractedParameters = withExtractedParameters;
myMatchType = matchType;
myEffectivelyLocal = effectivelyLocal != null ? effectivelyLocal : Collections.emptySet();
final PsiElement codeFragment = ControlFlowUtil.findCodeFragment(pattern[0]);
@@ -101,7 +101,7 @@ public class DuplicatesFinder {
InputVariables parameters,
@Nullable ReturnValue returnValue,
@NotNull List<? extends PsiVariable> outputParameters) {
this(pattern, parameters, returnValue, outputParameters, false, null);
this(pattern, parameters, returnValue, outputParameters, MatchType.EXACT, null);
}
public DuplicatesFinder(final PsiElement[] pattern,
@@ -435,10 +435,16 @@ public class DuplicatesFinder {
@Nullable
private Boolean matchParameter(@NotNull PsiElement pattern, @NotNull PsiElement candidate, @NotNull Match match) {
final Parameter parameter = pattern.getUserData(PARAMETER);
if (parameter == null || myWithExtractedParameters && !parameter.isReferencedBy(pattern)) {
if (parameter == null || myMatchType == MatchType.EXACT && parameter.isFolded()) {
return null;
}
return match.putParameter(parameter, candidate);
if (!match.putParameter(parameter, candidate)) {
return false;
}
if (parameter.isFolded() && pattern instanceof PsiExpression && candidate instanceof PsiExpression) {
match.putFoldedExpressionMapping(parameter, (PsiExpression)pattern, (PsiExpression)candidate);
}
return true;
}
@Nullable
@@ -721,7 +727,7 @@ public class DuplicatesFinder {
private boolean matchExtractableExpression(@Nullable PsiElement pattern, @Nullable PsiElement candidate,
@NotNull List<PsiElement> candidates, @NotNull Match match) {
if (!myWithExtractedParameters || !(pattern instanceof PsiExpression) || !(candidate instanceof PsiExpression)) {
if (myMatchType == MatchType.EXACT || !(pattern instanceof PsiExpression) || !(candidate instanceof PsiExpression)) {
return false;
}
@@ -751,7 +757,7 @@ public class DuplicatesFinder {
}
private boolean matchExtractableVariable(@NotNull PsiElement pattern, @NotNull PsiElement candidate, @NotNull Match match) {
if (!myWithExtractedParameters || !(pattern instanceof PsiReferenceExpression) || !(candidate instanceof PsiReferenceExpression)) {
if (myMatchType == MatchType.EXACT || !(pattern instanceof PsiReferenceExpression) || !(candidate instanceof PsiReferenceExpression)) {
return false;
}
if (myPattern.length == 1 && myPattern[0] == pattern) {
@@ -931,10 +937,16 @@ public class DuplicatesFinder {
public static class Parameter {
private final PsiVariable myVariable;
private final PsiType myType;
private final boolean myFolded;
public Parameter(@Nullable PsiVariable variable, @Nullable PsiType type) {
this(variable, type, false);
}
public Parameter(@Nullable PsiVariable variable, @Nullable PsiType type, boolean folded) {
myVariable = variable;
myType = type;
myFolded = folded;
}
public PsiVariable getVariable() {
@@ -945,8 +957,29 @@ public class DuplicatesFinder {
return myType;
}
public boolean isReferencedBy(@Nullable PsiElement pattern) {
return myVariable != null && pattern instanceof PsiReferenceExpression && ((PsiReferenceExpression)pattern).resolve() == myVariable;
public boolean isFolded() {
return myFolded;
}
public String toString() {
return myVariable + ", " + myType + (myFolded ? ", folded" : "");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Parameter)) return false;
Parameter p = (Parameter)o;
return Objects.equals(myVariable, p.myVariable) &&
Objects.equals(myType, p.myType) &&
myFolded == p.myFolded;
}
@Override
public int hashCode() {
return Objects.hash(myVariable, myType, myFolded);
}
}
public enum MatchType {EXACT, PARAMETRIZED, FOLDED}
}
@@ -164,4 +164,13 @@ public class ExtractableExpressionPart {
public PsiExpression getUsage() {
return myUsage;
}
@NotNull
public static ExtractableExpressionPart fromUsage(@NotNull PsiExpression usage, @NotNull PsiType type) {
PsiType usageType;
//noinspection AssertWithSideEffects
assert (usageType = usage.getType()) == null || type.isAssignableFrom(usageType)
: "expected " + type.getCanonicalText() + ", got " + usageType.getCanonicalText();
return new ExtractableExpressionPart(usage, null, null, type);
}
}
@@ -83,7 +83,7 @@ public class ExtractedParameter {
return type.getCanonicalText();
}
private void addUsages(ExtractableExpressionPart patternPart) {
public void addUsages(ExtractableExpressionPart patternPart) {
myPatternUsages.add(patternPart.getUsage());
}
@@ -18,9 +18,7 @@ package com.intellij.refactoring.util.duplicates;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.*;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
@@ -44,13 +42,14 @@ public final class Match {
private final PsiElement myMatchStart;
private final PsiElement myMatchEnd;
private final Map<PsiVariable, List<PsiElement>> myParameterValues = new HashMap<>();
private final Map<PsiVariable, ArrayList<PsiElement>> myParameterOccurrences = new HashMap<>();
private final Map<PsiVariable, List<PsiElement>> myParameterOccurrences = new HashMap<>();
private final Map<PsiElement, PsiElement> myDeclarationCorrespondence = new HashMap<>();
private ReturnValue myReturnValue;
private Ref<PsiExpression> myInstanceExpression;
final Map<PsiVariable, PsiType> myChangedParams = new HashMap<>();
private final boolean myIgnoreParameterTypes;
private final List<ExtractedParameter> myExtractedParameters = new ArrayList<>();
private final Map<DuplicatesFinder.Parameter, List<Pair.NonNull<PsiExpression, PsiExpression>>> myFoldedExpressionMappings = new HashMap<>();
Match(PsiElement start, PsiElement end, boolean ignoreParameterTypes) {
LOG.assertTrue(start.getParent() == end.getParent());
@@ -427,4 +426,15 @@ public final class Match {
public List<ExtractedParameter> getExtractedParameters() {
return myExtractedParameters;
}
public void putFoldedExpressionMapping(@NotNull DuplicatesFinder.Parameter parameter,
@NotNull PsiExpression pattern,
@NotNull PsiExpression candidate) {
myFoldedExpressionMappings.computeIfAbsent(parameter, unused -> new ArrayList<>()).add(Pair.createNonNull(pattern, candidate));
}
@Nullable
public List<Pair.NonNull<PsiExpression, PsiExpression>> getFoldedExpressionMappings(@NotNull DuplicatesFinder.Parameter parameter) {
return myFoldedExpressionMappings.get(parameter);
}
}
@@ -114,7 +114,7 @@ public class ExtractMethodProcessor implements MatchProvider {
protected boolean myCanBeStatic;
protected boolean myCanBeChainedConstructor;
protected boolean myIsChainedConstructor;
private List<Match> myDuplicates;
protected List<Match> myDuplicates;
private ParametrizedDuplicates myParametrizedDuplicates;
@PsiModifier.ModifierConstant protected String myMethodVisibility = PsiModifier.PRIVATE;
protected boolean myGenerateConditionalExit;
@@ -890,35 +890,13 @@ public class ExtractMethodProcessor implements MatchProvider {
chooseAnchor();
}
@Nullable
private DuplicatesFinder initDuplicates() {
PsiElement[] elements = StreamEx.of(myElements)
.filter(element -> !(element instanceof PsiWhiteSpace || element instanceof PsiComment))
.toArray(PsiElement[]::new);
if (myExpression != null) {
DuplicatesFinder finder = new DuplicatesFinder(elements, myInputVariables.copy(), Collections.emptyList());
myDuplicates = finder.findDuplicates(myTargetClass);
myParametrizedDuplicates = ParametrizedDuplicates.findDuplicates(this);
return finder;
}
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<>();
}
return null;
protected void initDuplicates() {
myParametrizedDuplicates = ParametrizedDuplicates.findDuplicates(this);
myDuplicates = new ArrayList<>();
}
private int estimateDuplicatesCount() {
PsiElement[] elements = StreamEx.of(myElements)
.filter(element -> !(element instanceof PsiWhiteSpace || element instanceof PsiComment))
.toArray(PsiElement[]::new);
PsiElement[] elements = getFilteredElements();
ReturnValue value;
List<PsiVariable> parameters;
@@ -944,6 +922,13 @@ public class ExtractMethodProcessor implements MatchProvider {
return 0;
}
@NotNull
private PsiElement[] getFilteredElements() {
return StreamEx.of(myElements)
.filter(e -> !(e instanceof PsiWhiteSpace || e instanceof PsiComment || e instanceof PsiEmptyStatement))
.toArray(PsiElement.EMPTY_ARRAY);
}
public void doExtract() throws IncorrectOperationException {
PsiMethod newMethod = generateEmptyMethod();
@@ -1369,7 +1354,7 @@ public class ExtractMethodProcessor implements MatchProvider {
methodCallExpression.getArgumentList().add(myElementFactory.createExpressionFromText(data.variable.getName(), methodCallExpression));
}
}
List<String> reusedVariables = findReusedVariables(match, myOutputVariable);
List<String> reusedVariables = findReusedVariables(match, myInputVariables, myOutputVariable);
PsiElement replacedMatch = match.replace(myExtractedMethod, methodCallExpression, myOutputVariable);
PsiElement appendLocation = addNotNullConditionalCheck(match, replacedMatch);
@@ -1378,7 +1363,9 @@ public class ExtractMethodProcessor implements MatchProvider {
}
@NotNull
private static List<String> findReusedVariables(@NotNull Match match, @Nullable PsiVariable outputVariable) {
private static List<String> findReusedVariables(@NotNull Match match,
@NotNull InputVariables inputVariables,
@Nullable PsiVariable outputVariable) {
Set<PsiLocalVariable> ignoreVariables = Collections.emptySet();
ReturnValue returnValue = match.getOutputVariableValue(outputVariable);
if (returnValue instanceof VariableReturnValue) {
@@ -1388,7 +1375,7 @@ public class ExtractMethodProcessor implements MatchProvider {
}
}
List<ReusedLocalVariable> reusedLocalVariables =
ReusedLocalVariablesFinder.findReusedLocalVariables(match.getMatchStart(), match.getMatchEnd(), ignoreVariables);
ReusedLocalVariablesFinder.findReusedLocalVariables(match.getMatchStart(), match.getMatchEnd(), ignoreVariables, inputVariables);
if (!reusedLocalVariables.isEmpty()) {
List<String> result = new ArrayList<>();
for (ReusedLocalVariable variable : reusedLocalVariables) {
@@ -2159,22 +2146,29 @@ public class ExtractMethodProcessor implements MatchProvider {
public boolean initParametrizedDuplicates(boolean showDialog) {
if (myExtractedMethod != null && myParametrizedDuplicates != null) {
if (!showDialog ||
myDuplicates = myParametrizedDuplicates.getDuplicates();
if (myDuplicates.isEmpty()) {
return false;
}
boolean isSignatureUnchanged = myDuplicates.stream()
.map(Match::getExtractedParameters)
.allMatch(List::isEmpty);
boolean isFoldable = myInputVariables.isFoldable();
if (!showDialog || isSignatureUnchanged || isFoldable ||
ApplicationManager.getApplication().isUnitTestMode() ||
new SignatureSuggesterPreviewDialog(myExtractedMethod, myParametrizedDuplicates.getParametrizedMethod(),
myMethodCall, myParametrizedDuplicates.getParametrizedCall(),
myParametrizedDuplicates.getSize()).showAndGet()) {
myDuplicates = myParametrizedDuplicates.getDuplicates();
Runnable replaceMethod = () -> {
myExtractedMethod = myParametrizedDuplicates.replaceMethod(myExtractedMethod);
myMethodCall = myParametrizedDuplicates.replaceCall(myMethodCall);
};
if (myExtractedMethod.isPhysical()) {
WriteCommandAction.runWriteCommandAction(myProject, replaceMethod);
WriteCommandAction.runWriteCommandAction(myProject, () -> {
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
replaceParametrizedMethod();
});
}
else {
replaceMethod.run();
replaceParametrizedMethod();
}
myVariableDatum = myParametrizedDuplicates.getVariableDatum();
return true;
@@ -2183,8 +2177,15 @@ public class ExtractMethodProcessor implements MatchProvider {
return false;
}
private void replaceParametrizedMethod() {
LOG.assertTrue(myParametrizedDuplicates != null);
myExtractedMethod = myParametrizedDuplicates.replaceMethod(myExtractedMethod);
myMethodCall = myParametrizedDuplicates.replaceCall(myMethodCall);
}
public boolean hasDuplicates(Set<VirtualFile> files) {
final DuplicatesFinder finder = initDuplicates();
initDuplicates();
final DuplicatesFinder finder = getExactDuplicatesFinder();
final Boolean hasDuplicates = hasDuplicates();
if (hasDuplicates == null || hasDuplicates) return true;
@@ -2197,6 +2198,21 @@ public class ExtractMethodProcessor implements MatchProvider {
return false;
}
@Nullable
protected DuplicatesFinder getExactDuplicatesFinder() {
DuplicatesFinder finder = null;
PsiElement[] elements = getFilteredElements();
if (myExpression != null) {
finder = new DuplicatesFinder(elements, myInputVariables.copy(), Collections.emptyList());
}
else if (elements.length != 0) {
finder = new DuplicatesFinder(elements, myInputVariables.copy(),
myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null,
Arrays.asList(myOutputVariables));
}
return finder;
}
@Nullable
public String getConfirmDuplicatePrompt(Match match) {
final boolean needToBeStatic = RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass());
@@ -203,7 +203,7 @@ public class JavaDuplicatesExtractMethodProcessor extends ExtractMethodProcessor
ReturnValue returnValue = myOutputVariables.length == 1 ? new VariableReturnValue(myOutputVariables[0]) : null;
Set<PsiVariable> effectivelyLocal = getEffectivelyLocalVariables();
return new DuplicatesFinder(myElements, myInputVariables, returnValue, Collections.emptyList(), true, effectivelyLocal);
return new DuplicatesFinder(myElements, myInputVariables, returnValue, Collections.emptyList(), DuplicatesFinder.MatchType.PARAMETRIZED, effectivelyLocal);
}
private void relaxMethodVisibility(Match match) {
@@ -17,11 +17,11 @@ 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.search.LocalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
@@ -59,7 +59,7 @@ public class ParametrizedDuplicates {
LOG.assertTrue(pattern.length != 0, "pattern length");
if (pattern[0] instanceof PsiStatement) {
PsiElement[] copy = copyElements(pattern);
myElements = wrapWithCodeBlock(copy);
myElements = wrapWithCodeBlock(copy, originalProcessor.getInputVariables());
}
else if (pattern[0] instanceof PsiExpression) {
PsiElement[] copy = copyElements(pattern);
@@ -82,44 +82,119 @@ public class ParametrizedDuplicates {
if (pattern.length == 0) {
return null;
}
List<Match> matches = findOriginalDuplicates(originalProcessor);
DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.PARAMETRIZED);
List<Match> matches = finder.findDuplicates(originalProcessor.myTargetClass);
matches = filterNestedSubexpressions(matches);
if (matches.isEmpty()) {
return null;
}
Map<PsiExpression, String> predefinedNames = foldParameters(originalProcessor, matches);
ParametrizedDuplicates duplicates = new ParametrizedDuplicates(pattern, originalProcessor);
if (!duplicates.initMatches(matches)) {
return null;
}
if (!duplicates.extract(originalProcessor)) {
if (!duplicates.extract(originalProcessor, predefinedNames)) {
return null;
}
return duplicates;
}
@NotNull
private static List<Match> findOriginalDuplicates(@NotNull ExtractMethodProcessor processor) {
private static Map<PsiExpression, String> foldParameters(ExtractMethodProcessor originalProcessor, List<Match> matches) {
if (matches.isEmpty() || !originalProcessor.getInputVariables().isFoldable()) {
return Collections.emptyMap();
}
// As folded parameters don't work along with extracted parameters we need to apply the finder again to actually fold the parameters
DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.FOLDED);
Map<Match, Match> foldedMatches = new HashMap<>();
Map<DuplicatesFinder.Parameter, VariableData> parametersToFold = new LinkedHashMap<>();
for (VariableData data : originalProcessor.getInputVariables().getInputVariables()) {
parametersToFold.put(new DuplicatesFinder.Parameter(data.variable, data.type, true), data);
}
for (Match match : matches) {
Match foldedMatch = finder.isDuplicate(match.getMatchStart(), false);
LOG.assertTrue(foldedMatch != null, "folded match should exist");
LOG.assertTrue(match.getMatchStart() == foldedMatch.getMatchStart() &&
match.getMatchEnd() == foldedMatch.getMatchEnd(), "folded match range should be the same");
foldedMatches.put(match, foldedMatch);
parametersToFold.keySet().removeIf(parameter -> !canFoldParameter(match, foldedMatch, parameter));
}
if (parametersToFold.isEmpty()) {
return Collections.emptyMap();
}
Map<PsiExpression, String> predefinedNames = new HashMap<>();
for (Match match : matches) {
Match foldedMatch = foldedMatches.get(match);
LOG.assertTrue(foldedMatch != null, "folded match");
for (Map.Entry<DuplicatesFinder.Parameter, VariableData> entry : parametersToFold.entrySet()) {
DuplicatesFinder.Parameter parameter = entry.getKey();
VariableData variableData = entry.getValue();
List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
LOG.assertTrue(!ContainerUtil.isEmpty(expressionMappings), "foldedExpressionMappings can't be empty");
PsiType type = parameter.getType();
ExtractedParameter extractedParameter = null;
for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
PsiExpression patternExpression = expressionMapping.getFirst();
ExtractableExpressionPart patternPart = ExtractableExpressionPart.fromUsage(patternExpression, type);
if (extractedParameter == null) {
PsiExpression candidateExpression = expressionMapping.getSecond();
ExtractableExpressionPart candidatePart = ExtractableExpressionPart.fromUsage(candidateExpression, type);
extractedParameter = new ExtractedParameter(patternPart, candidatePart, type);
}
else {
extractedParameter.addUsages(patternPart);
}
predefinedNames.put(patternExpression, variableData.name);
}
LOG.assertTrue(extractedParameter != null, "extractedParameter can't be null");
match.getExtractedParameters().add(extractedParameter);
}
}
return predefinedNames;
}
private static boolean canFoldParameter(Match match, Match foldedMatch, DuplicatesFinder.Parameter parameter) {
List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
if (ContainerUtil.isEmpty(expressionMappings)) {
return false;
}
// Extracted parameters and folded parameters shouldn't overlap
for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
PsiExpression patternExpression = expressionMapping.getFirst();
for (ExtractedParameter extractedParameter : match.getExtractedParameters()) {
for (PsiExpression extractedUsage : extractedParameter.myPatternUsages) {
if (PsiTreeUtil.isAncestor(patternExpression, extractedUsage, false) ||
PsiTreeUtil.isAncestor(extractedUsage, patternExpression, false)) {
return false;
}
}
}
}
return true;
}
@NotNull
private static DuplicatesFinder createDuplicatesFinder(@NotNull ExtractMethodProcessor processor,
@NotNull DuplicatesFinder.MatchType matchType) {
PsiElement[] elements = getFilteredElements(processor.myElements);
Set<PsiVariable> effectivelyLocal = processor.getEffectivelyLocalVariables();
List<PsiVariable> variables = ContainerUtil.map(processor.myInputVariables.getInputVariables(), iv -> iv.variable);
InputVariables inputVariables = new InputVariables(variables, processor.myProject, new LocalSearchScope(processor.myElements), false);
DuplicatesFinder finder = new DuplicatesFinder(elements, inputVariables,
processor.myOutputVariable != null
? new VariableReturnValue(processor.myOutputVariable) : null,
Collections.emptyList(), true, effectivelyLocal) {
@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);
InputVariables inputVariables = matchType == DuplicatesFinder.MatchType.PARAMETRIZED
? processor.myInputVariables.copyWithoutFolding() : processor.myInputVariables;
ReturnValue returnValue = processor.myOutputVariable != null ? new VariableReturnValue(processor.myOutputVariable) : null;
return new DuplicatesFinder(elements, inputVariables, returnValue,
Collections.emptyList(), matchType, effectivelyLocal);
}
@NotNull
@@ -142,7 +217,6 @@ public class ParametrizedDuplicates {
if (myElements.length == 0) {
return false;
}
matches = filterNestedSubexpressions(matches);
myUsagesList = new ArrayList<>();
Map<PsiExpression, ClusterOfUsages> usagesMap = new THashMap<>();
@@ -234,12 +308,13 @@ public class ParametrizedDuplicates {
return result;
}
private boolean extract(@NotNull ExtractMethodProcessor originalProcessor) {
private boolean extract(@NotNull ExtractMethodProcessor originalProcessor, @NotNull Map<PsiExpression, String> predefinedNames) {
Map<PsiExpression, PsiExpression> expressionsMapping = new THashMap<>();
Map<PsiVariable, PsiVariable> variablesMapping = new THashMap<>();
collectCopyMapping(originalProcessor.myElements, myElements, myUsagesList, expressionsMapping, variablesMapping);
Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations = createParameterDeclarations(originalProcessor, expressionsMapping);
Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations =
createParameterDeclarations(originalProcessor, expressionsMapping, predefinedNames);
putMatchParameters(parameterDeclarations);
JavaDuplicatesExtractMethodProcessor parametrizedProcessor = new JavaDuplicatesExtractMethodProcessor(myElements, REFACTORING_NAME);
@@ -261,8 +336,8 @@ public class ParametrizedDuplicates {
@NotNull Map<PsiVariable, PsiVariable> variablesMapping) {
Map<PsiVariable, PsiVariable> reverseMapping = ContainerUtil.reverseMap(variablesMapping);
return StreamEx.of(variableDatum)
.map(data -> data.substitute(reverseMapping.get(data.variable)))
.toArray(VariableData[]::new);
.map(data -> data.substitute(reverseMapping.get(data.variable)))
.toArray(VariableData[]::new);
}
private static void replaceArguments(@NotNull Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations,
@@ -321,11 +396,11 @@ public class ParametrizedDuplicates {
}
@NotNull
private static PsiElement[] wrapWithCodeBlock(@NotNull PsiElement[] elements) {
private static PsiElement[] wrapWithCodeBlock(@NotNull PsiElement[] elements, @NotNull InputVariables inputVariables) {
PsiElement fragmentStart = elements[0];
PsiElement fragmentEnd = elements[elements.length - 1];
List<ReusedLocalVariable> reusedLocalVariables =
ReusedLocalVariablesFinder.findReusedLocalVariables(fragmentStart, fragmentEnd, Collections.emptySet());
ReusedLocalVariablesFinder.findReusedLocalVariables(fragmentStart, fragmentEnd, Collections.emptySet(), inputVariables);
PsiElement parent = fragmentStart.getParent();
PsiElementFactory factory = JavaPsiFacade.getElementFactory(fragmentStart.getProject());
@@ -424,7 +499,8 @@ public class ParametrizedDuplicates {
@NotNull
private Map<PsiLocalVariable, ClusterOfUsages> createParameterDeclarations(@NotNull ExtractMethodProcessor originalProcessor,
@NotNull Map<PsiExpression, PsiExpression> expressionsMapping) {
@NotNull Map<PsiExpression, PsiExpression> expressionsMapping,
@NotNull Map<PsiExpression, String> predefinedNames) {
Project project = myElements[0].getProject();
Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations = new THashMap<>();
@@ -441,8 +517,9 @@ public class ParametrizedDuplicates {
PsiExpression patternUsage = parameter.myPattern.getUsage();
String initializerText = patternUsage.getText();
PsiExpression initializer = factory.createExpressionFromText(initializerText, parent);
String predefinedName = predefinedNames.get(patternUsage);
final SuggestedNameInfo info =
JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, null, initializer, null);
JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, predefinedName, initializer, null);
final String parameterName = generator.generateUniqueName(info.names.length > 0 ? info.names[0] : "p");
String declarationText = parameter.getLocalVariableTypeText() + " " + parameterName + " = " + initializerText + ";";
@@ -33,7 +33,8 @@ public class ReusedLocalVariablesFinder {
public static List<ReusedLocalVariable> findReusedLocalVariables(@NotNull PsiElement fragmentStart,
@NotNull PsiElement fragmentEnd,
@NotNull Set<PsiLocalVariable> ignoreVariables) {
@NotNull Set<PsiLocalVariable> ignoreVariables,
@NotNull InputVariables inputVariables) {
List<PsiLocalVariable> declaredVariables = getDeclaredVariables(fragmentStart, fragmentEnd, ignoreVariables);
if (declaredVariables.isEmpty()) {
return Collections.emptyList();
@@ -50,7 +51,7 @@ public class ReusedLocalVariablesFinder {
}
List<ReusedLocalVariable> result = new ArrayList<>();
Set<String> tempNames = new HashSet<>();
Set<String> tempNames = new HashSet<>(ContainerUtil.map(inputVariables.getInputVariables(), data -> data.name));
for (PsiLocalVariable variable : reusedVariables) {
String name = variable.getName();
if (name == null) {
@@ -748,6 +748,18 @@ public class ExtractMethodObjectProcessor extends BaseRefactoringProcessor {
}
@Override
protected void initDuplicates() {
myDuplicates = Optional.ofNullable(getExactDuplicatesFinder())
.map(finder -> finder.findDuplicates(myTargetClass))
.orElse(new ArrayList<>());
}
@Override
public boolean initParametrizedDuplicates(boolean showDialog) {
return false;
}
@Override
protected boolean insertNotNullCheckIfPossible() {
return false;
@@ -6,7 +6,7 @@ class C {
}
private void newMethod(int i) {
if (i < 10){
if (i < 10) {
return;
}
}
@@ -7,7 +7,7 @@ class C {
}
private boolean newMethod(int i) {
if (i < 10){
if (i < 10) {
return true;
}
return false;
@@ -7,9 +7,9 @@ class Test10 {
private void newMethod() {
new Object() {
int get() {
return 0;
}
int get() {
return 0;
}
};
}
}
@@ -7,8 +7,8 @@ class Test
}
private boolean newMethod() {
if(test1()) return true;
if(test2()) return true;
if (test1()) return true;
if (test2()) return true;
return false;
}
@@ -7,9 +7,9 @@ class Test10 {
private void newMethod() {
new Super() {
int get() {
return 0;
}
int get() {
return 0;
}
};
}
}
@@ -0,0 +1,17 @@
class DeclaredOutputVariable {
void foo(String[] a, int j) {
<selection>
String s = a[j];
if (s == null) return;
System.out.println(s.charAt(1) + "X");
</selection>
System.out.println(s.length());
}
void bar(String[] a, int k) {
String s = a[k];
if (s == null) return;
System.out.println(s.charAt(2) + "Y");
System.out.println(s.length());
}
}
@@ -0,0 +1,17 @@
class DeclaredOutputVariable {
void foo(String[] a, int j) {
<selection>
if (a[j] == null) return;
String s = a[j];
System.out.println(s.charAt(1) + "X");
</selection>
System.out.println(s.length());
}
void bar(String[] a, int k) {
if (a[k] == null) return;
String s = a[k];
System.out.println(s.charAt(2) + "Y");
System.out.println(s.length());
}
}
@@ -0,0 +1,25 @@
import org.jetbrains.annotations.Nullable;
class DeclaredOutputVariable {
void foo(String[] a, int j) {
String s = newMethod(a[j], 1, "X");
if (s == null) return;
System.out.println(s.length());
}
@Nullable
private String newMethod(String s2, int i, String x) {
if (s2 == null) return null;
String s = s2;
System.out.println(s.charAt(i) + x);
return s;
}
void bar(String[] a, int k) {
String s = newMethod(a[k], 2, "Y");
if (s == null) return;
System.out.println(s.length());
}
}
@@ -0,0 +1,25 @@
import org.jetbrains.annotations.Nullable;
class DeclaredOutputVariable {
void foo(String[] a, int j) {
String s = newMethod(a[j], 1, "X");
if (s == null) return;
System.out.println(s.length());
}
@Nullable
private String newMethod(String s1, int i, String x) {
String s = s1;
if (s == null) return null;
System.out.println(s.charAt(i) + x);
return s;
}
void bar(String[] a, int k) {
String s = newMethod(a[k], 2, "Y");
if (s == null) return;
System.out.println(s.length());
}
}
@@ -0,0 +1,19 @@
import java.util.List;
class DeclaredOutputVariable {
void foo(List<String> a, int j) {
<selection>
String s = a.get(j);
if (s == null) return;
System.out.println(s.charAt(1) + "X");
</selection>
System.out.println(s.length());
}
void bar(List<String> a, int k) {
String s = a.get(k);
if (s == null) return;
System.out.println(s.charAt(2) + "Y");
System.out.println(s.length());
}
}
@@ -0,0 +1,27 @@
import org.jetbrains.annotations.Nullable;
import java.util.List;
class DeclaredOutputVariable {
void foo(List<String> a, int j) {
String s = newMethod(a, j, 1, "X");
if (s == null) return;
System.out.println(s.length());
}
@Nullable
private String newMethod(List<String> a, int j, int i, String x) {
String s = a.get(j);
if (s == null) return null;
System.out.println(s.charAt(i) + x);
return s;
}
void bar(List<String> a, int k) {
String s = newMethod(a, k, 2, "Y");
if (s == null) return;
System.out.println(s.length());
}
}
@@ -0,0 +1,24 @@
class DeclaredOutputVariable {
void foo(String[] a, int j) {
<selection>
String s = a[j];
if (s == null) return;
System.out.println(s.charAt(1) + "X");
</selection>
System.out.println(s.length());
}
void bar(String[] a, int k) {
String s = a[k];
if (s == null) return;
System.out.println(s.charAt(2) + "Y");
System.out.println(s.length());
}
void bar(String[] a, int n) {
String s = a[n];
if (s == null) return;
System.out.println(s.charAt(1) + "Z");
System.out.println(s.length());
}
}
@@ -0,0 +1,31 @@
import org.jetbrains.annotations.Nullable;
class DeclaredOutputVariable {
void foo(String[] a, int j) {
String s = newMethod(a[j], 1, "X");
if (s == null) return;
System.out.println(s.length());
}
@Nullable
private String newMethod(String s1, int i, String x) {
String s = s1;
if (s == null) return null;
System.out.println(s.charAt(i) + x);
return s;
}
void bar(String[] a, int k) {
String s = newMethod(a[k], 2, "Y");
if (s == null) return;
System.out.println(s.length());
}
void bar(String[] a, int n) {
String s = newMethod(a[n], 1, "Z");
if (s == null) return;
System.out.println(s.length());
}
}
@@ -0,0 +1,16 @@
import java.util.Arrays;
class Triple {
void foo(int... x) {
<selection>
System.out.println(Arrays.toString(x)); // original fragment
System.out.println(1);
</selection>
System.out.println(Arrays.toString(x)); // first duplicate
System.out.println(2);
System.out.println(Arrays.toString(new int[]{})); // second duplicate
System.out.println(1);
}
}
@@ -0,0 +1,18 @@
import java.util.Arrays;
class Triple {
void foo(int... x) {
newMethod(x, 1);
newMethod(x, 2);
newMethod(new int[]{}, 1);
}
private void newMethod(int[] x, int i) {
System.out.println(Arrays.toString(x)); // original fragment
System.out.println(i);
}
}
@@ -893,6 +893,26 @@ public class ExtractMethodTest extends LightCodeInsightTestCase {
doDuplicatesTest();
}
public void testParametrizedDuplicateFoldListElement() throws Exception {
doDuplicatesTest();
}
public void testParametrizedDuplicateFoldArrayElement() throws Exception {
doDuplicatesTest();
}
public void testParametrizedMultiDuplicatesFoldArrayElement() throws Exception {
doDuplicatesTest();
}
public void testParametrizedDuplicateFoldArrayElementTwoUsages() throws Exception {
doDuplicatesTest();
}
public void testTripleParametrizedDuplicate() throws Exception {
doDuplicatesTest();
}
public void testSuggestChangeSignatureWithChangedParameterName() throws Exception {
configureByFile(BASE_PATH + getTestName(false) + ".java");
boolean success = performExtractMethod(true, true, getEditor(), getFile(), getProject(), false, null, false, "p");