adjusted solution to memory leaks in RubyProjectStructureConfigurable according to...
[idea/community.git] / plugins / generate-tostring / src / org / jetbrains / java / generate / GenerateToStringActionHandlerImpl.java
1 /*
2  * Copyright 2001-2014 the original author or authors.
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 org.jetbrains.java.generate;
17
18 import com.intellij.codeInsight.CodeInsightActionHandler;
19 import com.intellij.codeInsight.FileModificationService;
20 import com.intellij.codeInsight.generation.PsiElementClassMember;
21 import com.intellij.codeInsight.hint.HintManager;
22 import com.intellij.ide.util.MemberChooser;
23 import com.intellij.openapi.application.WriteAction;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.options.Configurable;
27 import com.intellij.openapi.options.ConfigurationException;
28 import com.intellij.openapi.options.ShowSettingsUtil;
29 import com.intellij.openapi.options.TabbedConfigurable;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.ui.ComboBox;
32 import com.intellij.openapi.ui.DialogWrapper;
33 import com.intellij.psi.*;
34 import com.intellij.psi.search.GlobalSearchScope;
35 import com.intellij.psi.util.PsiTreeUtil;
36 import com.intellij.psi.util.PsiUtil;
37 import com.intellij.ui.JBColor;
38 import com.intellij.ui.ListCellRendererWrapper;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41 import org.jetbrains.generate.tostring.GenerateToStringClassFilter;
42 import org.jetbrains.java.generate.config.Config;
43 import org.jetbrains.java.generate.template.TemplateResource;
44 import org.jetbrains.java.generate.template.toString.ToStringTemplatesManager;
45 import org.jetbrains.java.generate.view.TemplatesPanel;
46
47 import javax.swing.*;
48 import java.awt.*;
49 import java.awt.event.ActionEvent;
50 import java.awt.event.ActionListener;
51 import java.awt.event.KeyEvent;
52 import java.util.ArrayList;
53 import java.util.Collection;
54 import java.util.List;
55
56 /**
57  * The action-handler that does the code generation.
58  */
59 public class GenerateToStringActionHandlerImpl implements GenerateToStringActionHandler, CodeInsightActionHandler {
60     private static final Logger logger = Logger.getInstance("#GenerateToStringActionHandlerImpl");
61
62     @Override
63     public boolean startInWriteAction() {
64         return false;
65     }
66
67     @Override
68     public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
69         PsiClass clazz = getSubjectClass(editor, file);
70         assert clazz != null;
71
72         doExecuteAction(project, clazz, editor);
73     }
74
75
76     public void executeActionQuickFix(final Project project, final PsiClass clazz) {
77         doExecuteAction(project, clazz, null);
78     }
79
80     private static void doExecuteAction(@NotNull final Project project, @NotNull final PsiClass clazz, final Editor editor) {
81         if (!FileModificationService.getInstance().preparePsiElementsForWrite(clazz)) {
82             return;
83         }
84         logger.debug("+++ doExecuteAction - START +++");
85
86         if (logger.isDebugEnabled()) {
87             logger.debug("Current project " + project.getName());
88         }
89
90         final PsiElementClassMember[] dialogMembers = buildMembersToShow(clazz);
91
92         final MemberChooserHeaderPanel header = new MemberChooserHeaderPanel(clazz);
93         logger.debug("Displaying member chooser dialog");
94
95         final MemberChooser<PsiElementClassMember> chooser =
96             new MemberChooser<PsiElementClassMember>(dialogMembers, true, true, project, PsiUtil.isLanguageLevel5OrHigher(clazz), header) {
97                 @Nullable
98                 @Override
99                 protected String getHelpId() {
100                     return "editing.altInsert.tostring";
101                 }
102             };
103         //noinspection DialogTitleCapitalization
104         chooser.setTitle("Generate toString()");
105
106         chooser.setCopyJavadocVisible(false);
107         chooser.selectElements(dialogMembers);
108         header.setChooser(chooser);
109         chooser.show();
110
111         if (DialogWrapper.OK_EXIT_CODE == chooser.getExitCode()) {
112             Collection<PsiMember> selectedMembers = GenerationUtil.convertClassMembersToPsiMembers(chooser.getSelectedElements());
113
114             final TemplateResource template = header.getSelectedTemplate();
115             ToStringTemplatesManager.getInstance().setDefaultTemplate(template);
116
117             if (template.isValidTemplate()) {
118                 WriteAction.run(() -> {
119                     try {
120                         new GenerateToStringWorker(clazz, editor, chooser.isInsertOverrideAnnotation()).execute(selectedMembers, template);
121                     }
122                     catch (Exception e) {
123                         GenerationUtil.handleException(project, e);
124                     }
125                 });
126             }
127             else {
128                 HintManager.getInstance().showErrorHint(editor, "toString() template '" + template.getFileName() + "' is invalid");
129             }
130         }
131
132         logger.debug("+++ doExecuteAction - END +++");
133     }
134
135     public static void updateDialog(PsiClass clazz, MemberChooser<PsiElementClassMember> dialog) {
136         final PsiElementClassMember[] members = buildMembersToShow(clazz);
137         dialog.resetElements(members);
138         dialog.selectElements(members);
139     }
140
141     private static PsiElementClassMember[] buildMembersToShow(PsiClass clazz) {
142         Config config = GenerateToStringContext.getConfig();
143         PsiField[] filteredFields = GenerateToStringUtils.filterAvailableFields(clazz, config.getFilterPattern());
144         if (logger.isDebugEnabled()) logger.debug("Number of fields after filtering: " + filteredFields.length);
145         PsiMethod[] filteredMethods;
146         if (config.enableMethods) {
147             // filter methods as it is enabled from config
148             filteredMethods = GenerateToStringUtils.filterAvailableMethods(clazz, config.getFilterPattern());
149             if (logger.isDebugEnabled()) logger.debug("Number of methods after filtering: " + filteredMethods.length);
150         } else {
151           filteredMethods = PsiMethod.EMPTY_ARRAY;
152         }
153
154         return GenerationUtil.combineToClassMemberList(filteredFields, filteredMethods);
155     }
156
157     @Nullable
158     private static PsiClass getSubjectClass(Editor editor, final PsiFile file) {
159         if (file == null) return null;
160
161         int offset = editor.getCaretModel().getOffset();
162         PsiElement context = file.findElementAt(offset);
163
164         if (context == null) return null;
165
166         PsiClass clazz = PsiTreeUtil.getParentOfType(context, PsiClass.class, false);
167         if (clazz == null) {
168             return null;
169         }
170
171         //exclude interfaces, non-java classes etc
172         for (GenerateToStringClassFilter filter : GenerateToStringClassFilter.EP_NAME.getExtensions()) {
173             if (!filter.canGenerateToString(clazz)) return null;
174         }
175         return clazz;
176     }
177
178     public static class MemberChooserHeaderPanel extends JPanel {
179         private MemberChooser<PsiElementClassMember> chooser;
180         private final JComboBox<TemplateResource> comboBox;
181
182         public void setChooser(MemberChooser chooser) {
183             this.chooser = chooser;
184         }
185
186         public MemberChooserHeaderPanel(final PsiClass clazz) {
187             super(new GridBagLayout());
188
189             final Collection<TemplateResource> templates = ToStringTemplatesManager.getInstance().getAllTemplates();
190             final TemplateResource[] all = templates.toArray(new TemplateResource[templates.size()]);
191
192             final JButton settingsButton = new JButton("Settings");
193             settingsButton.setMnemonic(KeyEvent.VK_S);
194
195             comboBox = new ComboBox<>(all);
196             final JavaPsiFacade instance = JavaPsiFacade.getInstance(clazz.getProject());
197             final GlobalSearchScope resolveScope = clazz.getResolveScope();
198             final ListCellRendererWrapper<TemplateResource> renderer = new ListCellRendererWrapper<TemplateResource>() {
199               @Override
200               public void customize(JList list, TemplateResource value, int index, boolean selected, boolean hasFocus) {
201                 setText(value.getName());
202                 final String className = value.getClassName();
203                 if (className != null && instance.findClass(className, resolveScope) == null) {
204                   setForeground(JBColor.RED);
205                 }
206               }
207             };
208             comboBox.setRenderer(renderer);
209             settingsButton.addActionListener(new ActionListener() {
210                 public void actionPerformed(ActionEvent e) {
211                   final TemplatesPanel ui = new TemplatesPanel(clazz.getProject());
212                   Configurable composite = new TabbedConfigurable() {
213                         protected List<Configurable> createConfigurables() {
214                             List<Configurable> res = new ArrayList<>();
215                             res.add(new GenerateToStringConfigurable(clazz.getProject()));
216                             res.add(ui);
217                             return res;
218                         }
219
220                         public String getDisplayName() {
221                             return "toString() Generation Settings";
222                         }
223
224                         public String getHelpTopic() {
225                             return "editing.altInsert.tostring.settings";
226                         }
227
228                         @Override
229                         public void apply() throws ConfigurationException {
230                             super.apply();
231                             updateDialog(clazz, chooser);
232
233                             comboBox.removeAllItems();
234                             for (TemplateResource resource : ToStringTemplatesManager.getInstance().getAllTemplates()) {
235                               comboBox.addItem(resource);
236                             }
237                             comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate());
238                         }
239                     };
240
241                     ShowSettingsUtil.getInstance().editConfigurable(MemberChooserHeaderPanel.this, composite, () -> ui.selectItem(ToStringTemplatesManager.getInstance().getDefaultTemplate()));
242                   composite.disposeUIResources();
243                 }
244             });
245
246             comboBox.setSelectedItem(ToStringTemplatesManager.getInstance().getDefaultTemplate());
247
248             final JLabel templatesLabel = new JLabel("Template: ");
249             templatesLabel.setDisplayedMnemonic('T');
250             templatesLabel.setLabelFor(comboBox);
251
252             final GridBagConstraints constraints = new GridBagConstraints();
253             constraints.anchor = GridBagConstraints.BASELINE;
254             constraints.gridx = 0;
255             add(templatesLabel, constraints);
256             constraints.gridx = 1;
257             constraints.weightx = 1.0;
258             constraints.fill = GridBagConstraints.HORIZONTAL;
259             add(comboBox, constraints);
260             constraints.gridx = 2;
261             constraints.weightx = 0.0;
262             add(settingsButton, constraints);
263         }
264
265         public TemplateResource getSelectedTemplate() {
266             return (TemplateResource) comboBox.getSelectedItem();
267         }
268     }
269 }