[duplicates] enable duplicates analysis in PyCharm/WebStorm/PhpStorm/RubyMine
[idea/community.git] / java / java-analysis-impl / src / com / intellij / refactoring / util / duplicates / ExtractedParameter.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.util.duplicates;
17
18 import com.intellij.psi.*;
19 import com.intellij.psi.tree.IElementType;
20 import com.intellij.psi.util.PsiUtil;
21 import com.intellij.util.ObjectUtils;
22 import com.intellij.util.containers.ContainerUtil;
23 import one.util.streamex.StreamEx;
24 import org.jetbrains.annotations.NotNull;
25
26 import java.util.*;
27
28 /**
29  * @author Pavel.Dolgov
30  */
31 public class ExtractedParameter {
32   @NotNull public final PsiType myType;
33   @NotNull public final ExtractableExpressionPart myPattern;
34   @NotNull public final ExtractableExpressionPart myCandidate;
35   @NotNull public final Set<PsiExpression> myPatternUsages = new HashSet<>();
36
37   public ExtractedParameter(@NotNull ExtractableExpressionPart patternPart,
38                             @NotNull ExtractableExpressionPart candidatePart,
39                             @NotNull PsiType type) {
40     myType = type;
41     myPattern = patternPart;
42     myCandidate = candidatePart;
43     addUsages(patternPart);
44   }
45
46   public static boolean match(@NotNull ExtractableExpressionPart patternPart,
47                               @NotNull ExtractableExpressionPart candidatePart,
48                               @NotNull List<ExtractedParameter> parameters) {
49     PsiType type = ExtractableExpressionPart.commonType(patternPart, candidatePart);
50     if (type == null) {
51       return false;
52     }
53     parameters.add(new ExtractedParameter(patternPart, candidatePart, type));
54     return true;
55   }
56
57   @NotNull
58   public ExtractedParameter copyWithCandidateUsage(@NotNull PsiExpression candidateUsage) {
59     ExtractedParameter result = new ExtractedParameter(myPattern, ExtractableExpressionPart.fromUsage(candidateUsage, myType), myType);
60     result.myPatternUsages.addAll(myPatternUsages);
61     return result;
62   }
63
64   @NotNull
65   public String getLocalVariableTypeText() {
66     PsiType type = GenericsUtil.getVariableTypeByExpressionType(myType);
67     return type.getCanonicalText();
68   }
69
70   public void addUsages(@NotNull ExtractableExpressionPart patternPart) {
71     myPatternUsages.add(patternPart.getUsage());
72   }
73
74   public static List<Match> getCompatibleMatches(@NotNull List<Match> matches,
75                                                  @NotNull PsiElement[] pattern,
76                                                  @NotNull List<PsiElement[]> candidates) {
77     List<Match> result = new ArrayList<>();
78     Set<PsiExpression> firstUsages = null;
79     for (Match match : matches) {
80       List<ExtractedParameter> parameters = match.getExtractedParameters();
81       PsiElement[] candidateElements = ContainerUtil.find(candidates,
82                                                           elements -> elements.length != 0 && match.getMatchStart() == elements[0]);
83       Set<PsiVariable> candidateVariables = ContainerUtil.map2SetNotNull(parameters, parameter -> parameter.myCandidate.myVariable);
84       if (candidateElements == null || containsModifiedField(candidateElements, candidateVariables)) {
85         continue;
86       }
87       Set<PsiExpression> patternUsages = StreamEx.of(parameters).map(p -> p.myPattern.getUsage()).toSet();
88       if (firstUsages == null) {
89         Set<PsiVariable> patternVariables = ContainerUtil.map2SetNotNull(parameters, parameter -> parameter.myPattern.myVariable);
90         if (containsModifiedField(pattern, patternVariables)) {
91           return Collections.emptyList();
92         }
93         firstUsages = patternUsages;
94         result.add(match);
95       }
96       else if (firstUsages.equals(patternUsages)) {
97         result.add(match);
98       }
99     }
100     return result;
101   }
102
103   private static boolean containsModifiedField(@NotNull PsiElement[] elements, @NotNull Set<PsiVariable> variables) {
104     Set<PsiField> fields = StreamEx.of(variables)
105       .select(PsiField.class)
106       .filter(field -> !field.hasModifierProperty(PsiModifier.FINAL))
107       .toSet();
108
109     if (!fields.isEmpty()) {
110       FieldModificationVisitor visitor = new FieldModificationVisitor(fields);
111       for (PsiElement element : elements) {
112         element.accept(visitor);
113         if (visitor.myModified) {
114           return true;
115         }
116       }
117     }
118     return false;
119   }
120
121   private static class FieldModificationVisitor extends JavaRecursiveElementWalkingVisitor {
122     private final Set<PsiField> myFields;
123     private boolean myModified;
124
125     FieldModificationVisitor(Set<PsiField> fields) {
126       myFields = fields;
127     }
128
129     @Override
130     public void visitAssignmentExpression(PsiAssignmentExpression expression) {
131       super.visitAssignmentExpression(expression);
132
133       visitModifiedExpression(expression.getLExpression());
134     }
135
136     @Override
137     public void visitPrefixExpression(PsiPrefixExpression expression) {
138       super.visitPrefixExpression(expression);
139
140       IElementType op = expression.getOperationTokenType();
141       if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) {
142         visitModifiedExpression(expression.getOperand());
143       }
144     }
145
146     @Override
147     public void visitPostfixExpression(PsiPostfixExpression expression) {
148       super.visitPostfixExpression(expression);
149
150       IElementType op = expression.getOperationTokenType();
151       if (op == JavaTokenType.PLUSPLUS || op == JavaTokenType.MINUSMINUS) {
152         visitModifiedExpression(expression.getOperand());
153       }
154     }
155
156     private void visitModifiedExpression(PsiExpression modifiedExpression) {
157       PsiExpression expression = PsiUtil.skipParenthesizedExprDown(modifiedExpression);
158       if (expression instanceof PsiReferenceExpression) {
159         PsiField field = ObjectUtils.tryCast(((PsiReferenceExpression)expression).resolve(), PsiField.class);
160         if (field != null && myFields.contains(field)) {
161           myModified = true;
162           stopWalking();
163         }
164       }
165     }
166   }
167
168   @Override
169   public String toString() {
170     return myPattern + " -> " + myCandidate + " [" + myPatternUsages.size() + "] : " + myType.getPresentableText();
171   }
172 }