GrChangeSignature parameterTableModel
[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.JavaPsiFacade;
22 import com.intellij.psi.PsiDocumentManager;
23 import com.intellij.psi.PsiType;
24 import com.intellij.psi.PsiTypeCodeFragment;
25 import com.intellij.refactoring.HelpID;
26 import com.intellij.refactoring.ui.CodeFragmentTableCellEditor;
27 import com.intellij.refactoring.ui.CodeFragmentTableCellRenderer;
28 import com.intellij.refactoring.ui.RefactoringDialog;
29 import com.intellij.refactoring.util.CanonicalTypes;
30 import com.intellij.refactoring.util.CommonRefactoringUtil;
31 import com.intellij.ui.EditorTextField;
32 import com.intellij.ui.TableUtil;
33 import com.intellij.util.ui.Table;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
36 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
37 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
39 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
40 import org.jetbrains.plugins.groovy.refactoring.ui.GrCodeFragmentTableCellEditor;
41 import org.jetbrains.plugins.groovy.refactoring.ui.GrCodeFragmentTableCellRenderer;
42
43 import javax.swing.*;
44 import javax.swing.event.TableModelEvent;
45 import javax.swing.event.TableModelListener;
46 import java.awt.event.ActionEvent;
47 import java.awt.event.ActionListener;
48 import java.util.List;
49
50 /**
51  * @author Maxim.Medvedev
52  */
53 public class GrChangeSignatureDialog extends RefactoringDialog {
54   private EditorTextField myNameField;
55   private EditorTextField myReturnTypeField;
56   private JRadioButton myPublicRadioButton;
57   private JRadioButton myProtectedRadioButton;
58   private JRadioButton myPrivateRadioButton;
59   private JPanel myParametersPanel;
60   private Table myParameterTable;
61   private JButton myAddButton;
62   private JButton myRemoveButton;
63   private JButton myMoveUpButton;
64   private JButton myMoveDownButton;
65   private JPanel contentPane;
66   private JLabel mySignatureLabel;
67   private GrParameterTableModel myParameterModel;
68   private GrMethod myMethod;
69 //  private Project myProject;
70   private PsiTypeCodeFragment myReturnTypeCodeFragment;
71   private GroovyCodeFragment myNameCodeFragment;
72
73   public GrChangeSignatureDialog(@NotNull Project project, GrMethod method) {
74     super(project, true);
75     myMethod = method;
76     init();
77     configureParameterButtons();
78     updateSignature();
79   }
80
81   private void configureParameterButtons() {
82     myAddButton.addActionListener(new ActionListener() {
83       public void actionPerformed(ActionEvent e) {
84         int selectedColumn = myParameterTable.getSelectedColumn();
85         myParameterModel.addRow();
86         myParameterTable.setRowSelectionInterval(myParameterModel.getRowCount() - 1, myParameterModel.getRowCount() - 1);
87         myParameterTable.setColumnSelectionInterval(selectedColumn, selectedColumn);
88       }
89     });
90     myRemoveButton.addActionListener(new ActionListener() {
91       public void actionPerformed(ActionEvent e) {
92         int selectedRow = myParameterTable.getSelectedRow();
93         int selectedColumn = myParameterTable.getSelectedColumn();
94
95         myParameterModel.removeRow(myParameterTable.getSelectedRow());
96
97         if (selectedRow == myParameterModel.getRowCount()) selectedRow--;
98         if (myParameterModel.getRowCount() == 0) return;
99         myParameterTable.setRowSelectionInterval(selectedRow, selectedRow);
100         myParameterTable.setColumnSelectionInterval(selectedColumn, selectedColumn);
101       }
102     });
103
104     myMoveUpButton.addActionListener(new ActionListener() {
105       public void actionPerformed(ActionEvent e) {
106         final int selectedRow = myParameterTable.getSelectedRow();
107         int selectedColumn = myParameterTable.getSelectedColumn();
108         myParameterModel.exchangeRows(selectedRow, selectedRow - 1);
109         myParameterTable.setRowSelectionInterval(selectedRow - 1, selectedRow - 1);
110         myParameterTable.setColumnSelectionInterval(selectedColumn, selectedColumn);
111       }
112     });
113
114     myMoveDownButton.addActionListener(new ActionListener() {
115       public void actionPerformed(ActionEvent e) {
116         final int selectedRow = myParameterTable.getSelectedRow();
117         int selectedColumn = myParameterTable.getSelectedColumn();
118         myParameterModel.exchangeRows(selectedRow, selectedRow + 1);
119         myParameterTable.setRowSelectionInterval(selectedRow + 1, selectedRow + 1);
120         myParameterTable.setColumnSelectionInterval(selectedColumn, selectedColumn);
121       }
122     });
123   }
124
125   protected void init() {
126     super.init();
127   }
128
129   private void stopEditing() {
130     TableUtil.stopEditing(myParameterTable);
131   }
132
133   @Override
134   protected JComponent createCenterPanel() {
135     return contentPane;
136   }
137
138
139   private void createUIComponents() {
140     createNameAndReturnTypeEditors();
141     createParametersModel();
142   }
143
144   private void createNameAndReturnTypeEditors() {
145     myNameCodeFragment = new GroovyCodeFragment(myProject, "");
146     myNameField = new EditorTextField(PsiDocumentManager.getInstance(myProject).getDocument(myNameCodeFragment), myProject,
147                                       myNameCodeFragment.getFileType());
148
149     myReturnTypeCodeFragment = JavaPsiFacade.getInstance(myProject).getElementFactory().createTypeCodeFragment("", myMethod, true, true);
150     final Document document = PsiDocumentManager.getInstance(myProject).getDocument(myReturnTypeCodeFragment);
151     myReturnTypeField = new EditorTextField(document, myProject, myReturnTypeCodeFragment.getFileType());
152
153     myNameField.setText(myMethod.getName());
154     final GrTypeElement element = myMethod.getReturnTypeElementGroovy();
155     if (element != null) {
156       myReturnTypeField.setText(element.getText());
157     }
158   }
159
160   private void createParametersModel() {
161     myParameterModel = new GrParameterTableModel(myMethod, this, myProject);
162     myParameterTable = new Table(myParameterModel);
163     myParameterTable.setCellSelectionEnabled(true);
164
165     myParameterTable.getColumnModel().getColumn(0).setCellRenderer(new CodeFragmentTableCellRenderer(myProject));
166     myParameterTable.getColumnModel().getColumn(1).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
167     myParameterTable.getColumnModel().getColumn(2).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
168     myParameterTable.getColumnModel().getColumn(3).setCellRenderer(new GrCodeFragmentTableCellRenderer(myProject));
169
170     myParameterTable.getColumnModel().getColumn(0).setCellEditor(new CodeFragmentTableCellEditor(myProject));
171     myParameterTable.getColumnModel().getColumn(1).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
172     myParameterTable.getColumnModel().getColumn(2).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
173     myParameterTable.getColumnModel().getColumn(3).setCellEditor(new GrCodeFragmentTableCellEditor(myProject));
174
175     if (myParameterTable.getRowCount() > 0) {
176       myParameterTable.setRowSelectionInterval(0, 0);
177       myParameterTable.setColumnSelectionInterval(0, 0);
178     }
179
180     myParameterModel.addTableModelListener(new TableModelListener() {
181       public void tableChanged(TableModelEvent e) {
182         updateSignature();
183       }
184     });
185
186   }
187
188   private void updateSignature() {
189     final int selectedRow = myParameterTable.getSelectedRow();
190     final int rowCount = myParameterModel.getRowCount();
191     myMoveUpButton.setEnabled(selectedRow > 0);
192     myMoveDownButton.setEnabled(selectedRow + 1 < rowCount && rowCount > 1);
193     myRemoveButton.setEnabled(rowCount > 0);
194
195     mySignatureLabel.setText(generateSignatureText());
196   }
197
198   private String generateSignatureText() {
199     String name = getNewName();
200     String type = myReturnTypeField.getText().trim();
201
202     StringBuilder builder = new StringBuilder();
203     if (myPublicRadioButton.isSelected() && type.length() == 0) {
204       builder.append(GrModifier.DEF);
205     }
206     if (myPrivateRadioButton.isSelected()) {
207       builder.append(GrModifier.PRIVATE).append(' ');
208     }
209     else if (myProtectedRadioButton.isSelected()) {
210       builder.append(GrModifier.PROTECTED).append(' ');
211     }
212     builder.append(type).append(' ');
213     builder.append(name).append('(');
214     final List<GrParameterInfo> infos = myParameterModel.getParameterInfos();
215     for (int i = 0, infosSize = infos.size() - 1; i < infosSize; i++) {
216       generateParameterText(infos.get(i), builder);
217       builder.append(", ");
218     }
219     if (infos.size() > 0) {
220       generateParameterText(infos.get(infos.size() - 1), builder);
221     }
222
223     builder.append(')');
224     return builder.toString();
225   }
226
227
228   private static void generateParameterText(GrParameterInfo info, StringBuilder builder) {
229     final PsiTypeCodeFragment typeFragment = info.getTypeFragment();
230     builder.append(typeFragment != null ? typeFragment.getText().trim() : GrModifier.DEF).append(' ');
231     final GroovyCodeFragment nameFragment = info.getNameFragment();
232     builder.append(nameFragment != null ? nameFragment.getText().trim() : "");
233     final GroovyCodeFragment defaultInitializer = info.getDefaultInitializer();
234
235     final String defaultInitializerText = defaultInitializer != null ? defaultInitializer.getText().trim() : "";
236     if (defaultInitializerText.length() > 0) {
237       builder.append(" = ").append(defaultInitializerText);
238     }
239   }
240
241   @Override
242   protected void doAction() {
243     if (!validateInputData()) {
244       return;
245     }
246
247     stopEditing();
248     String modifier = "";
249     if (myPublicRadioButton.isSelected()) {
250       modifier = GrModifier.PUBLIC;
251     }
252     else if (myPrivateRadioButton.isSelected()) {
253       modifier = GrModifier.PRIVATE;
254     }
255     else if (myProtectedRadioButton.isSelected()) {
256       modifier = GrModifier.PROTECTED;
257     }
258
259     PsiType returnType = null;
260     try {
261       returnType = myReturnTypeCodeFragment.getType();
262     }
263     catch (PsiTypeCodeFragment.TypeSyntaxException e) {
264       e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
265     }
266     catch (PsiTypeCodeFragment.NoTypeException e) {
267       returnType = PsiType.NULL;
268     }
269
270     String newName = getNewName();
271     final List<GrParameterInfo> parameterInfos = myParameterModel.getParameterInfos();
272     invokeRefactoring(new GrChangeSignatureProcessor(myProject, new GrChangeSignatureProcessor.GrChangeInfoImpl(myMethod, modifier,
273                                                                                                                 CanonicalTypes.createTypeWrapper(
274                                                                                                                   returnType), newName,
275                                                                                                                 parameterInfos)));
276
277   }
278
279   private String getNewName() {
280     return myNameField.getText().trim();
281   }
282
283   private boolean validateInputData() {
284     if (!checkName()) {
285       CommonRefactoringUtil.showErrorHint(myProject, null, "Name is wrong", "Incorrect data", HelpID.CHANGE_SIGNATURE);
286       return false;
287     }
288
289     if (!checkType(myReturnTypeCodeFragment)) {
290       CommonRefactoringUtil.showErrorHint(myProject, null, "Return type is wrong", "Incorrect data", HelpID.CHANGE_SIGNATURE);
291     }
292
293     for (GrParameterInfo info : myParameterModel.getParameterInfos()) {
294       if (!checkType(info.getTypeFragment())) {
295         CommonRefactoringUtil
296           .showErrorHint(myProject, null, "Type for parameter " + info.getName() + " is wrong", "Incorrect data", HelpID.CHANGE_SIGNATURE);
297         return false;
298       }
299     }
300     return true;
301   }
302
303   private boolean checkType(PsiTypeCodeFragment typeCodeFragment) {
304     try {
305       typeCodeFragment.getType();
306     }
307     catch (PsiTypeCodeFragment.TypeSyntaxException e) {
308       return false;
309     }
310     catch (PsiTypeCodeFragment.NoTypeException e) {
311       return true; //Groovy accepts methods and parameters without explicit type
312     }
313     return true;
314   }
315
316   private boolean checkName() {
317     final String newName = getNewName();
318     if (StringUtil.isJavaIdentifier(newName)) return true;
319     try {
320       GroovyPsiElementFactory.getInstance(myProject).createMethodFromText("def " + newName + "(){}");
321     }
322     catch (Throwable e) {
323       return false;
324     }
325     return true;
326   }
327 }