IDEA-51893: Quick Fix "​Create Field" when using Groovy named parameters
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / annotator / intentions / dynamic / ui / DynamicDialog.java
1 /*
2  * Copyright 2000-2009 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.annotator.intentions.dynamic.ui;
17
18 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
19 import com.intellij.openapi.command.CommandProcessor;
20 import com.intellij.openapi.command.undo.*;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.event.DocumentEvent;
24 import com.intellij.openapi.editor.event.DocumentListener;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.ui.DialogWrapper;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.IconLoader;
29 import com.intellij.psi.*;
30 import com.intellij.psi.search.GlobalSearchScope;
31 import com.intellij.psi.search.ProjectScope;
32 import com.intellij.ui.EditorComboBoxEditor;
33 import com.intellij.ui.EditorTextField;
34 import com.intellij.util.IncorrectOperationException;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.plugins.groovy.GroovyBundle;
37 import org.jetbrains.plugins.groovy.GroovyFileType;
38 import org.jetbrains.plugins.groovy.annotator.intentions.QuickfixUtil;
39 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.DynamicManager;
40 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.MyPair;
41 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.elements.DClassElement;
42 import org.jetbrains.plugins.groovy.annotator.intentions.dynamic.elements.DItemElement;
43 import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
44 import org.jetbrains.plugins.groovy.debugger.fragments.GroovyCodeFragment;
45 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
46 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
47 import org.jetbrains.plugins.groovy.lang.psi.expectedTypes.TypeConstraint;
48 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
49 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
50
51 import javax.swing.*;
52 import javax.swing.border.Border;
53 import javax.swing.event.EventListenerList;
54 import java.awt.*;
55 import java.awt.event.*;
56 import java.util.EventListener;
57 import java.util.List;
58
59 /**
60  * User: Dmitry.Krasilschikov
61  * Date: 18.12.2007
62  */
63 public abstract class DynamicDialog extends DialogWrapper {
64   private JComboBox myClassComboBox;
65   private JPanel myPanel;
66   private JComboBox myTypeComboBox;
67   private JLabel myClassLabel;
68   private JLabel myTypeLabel;
69   private JPanel myTypeStatusPanel;
70   private JLabel myTypeStatusLabel;
71   private JTable myParametersTable;
72   private JLabel myTableLabel;
73   private JCheckBox myStaticCheckBox;
74   private final DynamicManager myDynamicManager;
75   private final Project myProject;
76   private final EventListenerList myListenerList = new EventListenerList();
77   private final PsiElement myContext;
78   private final DynamicElementSettings mySettings;
79
80   private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.annotator.intentions.dynamic.ui.DynamicDialog");
81
82   public DynamicDialog(PsiElement context, DynamicElementSettings settings, TypeConstraint[] typeConstraints) {
83     super(context.getProject(), true);
84     myProject = context.getProject();
85
86     if (!isTableVisible()) {
87       myParametersTable.setVisible(false);
88       myTableLabel.setVisible(false);
89     }
90     myContext = context;
91     setTitle(GroovyInspectionBundle.message("dynamic.element"));
92     myDynamicManager = DynamicManager.getInstance(myProject);
93
94     init();
95
96     mySettings = settings;
97
98     setUpTypeComboBox(typeConstraints);
99     setUpContainingClassComboBox();
100     setUpStatusLabel();
101     setUpStaticComboBox();
102
103     myTableLabel.setLabelFor(myParametersTable);
104     setUpTableNameLabel(GroovyBundle.message("dynamic.properties.table.name"));
105
106     final Border border2 = BorderFactory.createLineBorder(Color.BLACK);
107     myParametersTable.setBorder(border2);
108     myParametersTable.setBackground(Color.WHITE);
109
110     myTypeLabel.setLabelFor(myTypeComboBox);
111     myClassLabel.setLabelFor(myClassComboBox);
112   }
113
114   private void setUpStaticComboBox() {
115     myStaticCheckBox.setMnemonic(KeyEvent.VK_S);
116     myStaticCheckBox.setSelected(mySettings.isStatic());
117   }
118
119   public DynamicElementSettings getSettings() {
120     return mySettings;
121   }
122
123   protected void setUpTableNameLabel(String text) {
124     myTableLabel.setText(text);
125   }
126
127   private void setUpStatusLabel() {
128     if (!isTypeCheckerPanelEnable()) {
129       myTypeStatusPanel.setVisible(false);
130       return;
131     }
132     myTypeStatusLabel.setHorizontalTextPosition(SwingConstants.RIGHT);
133
134     final GrTypeElement typeElement = getEnteredTypeName();
135     if (typeElement == null) {
136       setStatusTextAndIcon(IconLoader.getIcon("/compiler/warning.png"), GroovyInspectionBundle.message("no.type.specified"));
137       return;
138     }
139
140     final PsiType type = typeElement.getType();
141     if (type instanceof PsiClassType && ((PsiClassType)type).resolve() == null) {
142       setStatusTextAndIcon(IconLoader.getIcon("/compiler/warning.png"),
143                            GroovyInspectionBundle.message("unresolved.type.status", type.getPresentableText()));
144       return;
145     }
146     setStatusTextAndIcon(null, "");
147   }
148
149   private void setStatusTextAndIcon(final Icon icon, final String text) {
150     myTypeStatusLabel.setIcon(icon);
151     myTypeStatusLabel.setText(text);
152     pack();
153   }
154
155   @Nullable
156   public PsiClass getTargetClass() {
157     return JavaPsiFacade.getInstance(myProject).findClass(mySettings.getContainingClassName(), GlobalSearchScope.allScope(myProject));
158   }
159
160   private void setUpContainingClassComboBox() {
161     PsiClass targetClass = getTargetClass();
162     if (targetClass == null || targetClass instanceof SyntheticElement) {
163       try {
164         final GrTypeElement typeElement = GroovyPsiElementFactory.getInstance(myProject).createTypeElement("java.lang.Object");
165         if (typeElement == null) return;
166
167         final PsiType type = typeElement.getType();
168
169         if (!(type instanceof PsiClassType)) LOG.error("Type java.lang.Object doesn't resolve");
170         targetClass = ((PsiClassType) type).resolve();
171       } catch (IncorrectOperationException e) {
172         LOG.error(e);
173       }
174     }
175
176     if (targetClass == null) return;
177
178     for (PsiClass aClass : PsiUtil.iterateSupers(targetClass, true)) {
179       myClassComboBox.addItem(new ContainingClassItem(aClass));
180     }
181
182     myPanel.registerKeyboardAction(new ActionListener() {
183       public void actionPerformed(ActionEvent e) {
184         myClassComboBox.requestFocus();
185       }
186     }, KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
187   }
188
189   @Nullable
190   private Document createDocument(final String text) {
191     GroovyCodeFragment fragment = new GroovyCodeFragment(myProject, text);
192     fragment.setContext(myContext);
193     return PsiDocumentManager.getInstance(myProject).getDocument(fragment);
194   }
195
196   private void setUpTypeComboBox(TypeConstraint[] typeConstraints) {
197     final EditorComboBoxEditor comboEditor = new EditorComboBoxEditor(myProject, GroovyFileType.GROOVY_FILE_TYPE);
198
199     final Document document = createDocument("");
200     assert document != null;
201
202     comboEditor.setItem(document);
203
204     myTypeComboBox.setEditor(comboEditor);
205     myTypeComboBox.setEditable(true);
206     myTypeComboBox.grabFocus();
207
208     addDataChangeListener();
209
210     myTypeComboBox.addItemListener(
211         new ItemListener() {
212           public void itemStateChanged(ItemEvent e) {
213             fireDataChanged();
214           }
215         }
216     );
217
218     myPanel.registerKeyboardAction(new ActionListener() {
219       public void actionPerformed(ActionEvent e) {
220         myTypeComboBox.requestFocus();
221       }
222     }, KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
223
224
225     final EditorTextField editorTextField = (EditorTextField) myTypeComboBox.getEditor().getEditorComponent();
226
227     editorTextField.addDocumentListener(new DocumentListener() {
228       public void beforeDocumentChange(DocumentEvent event) {
229       }
230
231       public void documentChanged(DocumentEvent event) {
232         fireDataChanged();
233       }
234     });
235
236     PsiType type = typeConstraints.length == 1 ? typeConstraints[0].getDefaultType() : TypesUtil.getJavaLangObject(myContext);
237     if (type == null) {
238       type = TypesUtil.getJavaLangObject(myContext);
239     }
240     myTypeComboBox.getEditor().setItem(createDocument(type.getCanonicalText()));
241   }
242
243   protected void addDataChangeListener() {
244     myListenerList.add(DataChangedListener.class, new DataChangedListener());
245   }
246
247   class DataChangedListener implements EventListener {
248     void dataChanged() {
249       updateOkStatus();
250     }
251   }
252
253   protected void updateOkStatus() {
254     GrTypeElement typeElement = getEnteredTypeName();
255     if (typeElement == null) {
256       setOKActionEnabled(false);
257     } else {
258       setOKActionEnabled(true);
259     }
260
261     setUpStatusLabel();
262   }
263
264   @Nullable
265   public GrTypeElement getEnteredTypeName() {
266     final Document typeEditorDocument = getTypeEditorDocument();
267
268     if (typeEditorDocument == null) return null;
269
270     try {
271       final String typeText = typeEditorDocument.getText();
272
273       return GroovyPsiElementFactory.getInstance(myProject).createTypeElement(typeText);
274     } catch (IncorrectOperationException e) {
275       return null;
276     }
277   }
278
279   @Nullable
280   public Document getTypeEditorDocument() {
281     final Object item = myTypeComboBox.getEditor().getItem();
282
283     return item instanceof Document ? (Document) item : null;
284
285   }
286
287   @Nullable
288   public ContainingClassItem getEnteredContainingClass() {
289     final Object item = myClassComboBox.getSelectedItem();
290     if (!(item instanceof ContainingClassItem)) return null;
291
292     return ((ContainingClassItem) item);
293   }
294
295   protected void fireDataChanged() {
296     Object[] list = myListenerList.getListenerList();
297     for (Object aList : list) {
298       if (aList instanceof DataChangedListener) {
299         ((DataChangedListener) aList).dataChanged();
300       }
301     }
302   }
303
304   @Nullable
305   protected JComponent createCenterPanel() {
306     return myPanel;
307   }
308
309   protected void doOKAction() {
310     super.doOKAction();
311
312     mySettings.setContainingClassName(getEnteredContainingClass().getContainingClass().getQualifiedName());
313     mySettings.setStatic(myStaticCheckBox.isSelected());
314     GrTypeElement typeElement = getEnteredTypeName();
315
316     if (typeElement == null) {
317       mySettings.setType("java.lang.Object");
318     } else {
319       PsiType type = typeElement.getType();
320       if (type instanceof PsiPrimitiveType) {
321         type = TypesUtil.boxPrimitiveType(type, typeElement.getManager(), ProjectScope.getAllScope(myProject));
322       }
323
324       final String typeQualifiedName = type.getCanonicalText();
325
326       if (typeQualifiedName != null) {
327         mySettings.setType(typeQualifiedName);
328       } else {
329         mySettings.setType(type.getPresentableText());
330       }
331     }
332
333     Document document = PsiDocumentManager.getInstance(myProject).getDocument(myContext.getContainingFile());
334     final DocumentReference[] refs = new DocumentReference[]{DocumentReferenceManager.getInstance().create(document)};
335
336     CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
337       public void run() {
338         UndoManager.getInstance(myProject).undoableActionPerformed(new UndoableAction() {
339           public void undo() throws UnexpectedUndoException {
340
341             final DItemElement itemElement;
342             if (mySettings.isMethod()) {
343               final List<MyPair> myPairList = mySettings.getPairs();
344               final String[] argumentsTypes = QuickfixUtil.getArgumentsTypes(myPairList);
345               itemElement = myDynamicManager.findConcreteDynamicMethod(mySettings.getContainingClassName(), mySettings.getName(), argumentsTypes);
346             } else {
347               itemElement = myDynamicManager.findConcreteDynamicProperty(mySettings.getContainingClassName(), mySettings.getName());
348             }
349
350             if (itemElement == null) {
351               Messages.showWarningDialog(myProject, GroovyInspectionBundle.message("Cannot.perform.undo.operation"), GroovyInspectionBundle.message("Undo.disable"));
352               return;
353             }
354             final DClassElement classElement = myDynamicManager.getClassElementByItem(itemElement);
355
356             if (classElement == null) {
357               Messages.showWarningDialog(myProject, GroovyInspectionBundle.message("Cannot.perform.undo.operation"), GroovyInspectionBundle.message("Undo.disable"));
358               return;
359             }
360
361             removeElement(itemElement);
362
363             if (classElement.getMethods().size() == 0 && classElement.getProperties().size() == 0) {
364               myDynamicManager.removeClassElement(classElement);
365             }
366           }
367
368           public void redo() throws UnexpectedUndoException {
369             addElement(mySettings);
370           }
371
372           public DocumentReference[] getAffectedDocuments() {
373             return refs;
374           }
375
376           public boolean isGlobal() {
377             return true;
378           }
379         });
380
381         addElement(mySettings);
382       }
383     }, "Add dynamic element", null);
384   }
385
386   private void removeElement(DItemElement itemElement) {
387     myDynamicManager.removeItemElement(itemElement);
388     myDynamicManager.fireChange();
389   }
390
391   public void addElement(final DynamicElementSettings settings) {
392     if (settings.isMethod()) {
393       myDynamicManager.addMethod(settings);
394     } else {
395       myDynamicManager.addProperty(settings);
396     }
397
398     myDynamicManager.fireChange();
399   }
400
401   static class ContainingClassItem {
402     private final PsiClass myContainingClass;
403
404     ContainingClassItem(PsiClass containingClass) {
405       myContainingClass = containingClass;
406     }
407
408     public String toString() {
409       return myContainingClass.getName();
410     }
411
412     public PsiClass getContainingClass() {
413       return myContainingClass;
414     }
415   }
416
417   public void doCancelAction() {
418     super.doCancelAction();
419
420     DaemonCodeAnalyzer.getInstance(myProject).restart();
421   }
422
423   public JComponent getPreferredFocusedComponent() {
424     return myTypeComboBox;
425   }
426
427 protected boolean isTableVisible() {
428     return false;
429   }
430
431   public JTable getParametersTable() {
432     return myParametersTable;
433   }
434
435   protected static boolean isTypeCheckerPanelEnable() {
436     return true;
437   }
438
439   public Project getProject() {
440     return myProject;
441   }
442
443   protected void setUpTypeLabel(String typeLabelText) {
444     myTypeLabel.setText(typeLabelText);
445   }
446 }