ability to change name of method to any string constant. Detection of incorrect usages
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / changeSignature / GrChangeSignatureDialog.java
1 /*
2  * Copyright 2000-2010 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 org.jetbrains.plugins.groovy.refactoring.changeSignature;
17
18 import com.intellij.openapi.editor.Document;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.util.text.StringUtil;
21 import com.intellij.psi.*;
22 import com.intellij.refactoring.HelpID;
23 import com.intellij.refactoring.changeSignature.ExceptionsTableModel;
24 import com.intellij.refactoring.changeSignature.ThrownExceptionInfo;
25 import com.intellij.refactoring.ui.CodeFragmentTableCellEditor;
26 import com.intellij.refactoring.ui.CodeFragmentTableCellRenderer;
27 import com.intellij.refactoring.ui.RefactoringDialog;
28 import com.intellij.refactoring.util.CanonicalTypes;
29 import com.intellij.refactoring.util.CommonRefactoringUtil;
30 import com.intellij.ui.EditableRowTable;
31 import com.intellij.ui.EditorTextField;
32 import com.intellij.ui.TableUtil;
33 import com.intellij.ui.table.JBTable;
34 import com.intellij.util.Function;
35 import com.intellij.util.containers.ContainerUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
38 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
39 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
41 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
42 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
43 import org.jetbrains.plugins.groovy.refactoring.ui.GrCodeFragmentTableCellEditor;
44 import org.jetbrains.plugins.groovy.refactoring.ui.GrCodeFragmentTableCellRenderer;
45
46 import javax.swing.*;
47 import javax.swing.event.TableModelEvent;
48 import javax.swing.event.TableModelListener;
49 import javax.swing.table.TableColumnModel;
50 import java.awt.*;
51 import java.awt.event.ActionEvent;
52 import java.awt.event.ActionListener;
53 import java.util.List;
54
55 import static org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle.message;
56
57 /**
58  * @author Maxim.Medvedev
59  */
60 public class GrChangeSignatureDialog extends RefactoringDialog {
61   private EditorTextField myNameField;
62   private EditorTextField myReturnTypeField;
63   private JRadioButton myPublicRadioButton;
64   private JRadioButton myProtectedRadioButton;
65   private JRadioButton myPrivateRadioButton;
66   private JBTable myParameterTable;
67   private JPanel contentPane;
68   private JTextArea mySignatureLabel;
69   private JLabel myNameLabel;
70   private JLabel myReturnTypeLabel;
71   @SuppressWarnings({"UnusedDeclaration"}) private JRadioButton myModifyRadioButton;
72   private JRadioButton myDelegateRadioButton;
73   @SuppressWarnings({"UnusedDeclaration"}) private JPanel myParameterButtonPanel;
74   private JBTable myExceptionsTable;
75   @SuppressWarnings({"UnusedDeclaration"}) private JPanel myExceptionsButtonPanel;
76   private JPanel myDelegatePanel;
77   private GrParameterTableModel myParameterModel;
78   private GrMethod myMethod;
79   private PsiTypeCodeFragment myReturnTypeCodeFragment;
80   private GroovyCodeFragment myNameCodeFragment;
81   private ExceptionsTableModel myExceptionTableModel;
82   private static final String INDENT = "    ";
83
84   public GrChangeSignatureDialog(@NotNull Project project, GrMethod method) {
85     super(project, true);
86     myMethod = method;
87     init();
88     updateSignature();
89     ActionListener listener = new ActionListener() {
90       public void actionPerformed(ActionEvent e) {
91         updateSignature();
92       }
93     };
94     myPublicRadioButton.addActionListener(listener);
95     myPrivateRadioButton.addActionListener(listener);
96     myProtectedRadioButton.addActionListener(listener);
97   }
98
99   protected void init() {
100     super.init();
101     final PsiClass psiClass = myMethod.getContainingClass();
102     if (psiClass == null) return;
103     if (psiClass.isInterface()) {
104       myDelegatePanel.setVisible(false);
105     }
106   }
107
108   private void stopEditing() {
109     TableUtil.stopEditing(myParameterTable);
110   }
111
112   @Override
113   protected JComponent createCenterPanel() {
114     return contentPane;
115   }
116
117
118   private void createUIComponents() {
119     createNameAndReturnTypeEditors();
120     createParametersPanel();
121     createExceptionsPanel();
122   }
123
124   private void createNameAndReturnTypeEditors() {
125     myNameCodeFragment = new GroovyCodeFragment(myProject, "");
126     myNameField = new EditorTextField(PsiDocumentManager.getInstance(myProject).getDocument(myNameCodeFragment), myProject,
127                                       myNameCodeFragment.getFileType());
128
129     myReturnTypeCodeFragment = JavaPsiFacade.getInstance(myProject).getElementFactory().createTypeCodeFragment("", myMethod, true, true);
130     final Document document = PsiDocumentManager.getInstance(myProject).getDocument(myReturnTypeCodeFragment);
131     myReturnTypeField = new EditorTextField(document, myProject, myReturnTypeCodeFragment.getFileType());
132
133     myNameField.setText(myMethod.getName());
134     final GrTypeElement element = myMethod.getReturnTypeElementGroovy();
135     if (element != null) {
136       myReturnTypeField.setText(element.getText());
137     }
138
139     myReturnTypeLabel = new JLabel();
140     myReturnTypeLabel.setLabelFor(myReturnTypeField);
141
142     myNameLabel = new JLabel();
143     myNameLabel.setLabelFor(myNameField);
144   }
145
146   private void createParametersPanel() {
147     myParameterModel = new GrParameterTableModel(myMethod, this, myProject);
148     myParameterModel.addTableModelListener(new TableModelListener() {
149       public void tableChanged(TableModelEvent e) {
150         updateSignature();
151       }
152     });
153     myParameterTable = new JBTable(myParameterModel);
154     myParameterTable.setPreferredScrollableViewportSize(new Dimension(550, myParameterTable.getRowHeight() * 8));
155
156     myParameterButtonPanel = EditableRowTable.createButtonsTable(myParameterTable, myParameterModel, true);
157
158     myParameterTable.setCellSelectionEnabled(true);
159     final TableColumnModel columnModel = myParameterTable.getColumnModel();
160     columnModel.getColumn(0).setCellRenderer(new CodeFragmentTableCellRenderer(myProject));
161     columnModel.getColumn(1).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
162     columnModel.getColumn(2).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
163     columnModel.getColumn(3).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
164
165     columnModel.getColumn(0).setCellEditor(new CodeFragmentTableCellEditor(myProject));
166     columnModel.getColumn(1).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
167     columnModel.getColumn(2).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
168     columnModel.getColumn(3).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
169
170     if (myParameterModel.getRowCount() > 0) {
171       myParameterTable.setRowSelectionInterval(0, 0);
172       myParameterTable.setColumnSelectionInterval(0, 0);
173     }
174   }
175
176   private void createExceptionsPanel() {
177     myExceptionTableModel = new ExceptionsTableModel(myMethod);
178     myExceptionTableModel.setTypeInfos(myMethod);
179     myExceptionTableModel.addTableModelListener(new TableModelListener() {
180       public void tableChanged(TableModelEvent e) {
181         updateSignature();
182       }
183     });
184     myExceptionsTable = new JBTable(myExceptionTableModel);
185     myExceptionsTable.setPreferredScrollableViewportSize(new Dimension(200, myExceptionsTable.getRowHeight() * 8));
186
187     myExceptionsButtonPanel = EditableRowTable.createButtonsTable(myExceptionsTable, myExceptionTableModel, false);
188
189     myExceptionsTable.getColumnModel().getColumn(0).setCellRenderer(new CodeFragmentTableCellRenderer(myProject));
190     myExceptionsTable.getColumnModel().getColumn(0).setCellEditor(new CodeFragmentTableCellEditor(myProject));
191
192     if (myExceptionTableModel.getRowCount() > 0) {
193       myExceptionsTable.setRowSelectionInterval(0, 0);
194       myExceptionsTable.setColumnSelectionInterval(0, 0);
195     }
196
197   }
198
199
200   private void updateSignature() {
201     mySignatureLabel.setText(generateSignatureText());
202   }
203
204   private String generateSignatureText() {
205     String name = getNewName();
206     String type = myReturnTypeField.getText().trim();
207
208     StringBuilder builder = new StringBuilder();
209     if (myPublicRadioButton.isSelected() && type.length() == 0) {
210       builder.append(GrModifier.DEF);
211     }
212     if (myPrivateRadioButton.isSelected()) {
213       builder.append(GrModifier.PRIVATE).append(' ');
214     }
215     else if (myProtectedRadioButton.isSelected()) {
216       builder.append(GrModifier.PROTECTED).append(' ');
217     }
218     builder.append(type).append(' ');
219     builder.append(name).append('(');
220
221     final List<GrTableParameterInfo> infos = myParameterModel.getParameterInfos();
222     if (infos.size() > 0) {
223       final List<String> paramsText = ContainerUtil.map(infos, new Function<GrTableParameterInfo, String>() {
224         public String fun(GrTableParameterInfo grParameterInfo) {
225           return generateParameterText(grParameterInfo);
226         }
227       });
228       builder.append("\n").append(INDENT);
229       builder.append(StringUtil.join(paramsText, ",\n" + INDENT));
230       builder.append('\n');
231     }
232     builder.append(')');
233
234     final PsiTypeCodeFragment[] exceptions = myExceptionTableModel.getTypeCodeFragments();
235     if (exceptions.length > 0) {
236       builder.append("\nthrows\n");
237       final List<String> exceptionNames = ContainerUtil.map(exceptions, new Function<PsiTypeCodeFragment, String>() {
238         public String fun(PsiTypeCodeFragment fragment) {
239           return fragment.getText();
240         }
241       });
242
243       builder.append(INDENT).append(StringUtil.join(exceptionNames, ",\n" + INDENT));
244     }
245     return builder.toString();
246   }
247
248
249   private static String generateParameterText(GrTableParameterInfo info) {
250     StringBuilder builder = new StringBuilder();
251     final PsiTypeCodeFragment typeFragment = info.getTypeFragment();
252     String typeText = typeFragment != null ? typeFragment.getText().trim() : GrModifier.DEF;
253     if (typeText.length() == 0) typeText = GrModifier.DEF;
254     builder.append(typeText).append(' ');
255     final GroovyCodeFragment nameFragment = info.getNameFragment();
256     builder.append(nameFragment != null ? nameFragment.getText().trim() : "");
257     final GroovyCodeFragment defaultInitializer = info.getDefaultInitializerFragment();
258
259     final String defaultInitializerText = defaultInitializer != null ? defaultInitializer.getText().trim() : "";
260     if (defaultInitializerText.length() > 0) {
261       builder.append(" = ").append(defaultInitializerText);
262     }
263     return builder.toString();
264   }
265
266   @Override
267   protected void doAction() {
268     if (!validateInputData()) {
269       return;
270     }
271
272     stopEditing();
273     String modifier = "";
274     if (myPublicRadioButton.isSelected()) {
275       modifier = GrModifier.PUBLIC;
276     }
277     else if (myPrivateRadioButton.isSelected()) {
278       modifier = GrModifier.PRIVATE;
279     }
280     else if (myProtectedRadioButton.isSelected()) {
281       modifier = GrModifier.PROTECTED;
282     }
283
284     PsiType returnType = null;
285     try {
286       returnType = myReturnTypeCodeFragment.getType();
287     }
288     catch (PsiTypeCodeFragment.TypeSyntaxException ignored) {
289     }
290     catch (PsiTypeCodeFragment.NoTypeException ignored) {
291     }
292
293     String newName = getNewName();
294     final List<GrTableParameterInfo> tableParameterInfos = myParameterModel.getParameterInfos();
295     final List<GrParameterInfo> parameterInfos = ContainerUtil.map(tableParameterInfos, new Function<GrTableParameterInfo, GrParameterInfo>() {
296       public GrParameterInfo fun(GrTableParameterInfo info) {
297         return info.generateParameterInfo();
298       }
299     });
300     final ThrownExceptionInfo[] exceptionInfos = myExceptionTableModel.getThrownExceptions();
301     invokeRefactoring(new GrChangeSignatureProcessor(myProject, new GrChangeInfoImpl(myMethod, modifier, returnType == null
302                                                                                                          ? null
303                                                                                                          : CanonicalTypes
304                                                                                                            .createTypeWrapper(returnType),
305                                                                                      newName, parameterInfos, exceptionInfos,
306                                                                                      myDelegateRadioButton.isSelected())));
307   }
308
309   private String getNewName() {
310     return myNameField.getText().trim();
311   }
312
313   private void showErrorHint(String hint) {
314     CommonRefactoringUtil.showErrorHint(myProject, null, hint, GroovyRefactoringBundle.message("incorrect.data"), HelpID.CHANGE_SIGNATURE);
315   }
316
317   private boolean isGroovyMethodName(String name) {
318     String methodText = "def " + name + "(){}";
319     try {
320       final GrMethod method = GroovyPsiElementFactory.getInstance(getProject()).createMethodFromText(methodText);
321       return method != null;
322     }
323     catch (Throwable e) {
324       return false;
325     }
326   }
327
328   private boolean validateInputData() {
329     if (!isGroovyMethodName(getNewName())) {
330       showErrorHint(message("name.is.wrong", getNewName()));
331       return false;
332     }
333
334     if (!checkType(myReturnTypeCodeFragment)) {
335       showErrorHint(message("return.type.is.wrong"));
336       return false;
337     }
338
339     for (GrTableParameterInfo info : myParameterModel.getParameterInfos()) {
340       if (!StringUtil.isJavaIdentifier(info.getName())) {
341         showErrorHint(message("name.is.wrong", info.getName()));
342         return false;
343       }
344       if (!checkType(info.getTypeFragment())) {
345         showErrorHint(message("type.for.parameter.is.incorrect", info.getName()));
346         return false;
347       }
348       String defaultValue = info.getDefaultValue();
349       if (info.getOldIndex() < 0 && (defaultValue == null || defaultValue.trim().length() == 0)) {
350         showErrorHint(message("specify.default.value", info.getName()));
351         return false;
352       }
353     }
354
355     ThrownExceptionInfo[] exceptionInfos = myExceptionTableModel.getThrownExceptions();
356     PsiTypeCodeFragment[] typeCodeFragments = myExceptionTableModel.getTypeCodeFragments();
357     for (int i = 0; i < exceptionInfos.length; i++) {
358       ThrownExceptionInfo exceptionInfo = exceptionInfos[i];
359       PsiTypeCodeFragment typeCodeFragment = typeCodeFragments[i];
360       try {
361         PsiType type = typeCodeFragment.getType();
362         if (!(type instanceof PsiClassType)) {
363           showErrorHint(GroovyRefactoringBundle.message("changeSignature.wrong.type.for.exception", typeCodeFragment.getText()));
364           return false;
365         }
366
367         PsiClassType throwable = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory()
368           .createTypeByFQClassName("java.lang.Throwable", type.getResolveScope());
369         if (!throwable.isAssignableFrom(type)) {
370           showErrorHint(GroovyRefactoringBundle.message("changeSignature.not.throwable.type", typeCodeFragment.getText()));
371           return false;
372         }
373         exceptionInfo.setType((PsiClassType)type);
374       }
375       catch (PsiTypeCodeFragment.TypeSyntaxException e) {
376         showErrorHint(GroovyRefactoringBundle.message("changeSignature.wrong.type.for.exception", typeCodeFragment.getText()));
377         return false;
378       }
379       catch (PsiTypeCodeFragment.NoTypeException e) {
380         showErrorHint(GroovyRefactoringBundle.message("changeSignature.no.type.for.exception"));
381         return false;
382       }
383     }
384
385     return true;
386   }
387
388   private static boolean checkType(PsiTypeCodeFragment typeCodeFragment) {
389     try {
390       typeCodeFragment.getType();
391     }
392     catch (PsiTypeCodeFragment.TypeSyntaxException e) {
393       return false;
394     }
395     catch (PsiTypeCodeFragment.NoTypeException e) {
396       return true; //Groovy accepts methods and parameters without explicit type
397     }
398     return true;
399   }
400
401 }