42b47d75f5f26e356d4485ecf5795ea76ae59f3c
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / ui / ConflictsDialog.java
1 // Copyright 2000-2019 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.
2
3 package com.intellij.refactoring.ui;
4
5 import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
6 import com.intellij.openapi.fileEditor.FileEditorLocation;
7 import com.intellij.openapi.project.Project;
8 import com.intellij.openapi.ui.DialogWrapper;
9 import com.intellij.openapi.util.text.HtmlBuilder;
10 import com.intellij.openapi.util.text.StringUtil;
11 import com.intellij.psi.PsiElement;
12 import com.intellij.refactoring.RefactoringBundle;
13 import com.intellij.ui.ScrollPaneFactory;
14 import com.intellij.ui.SimpleTextAttributes;
15 import com.intellij.usageView.UsageInfo;
16 import com.intellij.usages.*;
17 import com.intellij.util.ArrayUtilRt;
18 import com.intellij.util.containers.MultiMap;
19 import com.intellij.util.ui.JBUI;
20 import com.intellij.util.ui.UIUtil;
21 import org.jetbrains.annotations.NonNls;
22 import org.jetbrains.annotations.NotNull;
23 import org.jetbrains.annotations.Nullable;
24
25 import javax.swing.*;
26 import javax.swing.event.HyperlinkEvent;
27 import java.awt.*;
28 import java.awt.event.ActionEvent;
29 import java.util.ArrayList;
30 import java.util.LinkedHashSet;
31 import java.util.regex.Pattern;
32
33 public class ConflictsDialog extends DialogWrapper{
34   private static final int SHOW_CONFLICTS_EXIT_CODE = 4;
35   private static final int MAX_CONFLICTS_SHOWN = 20;
36   @NonNls private static final String EXPAND_LINK = "expand";
37
38   protected final String[] myConflictDescriptions;
39   protected MultiMap<PsiElement, String> myElementConflictDescription;
40   private final Project myProject;
41   private Runnable myDoRefactoringRunnable;
42   private final boolean myCanShowConflictsInView;
43   private String myCommandName;
44
45   public ConflictsDialog(@NotNull Project project, @NotNull MultiMap<PsiElement, String> conflictDescriptions) {
46     this(project, conflictDescriptions, null, true, true);
47   }
48
49   public ConflictsDialog(@NotNull Project project,
50                          @NotNull MultiMap<PsiElement, String> conflictDescriptions,
51                          @Nullable Runnable doRefactoringRunnable) {
52     this(project, conflictDescriptions, doRefactoringRunnable, true, true);
53   }
54
55   public ConflictsDialog(@NotNull Project project,
56                          @NotNull MultiMap<PsiElement, String> conflictDescriptions,
57                          @Nullable Runnable doRefactoringRunnable,
58                          boolean alwaysShowOkButton,
59                          boolean canShowConflictsInView) {
60     super(project, true);
61     myProject = project;
62     myDoRefactoringRunnable = doRefactoringRunnable;
63     myCanShowConflictsInView = canShowConflictsInView;
64
65     final LinkedHashSet<String> conflicts = new LinkedHashSet<>(conflictDescriptions.values());
66     myConflictDescriptions = ArrayUtilRt.toStringArray(conflicts);
67     myElementConflictDescription = conflictDescriptions;
68     setTitle(RefactoringBundle.message("problems.detected.title"));
69     setOKButtonText(RefactoringBundle.message("continue.button"));
70     setOKActionEnabled(alwaysShowOkButton || getDoRefactoringRunnable(null) != null);
71     init();
72   }
73
74   /**
75    * @deprecated use other CTORs
76    */
77   @Deprecated
78   public ConflictsDialog(Project project, String... conflictDescriptions) {
79     super(project, true);
80     myProject = project;
81     myConflictDescriptions = conflictDescriptions;
82     myCanShowConflictsInView = true;
83     setTitle(RefactoringBundle.message("problems.detected.title"));
84     setOKButtonText(RefactoringBundle.message("continue.button"));
85     init();
86   }
87
88   @Override
89   protected Action @NotNull [] createActions(){
90     final Action okAction = getOKAction();
91     boolean showUsagesButton = myElementConflictDescription != null && myCanShowConflictsInView;
92
93     if (showUsagesButton || !okAction.isEnabled()) {
94       okAction.putValue(DEFAULT_ACTION, null);
95     }
96
97     if (!showUsagesButton) {
98       return new Action[]{okAction,new CancelAction()};
99     }
100     return new Action[]{okAction, new MyShowConflictsInUsageViewAction(), new CancelAction()};
101   }
102
103   public boolean isShowConflicts() {
104     return getExitCode() == SHOW_CONFLICTS_EXIT_CODE;
105   }
106
107   @Override
108   protected JComponent createCenterPanel() {
109     JPanel panel = new JPanel(new BorderLayout(0, 2));
110
111     panel.add(new JLabel(RefactoringBundle.message("the.following.problems.were.found")), BorderLayout.NORTH);
112
113     HtmlBuilder buf = new HtmlBuilder();
114
115     for (int i = 0; i < Math.min(myConflictDescriptions.length, MAX_CONFLICTS_SHOWN); i++) {
116       buf.append(myConflictDescriptions[i]).br().br();
117     }
118
119     if (myConflictDescriptions.length > MAX_CONFLICTS_SHOWN) {
120       buf.appendLink(EXPAND_LINK, "Show more...");
121     }
122
123     JEditorPane messagePane = new JEditorPane();
124     messagePane.setEditorKit(UIUtil.getHTMLEditorKit());
125     messagePane.setText(buf.toString());
126     messagePane.setEditable(false);
127     JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(messagePane,
128                                                                 ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
129                                                                 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
130     scrollPane.setPreferredSize(JBUI.size(500, 400));
131     messagePane.addHyperlinkListener(e -> {
132       if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED && 
133           EXPAND_LINK.equals(e.getDescription())) {
134         messagePane.setText(StringUtil.join(myConflictDescriptions, "<br><br>"));
135       }
136     });
137     panel.add(scrollPane, BorderLayout.CENTER);
138
139     if (getOKAction().isEnabled()) {
140       panel.add(new JLabel(RefactoringBundle.message("do.you.wish.to.ignore.them.and.continue")), BorderLayout.SOUTH);
141     }
142
143     return panel;
144   }
145
146   public void setCommandName(String commandName) {
147     myCommandName = commandName;
148   }
149
150   private class CancelAction extends AbstractAction {
151     CancelAction() {
152       super(RefactoringBundle.message("cancel.button"));
153       putValue(DEFAULT_ACTION,Boolean.TRUE);
154     }
155
156     @Override
157     public void actionPerformed(ActionEvent e) {
158       doCancelAction();
159     }
160   }
161
162   protected Runnable getDoRefactoringRunnable(@Nullable UsageView usageView) {
163     return myDoRefactoringRunnable;
164   }
165
166   private class MyShowConflictsInUsageViewAction extends AbstractAction {
167
168
169     MyShowConflictsInUsageViewAction() {
170       super(RefactoringBundle.message("action.show.conflicts.in.view.text"));
171     }
172
173     @Override
174     public void actionPerformed(ActionEvent e) {
175       final UsageViewPresentation presentation = new UsageViewPresentation();
176       final String codeUsagesString = RefactoringBundle.message("conflicts.tab.name");
177       presentation.setCodeUsagesString(codeUsagesString);
178       presentation.setTabName(codeUsagesString);
179       presentation.setTabText(codeUsagesString);
180       presentation.setShowCancelButton(true);
181
182       final ArrayList<Usage> usages = new ArrayList<>(myElementConflictDescription.values().size());
183       for (final PsiElement element : myElementConflictDescription.keySet()) {
184         if (element == null) {
185           usages.add(new DescriptionOnlyUsage());
186           continue;
187         }
188         boolean isRead = false;
189         boolean isWrite = false;
190         ReadWriteAccessDetector detector = ReadWriteAccessDetector.findDetector(element);
191         if (detector != null) {
192           final ReadWriteAccessDetector.Access access = detector.getExpressionAccess(element);
193           isRead = access != ReadWriteAccessDetector.Access.Write;
194           isWrite = access != ReadWriteAccessDetector.Access.Read;
195         }
196
197         for (final String conflictDescription : myElementConflictDescription.get(element)) {
198           final UsagePresentation usagePresentation = new DescriptionOnlyUsage(conflictDescription).getPresentation();
199           Usage usage = isRead || isWrite ? new ReadWriteAccessUsageInfo2UsageAdapter(new UsageInfo(element), isRead, isWrite) {
200             @NotNull
201             @Override
202             public UsagePresentation getPresentation() {
203               return usagePresentation;
204             }
205           } : new UsageInfo2UsageAdapter(new UsageInfo(element)) {
206             @NotNull
207             @Override
208             public UsagePresentation getPresentation() {
209               return usagePresentation;
210             }
211           };
212           usages.add(usage);
213         }
214       }
215       final UsageView usageView = UsageViewManager.getInstance(myProject)
216         .showUsages(UsageTarget.EMPTY_ARRAY, usages.toArray(Usage.EMPTY_ARRAY), presentation);
217       Runnable doRefactoringRunnable = getDoRefactoringRunnable(usageView);
218       if (doRefactoringRunnable != null) {
219         usageView.addPerformOperationAction(
220           doRefactoringRunnable,
221           myCommandName != null ? myCommandName : RefactoringBundle.message("retry.command"),
222           "Unable to perform refactoring. There were changes in code after the usages have been found.", RefactoringBundle.message("usageView.doAction"));
223       }
224       close(SHOW_CONFLICTS_EXIT_CODE);
225     }
226
227     private class DescriptionOnlyUsage implements Usage {
228       private final String myConflictDescription;
229
230       DescriptionOnlyUsage(@NotNull String conflictDescription) {
231         myConflictDescription = StringUtil.unescapeXmlEntities(conflictDescription)
232           .replaceAll("<code>", "")
233           .replaceAll("</code>", "")
234           .replaceAll("<b>", "")
235           .replaceAll("</b>", "");
236       }
237
238       DescriptionOnlyUsage() {
239         myConflictDescription =
240           Pattern.compile("<[^<>]*>").matcher(StringUtil.join(new LinkedHashSet<>(myElementConflictDescription.get(null)), "\n")).replaceAll("");
241       }
242
243       @Override
244       @NotNull
245       public UsagePresentation getPresentation() {
246         return new UsagePresentation() {
247           @Override
248           public TextChunk @NotNull [] getText() {
249             return new TextChunk[] {new TextChunk(SimpleTextAttributes.REGULAR_ATTRIBUTES.toTextAttributes(), myConflictDescription)};
250           }
251
252           @Override
253           @Nullable
254           public Icon getIcon() {
255             return null;
256           }
257
258           @Override
259           public String getTooltipText() {
260             return myConflictDescription;
261           }
262
263           @Override
264           @NotNull
265           public String getPlainText() {
266             return myConflictDescription;
267           }
268         };
269       }
270
271       @Override
272       public boolean canNavigateToSource() {
273         return false;
274       }
275
276       @Override
277       public boolean canNavigate() {
278         return false;
279       }
280       @Override
281       public void navigate(boolean requestFocus) {}
282
283       @Override
284       public FileEditorLocation getLocation() {
285         return null;
286       }
287
288       @Override
289       public boolean isReadOnly() {
290         return false;
291       }
292
293       @Override
294       public boolean isValid() {
295         return true;
296       }
297
298       @Override
299       public void selectInEditor() {}
300       @Override
301       public void highlightInEditor() {}
302     }
303   }
304 }