get rid of intellij.build.toolbox.litegen parameter and use BuildOptions.TOOLBOX_LITE...
[idea/community.git] / java / java-impl / src / com / intellij / refactoring / extractMethod / ParametrizedDuplicates.java
1 /*
2  * Copyright 2000-2017 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.refactoring.extractMethod;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.openapi.util.TextRange;
22 import com.intellij.psi.*;
23 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
24 import com.intellij.psi.codeStyle.SuggestedNameInfo;
25 import com.intellij.psi.codeStyle.VariableKind;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtil;
28 import com.intellij.refactoring.introduceField.ElementToWorkOn;
29 import com.intellij.refactoring.introduceParameter.IntroduceParameterHandler;
30 import com.intellij.refactoring.util.VariableData;
31 import com.intellij.refactoring.util.duplicates.*;
32 import com.intellij.util.containers.ContainerUtil;
33 import com.intellij.util.text.UniqueNameGenerator;
34 import gnu.trove.THashMap;
35 import gnu.trove.THashSet;
36 import one.util.streamex.StreamEx;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.util.*;
41 import java.util.function.BiConsumer;
42 import java.util.function.Predicate;
43
44 import static com.intellij.refactoring.extractMethod.ExtractMethodHandler.REFACTORING_NAME;
45
46 /**
47  * @author Pavel.Dolgov
48  */
49 public class ParametrizedDuplicates {
50   private static final Logger LOG = Logger.getInstance(ParametrizedDuplicates.class);
51
52   private final PsiElement[] myElements;
53   private List<Match> myMatches;
54   private List<ClusterOfUsages> myUsagesList;
55   private PsiMethod myParametrizedMethod;
56   private PsiMethodCallExpression myParametrizedCall;
57   private VariableData[] myVariableDatum;
58
59   private ParametrizedDuplicates(@NotNull PsiElement[] pattern,
60                                  @NotNull ExtractMethodProcessor originalProcessor) {
61     PsiElement[] filteredPattern = getFilteredElements(pattern);
62     PsiElement firstElement = filteredPattern.length != 0 ? filteredPattern[0] : null;
63     if (firstElement instanceof PsiStatement) {
64       PsiElement[] copy = copyElements(pattern);
65       myElements = wrapWithCodeBlock(copy, originalProcessor.getInputVariables());
66     }
67     else if (firstElement instanceof PsiExpression) {
68       PsiElement[] copy = copyElements(pattern);
69       PsiExpression wrapped = wrapExpressionWithCodeBlock(copy, originalProcessor);
70       myElements = wrapped != null ? new PsiElement[]{wrapped} : PsiElement.EMPTY_ARRAY;
71     }
72     else {
73       myElements = PsiElement.EMPTY_ARRAY;
74     }
75   }
76
77   private static PsiElement[] copyElements(@NotNull PsiElement[] pattern) {
78     Project project = pattern[0].getProject();
79     return IntroduceParameterHandler.getElementsInCopy(project, pattern[0].getContainingFile(), pattern, false);
80   }
81
82   @Nullable
83   public static ParametrizedDuplicates findDuplicates(@NotNull ExtractMethodProcessor originalProcessor,
84                                                       @NotNull DuplicatesFinder.MatchType matchType,
85                                                       @Nullable Set<? extends TextRange> textRanges) {
86     DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, matchType, textRanges);
87     if (finder == null) {
88       return null;
89     }
90     List<Match> matches = finder.findDuplicates(originalProcessor.myTargetClass);
91     matches = filterNestedSubexpressions(matches);
92     if (matches.isEmpty()) {
93       return null;
94     }
95
96     Map<PsiExpression, String> predefinedNames = foldParameters(originalProcessor, matches);
97
98     PsiElement[] pattern = originalProcessor.myElements;
99     ParametrizedDuplicates duplicates = new ParametrizedDuplicates(pattern, originalProcessor);
100     if (!duplicates.initMatches(pattern, matches)) {
101       return null;
102     }
103
104     if (!duplicates.extract(originalProcessor, predefinedNames)) {
105       return null;
106     }
107     return duplicates;
108   }
109
110   @NotNull
111   private static Map<PsiExpression, String> foldParameters(ExtractMethodProcessor originalProcessor, List<Match> matches) {
112     if (matches.isEmpty() || !originalProcessor.getInputVariables().isFoldable()) {
113       return Collections.emptyMap();
114     }
115
116     // As folded parameters don't work along with extracted parameters we need to apply the finder again to actually fold the parameters
117     DuplicatesFinder finder = createDuplicatesFinder(originalProcessor, DuplicatesFinder.MatchType.FOLDED, null);
118     if (finder == null) {
119       return Collections.emptyMap();
120     }
121     Map<Match, Match> foldedMatches = new HashMap<>();
122     Map<DuplicatesFinder.Parameter, VariableData> parametersToFold = new LinkedHashMap<>();
123     for (VariableData data : originalProcessor.getInputVariables().getInputVariables()) {
124       parametersToFold.put(new DuplicatesFinder.Parameter(data.variable, data.type, true), data);
125     }
126
127     for (Match match : matches) {
128       Match foldedMatch = finder.isDuplicate(match.getMatchStart(), false);
129       LOG.assertTrue(foldedMatch != null, "folded match should exist");
130       LOG.assertTrue(match.getMatchStart() == foldedMatch.getMatchStart() &&
131                      match.getMatchEnd() == foldedMatch.getMatchEnd(), "folded match range should be the same");
132       foldedMatches.put(match, foldedMatch);
133
134       parametersToFold.keySet().removeIf(parameter -> !canFoldParameter(match, foldedMatch, parameter));
135     }
136     if (parametersToFold.isEmpty()) {
137       return Collections.emptyMap();
138     }
139
140     Map<PsiExpression, String> predefinedNames = new HashMap<>();
141     for (Match match : matches) {
142       Match foldedMatch = foldedMatches.get(match);
143       LOG.assertTrue(foldedMatch != null, "folded match");
144
145       for (Map.Entry<DuplicatesFinder.Parameter, VariableData> entry : parametersToFold.entrySet()) {
146         DuplicatesFinder.Parameter parameter = entry.getKey();
147         VariableData variableData = entry.getValue();
148         List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
149         LOG.assertTrue(!ContainerUtil.isEmpty(expressionMappings), "foldedExpressionMappings can't be empty");
150         PsiType type = parameter.getType();
151
152         ExtractedParameter extractedParameter = null;
153         for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
154           PsiExpression patternExpression = expressionMapping.getFirst();
155           ExtractableExpressionPart patternPart = ExtractableExpressionPart.fromUsage(patternExpression, type);
156           if (extractedParameter == null) {
157             PsiExpression candidateExpression = expressionMapping.getSecond();
158             ExtractableExpressionPart candidatePart = ExtractableExpressionPart.fromUsage(candidateExpression, type);
159             extractedParameter = new ExtractedParameter(patternPart, candidatePart, type);
160           }
161           else {
162             extractedParameter.addUsages(patternPart);
163           }
164           predefinedNames.put(patternExpression, variableData.name);
165         }
166         LOG.assertTrue(extractedParameter != null, "extractedParameter can't be null");
167         match.getExtractedParameters().add(extractedParameter);
168       }
169     }
170
171     return predefinedNames;
172   }
173
174   private static boolean canFoldParameter(Match match, Match foldedMatch, DuplicatesFinder.Parameter parameter) {
175     List<Pair.NonNull<PsiExpression, PsiExpression>> expressionMappings = foldedMatch.getFoldedExpressionMappings(parameter);
176     if (ContainerUtil.isEmpty(expressionMappings)) {
177       return false;
178     }
179     // Extracted parameters and folded parameters shouldn't overlap
180     for (Pair.NonNull<PsiExpression, PsiExpression> expressionMapping : expressionMappings) {
181       PsiExpression patternExpression = expressionMapping.getFirst();
182       for (ExtractedParameter extractedParameter : match.getExtractedParameters()) {
183         for (PsiExpression extractedUsage : extractedParameter.myPatternUsages) {
184           if (PsiTreeUtil.isAncestor(patternExpression, extractedUsage, false) ||
185               PsiTreeUtil.isAncestor(extractedUsage, patternExpression, false)) {
186             return false;
187           }
188         }
189       }
190     }
191     return true;
192   }
193
194   @Nullable
195   private static DuplicatesFinder createDuplicatesFinder(@NotNull ExtractMethodProcessor processor,
196                                                          @NotNull DuplicatesFinder.MatchType matchType,
197                                                          @Nullable Set<? extends TextRange> textRanges) {
198     PsiElement[] elements = getFilteredElements(processor.myElements);
199     if (elements.length == 0) {
200       return null;
201     }
202     Set<PsiVariable> effectivelyLocal = processor.getEffectivelyLocalVariables();
203
204     InputVariables inputVariables = matchType == DuplicatesFinder.MatchType.PARAMETRIZED
205                                     ? processor.myInputVariables.copyWithoutFolding() : processor.myInputVariables;
206     ReturnValue returnValue = processor.myOutputVariable != null ? new VariableReturnValue(processor.myOutputVariable) : null;
207     return new DuplicatesFinder(elements, inputVariables, returnValue,
208                                 Collections.emptyList(), matchType, effectivelyLocal, textRanges);
209   }
210
211   @NotNull
212   public PsiMethod replaceMethod(@NotNull PsiMethod originalMethod) {
213     PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalMethod.getProject());
214     String text = myParametrizedMethod.getText();
215     PsiMethod method = factory.createMethodFromText(text, originalMethod.getParent());
216     return (PsiMethod)originalMethod.replace(method);
217   }
218
219   @NotNull
220   public PsiMethodCallExpression replaceCall(@NotNull PsiMethodCallExpression originalCall) {
221     PsiElementFactory factory = JavaPsiFacade.getElementFactory(originalCall.getProject());
222     String text = myParametrizedCall.getText();
223     PsiMethodCallExpression call = (PsiMethodCallExpression)factory.createExpressionFromText(text, originalCall.getParent());
224     return (PsiMethodCallExpression)originalCall.replace(call);
225   }
226
227   private boolean initMatches(@NotNull PsiElement[] pattern, @NotNull List<Match> matches) {
228     if (myElements.length == 0) {
229       return false;
230     }
231
232     myUsagesList = new ArrayList<>();
233     Map<PsiExpression, ClusterOfUsages> usagesMap = new THashMap<>();
234     Set<Match> badMatches = new THashSet<>();
235     for (Match match : matches) {
236       List<ClusterOfUsages> usagesInMatch = getUsagesInMatch(usagesMap, match);
237       if (usagesInMatch == null) {
238         badMatches.add(match);
239         continue;
240       }
241       for (ClusterOfUsages usages : usagesInMatch) {
242         myUsagesList.add(usages);
243         for (PsiExpression expression : usages.myPatterns) {
244           usagesMap.put(expression, usages);
245         }
246       }
247     }
248
249     if (!badMatches.isEmpty()) {
250       matches = new ArrayList<>(matches);
251       matches.removeAll(badMatches);
252     }
253     myMatches = matches;
254     if (myMatches.isEmpty()) {
255       return false;
256     }
257
258     Map<Match, Map<PsiExpression, PsiExpression>> expressionsMapping = new HashMap<>();
259     for (ClusterOfUsages usages : myUsagesList) {
260       for (Match match : myMatches) {
261         ExtractedParameter parameter = usages.getParameter(match);
262         if (parameter == null) {
263           Map<PsiExpression, PsiExpression> expressions =
264             expressionsMapping.computeIfAbsent(match, unused -> {
265               Map<PsiExpression, PsiExpression> result = new HashMap<>();
266               collectCopyMapping(pattern, match.getMatchElements(), usagesMap.keySet()::contains, result::put, (unused1, unused2) -> {});
267               return result;
268             });
269           PsiExpression candidateUsage = usages.myPatterns.stream().map(expressions::get).findAny().orElse(null);
270           LOG.assertTrue(candidateUsage != null, "candidateUsage shouldn't be null");
271
272           ExtractedParameter fromParameter = usages.myParameter;
273           parameter = fromParameter.copyWithCandidateUsage(candidateUsage);
274           match.addExtractedParameter(parameter);
275           usages.putParameter(match, parameter);
276         }
277       }
278     }
279
280     mergeDuplicateUsages(myUsagesList, myMatches);
281     myUsagesList.sort(Comparator.comparing(usages -> usages.myFirstOffset));
282     return true;
283   }
284
285   private static void mergeDuplicateUsages(@NotNull List<? extends ClusterOfUsages> usagesList, @NotNull List<Match> matches) {
286     Set<ClusterOfUsages> duplicateUsages = new THashSet<>();
287     for (int i = 0; i < usagesList.size(); i++) {
288       ClusterOfUsages usages = usagesList.get(i);
289       if (duplicateUsages.contains(usages)) continue;
290
291       for (int j = i + 1; j < usagesList.size(); j++) {
292         ClusterOfUsages otherUsages = usagesList.get(j);
293
294         if (usages.isEquivalent(otherUsages, matches)) {
295           for (Match match : matches) {
296             ExtractedParameter parameter = usages.getParameter(match);
297             ExtractedParameter otherParameter = otherUsages.getParameter(match);
298             if (parameter != null && otherParameter != null) {
299               parameter.addUsages(otherParameter.myPattern);
300               match.getExtractedParameters().remove(otherParameter);
301             }
302           }
303           duplicateUsages.add(otherUsages);
304         }
305       }
306     }
307     usagesList.removeAll(duplicateUsages);
308   }
309
310   private static List<Match> filterNestedSubexpressions(List<Match> matches) {
311     Map<PsiExpression, Set<Match>> patternUsages = new THashMap<>();
312     for (Match match : matches) {
313       for (ExtractedParameter parameter : match.getExtractedParameters()) {
314         for (PsiExpression patternUsage : parameter.myPatternUsages) {
315           patternUsages.computeIfAbsent(patternUsage, k -> new THashSet<>()).add(match);
316         }
317       }
318     }
319
320     Set<Match> badMatches = new THashSet<>();
321     for (Map.Entry<PsiExpression, Set<Match>> entry : patternUsages.entrySet()) {
322       PsiExpression patternUsage = entry.getKey();
323       Set<Match> patternMatches = entry.getValue();
324       for (PsiExpression maybeNestedUsage : patternUsages.keySet()) {
325         if (patternUsage == maybeNestedUsage) {
326           continue;
327         }
328         if (PsiTreeUtil.isAncestor(patternUsage, maybeNestedUsage, true)) {
329           badMatches.addAll(patternMatches);
330           break;
331         }
332       }
333     }
334
335     if (!badMatches.isEmpty()) {
336       matches = new ArrayList<>(matches);
337       matches.removeAll(badMatches);
338     }
339     return matches;
340   }
341
342   @Nullable
343   private static List<ClusterOfUsages> getUsagesInMatch(@NotNull Map<PsiExpression, ClusterOfUsages> usagesMap, @NotNull Match match) {
344     List<ClusterOfUsages> result = new ArrayList<>();
345     List<ExtractedParameter> parameters = match.getExtractedParameters();
346     for (ExtractedParameter parameter : parameters) {
347       ClusterOfUsages usages = usagesMap.get(parameter.myPattern.getUsage());
348       if (usages != null && !usages.arePatternsEquivalent(parameter) ||
349           usages == null && ClusterOfUsages.isPatternPresent(usagesMap, parameter)) {
350         return null;
351       }
352       if (usages == null) {
353         result.add(usages = new ClusterOfUsages(parameter));
354       }
355       usages.putParameter(match, parameter);
356     }
357     return result;
358   }
359
360   private boolean extract(@NotNull ExtractMethodProcessor originalProcessor, @NotNull Map<PsiExpression, String> predefinedNames) {
361     Map<PsiExpression, PsiExpression> expressionsMapping = new THashMap<>();
362     Map<PsiVariable, PsiVariable> variablesMapping = new THashMap<>();
363     collectCopyMapping(originalProcessor.myElements, myElements, myUsagesList, expressionsMapping, variablesMapping);
364
365     Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations =
366       createParameterDeclarations(originalProcessor, expressionsMapping, predefinedNames);
367     putMatchParameters(parameterDeclarations);
368
369     JavaDuplicatesExtractMethodProcessor parametrizedProcessor = new JavaDuplicatesExtractMethodProcessor(myElements, REFACTORING_NAME);
370     if (!parametrizedProcessor.prepare(false)) {
371       return false;
372     }
373     parametrizedProcessor.applyFrom(originalProcessor, variablesMapping);
374     parametrizedProcessor.doExtract();
375     myParametrizedMethod = parametrizedProcessor.getExtractedMethod();
376     myParametrizedCall = parametrizedProcessor.getMethodCall();
377     myVariableDatum = unmapVariableData(parametrizedProcessor.myVariableDatum, variablesMapping);
378     replaceArguments(parameterDeclarations, myParametrizedCall);
379
380     return true;
381   }
382
383   @NotNull
384   private static VariableData[] unmapVariableData(@NotNull VariableData[] variableDatum,
385                                                   @NotNull Map<PsiVariable, PsiVariable> variablesMapping) {
386     Map<PsiVariable, PsiVariable> reverseMapping = ContainerUtil.reverseMap(variablesMapping);
387     return StreamEx.of(variableDatum)
388                    .map(data -> data.substitute(reverseMapping.get(data.variable)))
389                    .toArray(VariableData[]::new);
390   }
391
392   private static void replaceArguments(@NotNull Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations,
393                                        @NotNull PsiMethodCallExpression parametrizedCall) {
394     PsiExpression[] arguments = parametrizedCall.getArgumentList().getExpressions();
395     for (PsiExpression argument : arguments) {
396       if (argument instanceof PsiReferenceExpression) {
397         PsiElement resolved = ((PsiReferenceExpression)argument).resolve();
398         if (resolved instanceof PsiLocalVariable && parameterDeclarations.containsKey(resolved)) {
399           PsiExpression initializer = ((PsiLocalVariable)resolved).getInitializer();
400           if (initializer != null) {
401             argument.replace(initializer);
402           }
403         }
404       }
405     }
406   }
407
408   private void putMatchParameters(@NotNull Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations) {
409     Map<PsiExpression, PsiLocalVariable> patternUsageToParameter = new THashMap<>();
410     for (Map.Entry<PsiLocalVariable, ClusterOfUsages> entry : parameterDeclarations.entrySet()) {
411       PsiExpression usage = entry.getValue().myParameter.myPattern.getUsage();
412       patternUsageToParameter.put(usage, entry.getKey());
413     }
414
415     for (Match match : myMatches) {
416       List<ExtractedParameter> matchedParameters = match.getExtractedParameters();
417       for (ExtractedParameter matchedParameter : matchedParameters) {
418         PsiLocalVariable localVariable = patternUsageToParameter.get(matchedParameter.myPattern.getUsage());
419         LOG.assertTrue(localVariable != null, "match local variable");
420         DuplicatesFinder.Parameter parameter = new DuplicatesFinder.Parameter(localVariable, matchedParameter.myType);
421         boolean ok = match.putParameter(parameter, matchedParameter.myCandidate.getUsage());
422         LOG.assertTrue(ok, "put match parameter");
423       }
424     }
425   }
426
427   public PsiMethod getParametrizedMethod() {
428     return myParametrizedMethod;
429   }
430
431   public PsiMethodCallExpression getParametrizedCall() {
432     return myParametrizedCall;
433   }
434
435   public VariableData[] getVariableDatum() {
436     return myVariableDatum;
437   }
438
439   public int getSize() {
440     return myMatches != null ? myMatches.size() : 0;
441   }
442
443   public List<Match> getDuplicates() {
444     return myMatches;
445   }
446
447   boolean isEmpty() {
448     return ContainerUtil.isEmpty(myMatches);
449   }
450
451   @NotNull
452   private static PsiElement[] wrapWithCodeBlock(@NotNull PsiElement[] elements, @NotNull InputVariables inputVariables) {
453     PsiElement fragmentStart = elements[0];
454     PsiElement fragmentEnd = elements[elements.length - 1];
455     List<ReusedLocalVariable> reusedLocalVariables =
456       ReusedLocalVariablesFinder.findReusedLocalVariables(fragmentStart, fragmentEnd, Collections.emptySet(), inputVariables);
457
458     PsiElement parent = fragmentStart.getParent();
459     PsiElementFactory factory = JavaPsiFacade.getElementFactory(fragmentStart.getProject());
460     PsiBlockStatement statement = (PsiBlockStatement)factory.createStatementFromText("{}", parent);
461     statement.getCodeBlock().addRange(fragmentStart, fragmentEnd);
462     statement = (PsiBlockStatement)parent.addBefore(statement, fragmentStart);
463     parent.deleteChildRange(fragmentStart, fragmentEnd);
464
465     PsiElement[] elementsInBlock = trimBracesAndWhitespaces(statement.getCodeBlock());
466
467     declareReusedLocalVariables(reusedLocalVariables, statement, factory);
468     return elementsInBlock;
469   }
470
471   @NotNull
472   private static PsiElement[] trimBracesAndWhitespaces(@NotNull PsiCodeBlock codeBlock) {
473     PsiElement[] elements = codeBlock.getChildren();
474     int start = 1;
475     while (start < elements.length && elements[start] instanceof PsiWhiteSpace) {
476       start++;
477     }
478     int end = elements.length - 1;
479     while (end > 0 && elements[end - 1] instanceof PsiWhiteSpace) {
480       end--;
481     }
482     LOG.assertTrue(start < end, "wrapper block length is too small");
483     return Arrays.copyOfRange(elements, start, end);
484   }
485
486   private static void declareReusedLocalVariables(@NotNull List<? extends ReusedLocalVariable> reusedLocalVariables,
487                                                   @NotNull PsiBlockStatement statement,
488                                                   @NotNull PsiElementFactory factory) {
489     PsiElement parent = statement.getParent();
490     PsiCodeBlock codeBlock = statement.getCodeBlock();
491     PsiStatement addAfter = statement;
492     for (ReusedLocalVariable variable : reusedLocalVariables) {
493       if (variable.reuseValue()) {
494         PsiStatement declarationBefore = factory.createStatementFromText(variable.getTempDeclarationText(), codeBlock.getRBrace());
495         parent.addBefore(declarationBefore, statement);
496
497         PsiStatement assignment = factory.createStatementFromText(variable.getAssignmentText(), codeBlock.getRBrace());
498         codeBlock.addBefore(assignment, codeBlock.getRBrace());
499       }
500       PsiStatement declarationAfter = factory.createStatementFromText(variable.getDeclarationText(), statement);
501       parent.addAfter(declarationAfter, addAfter);
502       addAfter = declarationAfter;
503     }
504   }
505
506   @Nullable
507   private static PsiExpression wrapExpressionWithCodeBlock(@NotNull PsiElement[] copy,
508                                                            @NotNull ExtractMethodProcessor originalProcessor) {
509     if (copy.length != 1 || !(copy[0] instanceof PsiExpression)) return null;
510
511     PsiExpression expression = (PsiExpression)copy[0];
512     PsiType type = expression.getType();
513     if (type == null || PsiType.NULL.equals(type)) return null;
514
515     PsiElement parent = expression.getParent();
516     PsiElementFactory factory = JavaPsiFacade.getElementFactory(expression.getProject());
517     PsiClass parentClass = PsiTreeUtil.getParentOfType(expression, PsiClass.class);
518     if (parentClass == null) return null;
519
520     PsiElement parentClassStart = parentClass.getLBrace();
521     if (parentClassStart == null) return null;
522
523     // It's syntactically correct to write "new Object() {void foo(){}}.foo()" - see JLS 15.9.5
524     String wrapperBodyText = (PsiType.VOID.equals(type) ? "" : "return ") + expression.getText() + ";";
525     String wrapperClassImmediateCallText = "new " + CommonClassNames.JAVA_LANG_OBJECT + "() { " +
526                                            type.getCanonicalText() + " wrapperMethod() {" + wrapperBodyText + "} " +
527                                            "}.wrapperMethod()";
528     PsiExpression wrapperClassImmediateCall = factory.createExpressionFromText(wrapperClassImmediateCallText, parent);
529     wrapperClassImmediateCall = (PsiExpression)expression.replace(wrapperClassImmediateCall);
530     PsiMethod method = PsiTreeUtil.findChildOfType(wrapperClassImmediateCall, PsiMethod.class);
531     LOG.assertTrue(method != null, "wrapper class method is null");
532
533     PsiCodeBlock body = method.getBody();
534     LOG.assertTrue(body != null, "wrapper class method's body is null");
535
536     PsiStatement[] statements = body.getStatements();
537     LOG.assertTrue(statements.length == 1, "wrapper class method's body statement count");
538     PsiStatement bodyStatement = statements[0];
539
540     Set<PsiVariable> effectivelyLocal = originalProcessor.getEffectivelyLocalVariables();
541     for (PsiVariable variable : effectivelyLocal) {
542       String name = variable.getName();
543       LOG.assertTrue(name != null, "effectively local variable's name is null");
544       PsiDeclarationStatement declaration = factory.createVariableDeclarationStatement(name, variable.getType(), null);
545       body.addBefore(declaration, bodyStatement);
546     }
547
548     PsiExpression wrapped = null;
549     if (PsiType.VOID.equals(type) && bodyStatement instanceof PsiExpressionStatement) {
550       wrapped = ((PsiExpressionStatement)bodyStatement).getExpression();
551     }
552     else if (bodyStatement instanceof PsiReturnStatement) {
553       wrapped = ((PsiReturnStatement)bodyStatement).getReturnValue();
554     }
555     else {
556       LOG.error("Unexpected statement in expression code block " + bodyStatement);
557     }
558     if (wrapped != null) {
559       // this key is not copyable so replace() doesn't preserve it - have to do it here
560       wrapped.putUserData(ElementToWorkOn.REPLACE_NON_PHYSICAL, expression.getUserData(ElementToWorkOn.REPLACE_NON_PHYSICAL));
561     }
562     return wrapped;
563   }
564
565   @NotNull
566   private Map<PsiLocalVariable, ClusterOfUsages> createParameterDeclarations(@NotNull ExtractMethodProcessor originalProcessor,
567                                                                              @NotNull Map<PsiExpression, PsiExpression> expressionsMapping,
568                                                                              @NotNull Map<PsiExpression, String> predefinedNames) {
569
570     Project project = myElements[0].getProject();
571     Map<PsiLocalVariable, ClusterOfUsages> parameterDeclarations = new THashMap<>();
572     UniqueNameGenerator generator = originalProcessor.getParameterNameGenerator(myElements[0]);
573     PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
574     PsiStatement statement =
575       myElements[0] instanceof PsiStatement ? (PsiStatement)myElements[0] : PsiTreeUtil.getParentOfType(myElements[0], PsiStatement.class);
576     LOG.assertTrue(statement != null, "first statement is null");
577     PsiElement parent = statement.getParent();
578     LOG.assertTrue(parent instanceof PsiCodeBlock, "first statement's parent isn't a code block");
579
580     for (ClusterOfUsages usages : myUsagesList) {
581       ExtractedParameter parameter = usages.myParameter;
582       PsiExpression patternUsage = parameter.myPattern.getUsage();
583       String initializerText = patternUsage.getText();
584       PsiExpression initializer = factory.createExpressionFromText(initializerText, parent);
585       String predefinedName = predefinedNames.get(patternUsage);
586       final SuggestedNameInfo info =
587         JavaCodeStyleManager.getInstance(project).suggestVariableName(VariableKind.PARAMETER, predefinedName, initializer, null);
588       final String parameterName = generator.generateUniqueName(info.names.length > 0 ? info.names[0] : "p");
589
590       String declarationText = parameter.getLocalVariableTypeText() + " " + parameterName + " = " + initializerText + ";";
591       PsiDeclarationStatement paramDeclaration = (PsiDeclarationStatement)factory.createStatementFromText(declarationText, parent);
592       paramDeclaration = (PsiDeclarationStatement)parent.addBefore(paramDeclaration, statement);
593       PsiLocalVariable localVariable = (PsiLocalVariable)paramDeclaration.getDeclaredElements()[0];
594       parameterDeclarations.put(localVariable, usages);
595
596       for (PsiExpression expression : parameter.myPatternUsages) {
597         PsiExpression mapped = expressionsMapping.get(expression);
598         if (mapped != null) {
599           PsiExpression replacement = factory.createExpressionFromText(parameterName, expression);
600           mapped.replace(replacement);
601         }
602       }
603     }
604
605     return parameterDeclarations;
606   }
607
608   private static void collectCopyMapping(@NotNull PsiElement[] pattern,
609                                          @NotNull PsiElement[] copy,
610                                          @NotNull List<? extends ClusterOfUsages> patternUsages,
611                                          @NotNull Map<PsiExpression, PsiExpression> expressions,
612                                          @NotNull Map<PsiVariable, PsiVariable> variables) {
613     Set<PsiExpression> patternExpressions = new THashSet<>();
614     for (ClusterOfUsages usages : patternUsages) {
615       patternExpressions.addAll(usages.myPatterns);
616     }
617
618     collectCopyMapping(pattern, copy, patternExpressions::contains, expressions::put, variables::put);
619   }
620
621   public static void collectCopyMapping(@NotNull PsiElement[] pattern,
622                                         @NotNull PsiElement[] copy,
623                                         @NotNull Predicate<? super PsiExpression> isReplaceablePattern,
624                                         @NotNull BiConsumer<? super PsiExpression, ? super PsiExpression> expressionsMapping,
625                                         @NotNull BiConsumer<? super PsiVariable, ? super PsiVariable> variablesMapping) {
626     pattern = DuplicatesFinder.getDeeplyFilteredElements(pattern);
627     copy = DuplicatesFinder.getDeeplyFilteredElements(copy);
628     if (copy.length != pattern.length) {
629       return; // it's an extracted parameter, so there's no need to go deeper
630     }
631     for (int i = 0; i < pattern.length; i++) {
632       collectCopyMapping(pattern[i], copy[i], isReplaceablePattern, expressionsMapping, variablesMapping);
633     }
634   }
635
636   private static void collectCopyMapping(@NotNull PsiElement pattern,
637                                          @NotNull PsiElement copy,
638                                          @NotNull Predicate<? super PsiExpression> isReplaceablePattern,
639                                          @NotNull BiConsumer<? super PsiExpression, ? super PsiExpression> expressionsMapping,
640                                          @NotNull BiConsumer<? super PsiVariable, ? super PsiVariable> variablesMapping) {
641     if (pattern == copy) return;
642     if (pattern instanceof PsiExpression && copy instanceof PsiExpression && isReplaceablePattern.test((PsiExpression)pattern)) {
643       expressionsMapping.accept((PsiExpression)pattern, (PsiExpression)copy);
644       return;
645     }
646
647     if (pattern instanceof PsiJavaCodeReferenceElement && copy instanceof PsiJavaCodeReferenceElement) {
648       PsiElement resolvedPattern = ((PsiJavaCodeReferenceElement)pattern).resolve();
649       PsiElement resolvedCopy = ((PsiJavaCodeReferenceElement)copy).resolve();
650       if (resolvedPattern != resolvedCopy && resolvedPattern instanceof PsiVariable && resolvedCopy instanceof PsiVariable) {
651         variablesMapping.accept((PsiVariable)resolvedPattern, (PsiVariable)resolvedCopy);
652       }
653       PsiElement patternQualifier = ((PsiJavaCodeReferenceElement)pattern).getQualifier();
654       PsiElement copyQualifier = ((PsiJavaCodeReferenceElement)copy).getQualifier();
655       if (patternQualifier != null && copyQualifier != null) {
656         collectCopyMapping(patternQualifier, copyQualifier, isReplaceablePattern, expressionsMapping, variablesMapping);
657       }
658       return;
659     }
660
661     if (pattern instanceof PsiVariable && copy instanceof PsiVariable) {
662       variablesMapping.accept((PsiVariable)pattern, (PsiVariable)copy);
663     }
664
665     collectCopyMapping(pattern.getChildren(), copy.getChildren(), isReplaceablePattern, expressionsMapping, variablesMapping);
666   }
667
668   @NotNull
669   private static PsiElement[] getFilteredElements(@NotNull PsiElement[] elements) {
670     if (elements.length == 0) {
671       return elements;
672     }
673     ArrayList<PsiElement> result = new ArrayList<>(elements.length);
674     for (PsiElement e : elements) {
675       if (e == null || e instanceof PsiWhiteSpace || e instanceof PsiComment || e instanceof PsiEmptyStatement) {
676         continue;
677       }
678       if (e instanceof PsiParenthesizedExpression) {
679         e = PsiUtil.skipParenthesizedExprDown((PsiParenthesizedExpression)e);
680       }
681       result.add(e);
682     }
683     return result.toArray(PsiElement.EMPTY_ARRAY);
684   }
685
686   private static class ClusterOfUsages {
687     @NotNull private final Set<PsiExpression> myPatterns;
688     @NotNull private final Map<Match, ExtractedParameter> myParameters;
689     @NotNull private final ExtractedParameter myParameter;
690     private final int myFirstOffset;
691
692     ClusterOfUsages(@NotNull ExtractedParameter parameter) {
693       myPatterns = parameter.myPatternUsages;
694       myParameters = new THashMap<>();
695       myParameter = parameter;
696       myFirstOffset = myPatterns.stream().mapToInt(PsiElement::getTextOffset).min().orElse(0);
697     }
698
699     void putParameter(@NotNull Match match, @NotNull ExtractedParameter parameter) {
700       myParameters.put(match, parameter);
701     }
702
703     @Nullable
704     ExtractedParameter getParameter(@NotNull Match match) {
705       return myParameters.get(match);
706     }
707
708     boolean arePatternsEquivalent(@NotNull ExtractedParameter parameter) {
709       return myPatterns.equals(parameter.myPatternUsages);
710     }
711
712     boolean isEquivalent(@NotNull ClusterOfUsages usages, @NotNull Collection<Match> matches) {
713       if (!myParameter.myPattern.isEquivalent(usages.myParameter.myPattern)) {
714         return false;
715       }
716       for (Match match : matches) {
717         ExtractedParameter parameter = getParameter(match);
718         ExtractedParameter otherParameter = usages.getParameter(match);
719         if (parameter == null || otherParameter == null || !parameter.myCandidate.isEquivalent(otherParameter.myCandidate)) {
720           return false;
721         }
722       }
723       return true;
724     }
725
726     static boolean isPatternPresent(@NotNull Map<PsiExpression, ClusterOfUsages> usagesMap, @NotNull ExtractedParameter parameter) {
727       return parameter.myPatternUsages.stream().anyMatch(usagesMap::containsKey);
728     }
729
730     @Override
731     public String toString() {
732       return StreamEx.of(myParameters.values()).map(p -> p.myPattern + "->" + p.myCandidate).joining(", ");
733     }
734   }
735 }