Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / introduce / constant / GrIntroduceConstantDialog.java
1 /*
2  * Copyright 2000-2010 JetBrains s.r.o.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  * http://www.apache.org/licenses/LICENSE-2.0
7  * Unless required by applicable law or agreed to in writing, software
8  * distributed under the License is distributed on an "AS IS" BASIS,
9  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10  * See the License for the specific language governing permissions and
11  * limitations under the License.
12  */
13 package org.jetbrains.plugins.groovy.refactoring.introduce.constant;
14
15 import com.intellij.ide.util.*;
16 import com.intellij.openapi.diagnostic.Logger;
17 import com.intellij.openapi.editor.event.*;
18 import com.intellij.openapi.editor.event.DocumentAdapter;
19 import com.intellij.openapi.module.Module;
20 import com.intellij.openapi.module.ModuleUtil;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.ComboBox;
23 import com.intellij.openapi.ui.DialogWrapper;
24 import com.intellij.openapi.ui.Messages;
25 import com.intellij.openapi.util.Comparing;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.psi.*;
28 import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
29 import com.intellij.psi.search.GlobalSearchScope;
30 import com.intellij.refactoring.JavaRefactoringSettings;
31 import com.intellij.refactoring.RefactoringBundle;
32 import com.intellij.refactoring.introduceField.IntroduceConstantHandler;
33 import com.intellij.refactoring.ui.JavaVisibilityPanel;
34 import com.intellij.refactoring.util.CommonRefactoringUtil;
35 import com.intellij.refactoring.util.RefactoringMessageUtil;
36 import com.intellij.ui.*;
37 import com.intellij.util.IncorrectOperationException;
38 import com.intellij.util.ui.UIUtil;
39 import gnu.trove.THashSet;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.plugins.groovy.GroovyFileType;
44 import org.jetbrains.plugins.groovy.actions.GroovyTemplatesFactory;
45 import org.jetbrains.plugins.groovy.actions.NewGroovyActionBase;
46 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
47 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
48 import org.jetbrains.plugins.groovy.refactoring.GroovyNameSuggestionUtil;
49 import org.jetbrains.plugins.groovy.refactoring.GroovyNamesUtil;
50 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
51 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
52 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext;
53 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceDialog;
54 import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
55
56 import javax.swing.*;
57 import java.awt.*;
58 import java.awt.event.*;
59 import java.util.*;
60
61 /**
62  * @author Maxim.Medvedev
63  */
64 public class GrIntroduceConstantDialog extends DialogWrapper
65   implements GrIntroduceConstantSettings, GrIntroduceDialog<GrIntroduceConstantSettings> {
66
67   private static final Logger LOG  = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.constant.GrIntroduceConstantDialog");
68
69   private final GrIntroduceContext myContext;
70   private final GrIntroduceHandlerBase.Validator myValidator;
71   private JLabel myNameLabel;
72   private JCheckBox myReplaceAllOccurences;
73   private JPanel myPanel;
74   private JComboBox myTypeCombo;
75   private ReferenceEditorComboWithBrowseButton myTargetClassEditor;
76   private ComboBox myNameComboBox;
77   private JavaVisibilityPanel myJavaVisibilityPanel;
78   private JPanel myTargetClassPanel;
79   private JLabel myTargetClassLabel;
80   private JCheckBox mySpecifyType;
81   private Map<String, PsiType> myTypes = null;
82   @Nullable private PsiClass myTargetClass;
83   @Nullable private PsiClass myDefaultTargetClass;
84
85   public GrIntroduceConstantDialog(GrIntroduceContext context,
86                                    GrIntroduceHandlerBase.Validator validator,
87                                    @Nullable PsiClass defaultTargetClass) {
88     super(context.project);
89     myContext = context;
90     myValidator = validator;
91     myTargetClass = defaultTargetClass;
92     myDefaultTargetClass = defaultTargetClass;
93
94     setTitle(GrIntroduceConstantHandler.REFACTORING_NAME);
95 //    myVPanel = new JavaVisibilityPanel(false, true);
96 //    myVisibilityPanel.add(myVPanel, BorderLayout.CENTER);
97     init();
98
99     myJavaVisibilityPanel.setVisibility(JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY);
100     //myIntroduceEnumConstantCb.setEnabled(EnumConstantsUtil.isSuitableForEnumConstant(getSelectedType(), myTargetClass));
101
102     mySpecifyType.addActionListener(new ActionListener() {
103       @Override
104       public void actionPerformed(ActionEvent e) {
105         myTypeCombo.setEnabled(mySpecifyType.isSelected());
106       }
107     });
108     mySpecifyType.setSelected(context.expression.getType() != null);
109     myTypeCombo.setEnabled(mySpecifyType.isSelected());
110     updateVisibilityPanel();
111     updateOkStatus();
112
113     myNameComboBox.requestFocus();
114   }
115
116   @Override
117   protected JComponent createCenterPanel() {
118     return null;
119   }
120
121   @Override
122   protected JComponent createNorthPanel() {
123     initializeTypeCombo();
124     initializeName();
125     initializeTargetClassEditor();
126
127     if (myContext.occurrences.length < 2) {
128       myReplaceAllOccurences.setVisible(false);
129     }
130     return myPanel;
131   }
132
133   private void initializeTargetClassEditor() {
134
135     myTargetClassEditor =
136       new ReferenceEditorComboWithBrowseButton(new ActionListener() {
137         @Override
138         public void actionPerformed(ActionEvent e) {
139           TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myContext.project)
140             .createWithInnerClassesScopeChooser(RefactoringBundle.message("choose.destination.class"),
141                                                 GlobalSearchScope.projectScope(myContext.project), new ClassFilter() {
142                 public boolean isAccepted(PsiClass aClass) {
143                   return aClass.getParent() instanceof GroovyFile || aClass.hasModifierProperty(PsiModifier.STATIC);
144                 }
145               }, null);
146           if (myTargetClass != null) {
147             chooser.selectDirectory(myTargetClass.getContainingFile().getContainingDirectory());
148           }
149           chooser.showDialog();
150           PsiClass aClass = chooser.getSelected();
151           if (aClass != null) {
152             myTargetClassEditor.setText(aClass.getQualifiedName());
153           }
154
155         }
156       }, "", PsiManager.getInstance(myContext.project), true, RECENTS_KEY);
157     myTargetClassPanel.setLayout(new BorderLayout());
158     myTargetClassPanel.add(myTargetClassLabel, BorderLayout.NORTH);
159     myTargetClassPanel.add(myTargetClassEditor, BorderLayout.CENTER);
160     Set<String> possibleClassNames = new LinkedHashSet<String>();
161     for (final PsiElement occurrence : myContext.occurrences) {
162       final PsiClass parentClass = GrIntroduceConstantHandler.getParentClass(occurrence);
163       if (parentClass != null && parentClass.getQualifiedName() != null) {
164         possibleClassNames.add(parentClass.getQualifiedName());
165       }
166     }
167
168     for (String possibleClassName : possibleClassNames) {
169       myTargetClassEditor.prependItem(possibleClassName);
170     }
171
172     if (myDefaultTargetClass != null) {
173       myTargetClassEditor.prependItem(myDefaultTargetClass.getQualifiedName());
174     }
175
176     myTargetClassEditor.getChildComponent().addDocumentListener(new DocumentAdapter() {
177       public void documentChanged(DocumentEvent e) {
178         targetClassChanged();
179         updateOkStatus();
180        // enableEnumDependant(introduceEnumConstant());
181       }
182     });
183   }
184
185   private void initializeName() {
186     myNameLabel.setLabelFor(myNameComboBox);
187     final EditorComboBoxEditor comboEditor = new StringComboboxEditor(myContext.project, GroovyFileType.GROOVY_FILE_TYPE, myNameComboBox);
188
189     myNameComboBox.setEditor(comboEditor);
190     myNameComboBox.setRenderer(new EditorComboBoxRenderer(comboEditor));
191
192     myNameComboBox.setEditable(true);
193     myNameComboBox.setMaximumRowCount(8);
194
195
196     myPanel.registerKeyboardAction(new ActionListener() {
197       public void actionPerformed(ActionEvent e) {
198         myNameComboBox.requestFocus();
199       }
200     }, KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
201
202     String[] possibleNames = GroovyNameSuggestionUtil.suggestVariableNames(myContext.expression, myValidator, true);
203     for (String possibleName : possibleNames) {
204       myNameComboBox.addItem(possibleName);
205     }
206
207     ((EditorTextField)myNameComboBox.getEditor().getEditorComponent()).addDocumentListener(new DocumentListener() {
208       public void beforeDocumentChange(DocumentEvent event) {
209       }
210
211       public void documentChanged(DocumentEvent event) {
212         updateOkStatus();
213       }
214     });
215
216     myNameComboBox.addItemListener(
217       new ItemListener() {
218         public void itemStateChanged(ItemEvent e) {
219           updateOkStatus();
220         }
221       }
222     );
223   }
224
225
226   private void initializeTypeCombo() {
227     final PsiType expressionType = myContext.expression.getType();
228     if (expressionType != null) {
229       myTypes = GroovyRefactoringUtil.getCompatibleTypeNames(expressionType);
230       for (String typeName : myTypes.keySet()) {
231         myTypeCombo.addItem(typeName);
232       }
233     }
234     else {
235       myTypeCombo.setEnabled(false);
236     }
237   }
238
239   @Override
240   public String getVisibilityModifier() {
241     return myJavaVisibilityPanel.getVisibility();
242   }
243
244   @Nullable
245   @Override
246   public PsiClass getTargetClass() {
247     return myTargetClass;
248   }
249
250   @NotNull
251   public String getTargetClassName() {
252     return myTargetClassEditor.getText();
253   }
254
255   @Override
256   public GrIntroduceConstantSettings getSettings() {
257     return this;
258   }
259
260   @Nullable
261   @Override
262   public String getName() {
263     if (myNameComboBox.getEditor().getItem() instanceof String && ((String)myNameComboBox.getEditor().getItem()).length() > 0) {
264       return (String)myNameComboBox.getEditor().getItem();
265     }
266     else {
267       return null;
268     }
269   }
270
271   @Override
272   public boolean replaceAllOccurrences() {
273     return myReplaceAllOccurences.isSelected();
274   }
275
276   @Override
277   public PsiType getSelectedType() {
278     if (!myTypeCombo.isEnabled() || myTypeCombo.getItemCount() == 0) {
279       return null;
280     } else {
281       return myTypes.get(myTypeCombo.getSelectedItem());
282     }
283   }
284
285   @NonNls private static final String RECENTS_KEY = "GrIntroduceConstantDialog.RECENTS_KEY";
286
287   private void createUIComponents() {
288     myJavaVisibilityPanel = new JavaVisibilityPanel(false, true);
289   }
290
291   private void targetClassChanged() {
292     final String targetClassName = getTargetClassName();
293     myTargetClass =
294       JavaPsiFacade.getInstance(myContext.project).findClass(targetClassName, GlobalSearchScope.projectScope(myContext.project));
295     updateVisibilityPanel();
296 //    myIntroduceEnumConstantCb.setEnabled(EnumConstantsUtil.isSuitableForEnumConstant(getSelectedType(), myTargetClassEditor));
297   }
298
299   private void updateVisibilityPanel() {
300     if (myTargetClass != null && myTargetClass.isInterface()) {
301       myJavaVisibilityPanel.disableAllButPublic();
302     }
303     else {
304       UIUtil.setEnabled(myJavaVisibilityPanel, true, true);
305       // exclude all modifiers not visible from all occurences
306       final Set<String> visible = new THashSet<String>();
307       visible.add(PsiModifier.PRIVATE);
308       visible.add(PsiModifier.PROTECTED);
309       visible.add(PsiModifier.PACKAGE_LOCAL);
310       visible.add(PsiModifier.PUBLIC);
311       for (PsiElement occurrence : myContext.occurrences) {
312         final PsiManager psiManager = PsiManager.getInstance(myContext.project);
313         for (Iterator<String> iterator = visible.iterator(); iterator.hasNext();) {
314           String modifier = iterator.next();
315
316           try {
317             final String modifierText = PsiModifier.PACKAGE_LOCAL.equals(modifier) ? "" : modifier;
318             final PsiField field = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createFieldFromText(modifierText + " int xxx;", myTargetClass);
319             if (!JavaResolveUtil.isAccessible(field, myTargetClass, field.getModifierList(), occurrence, myTargetClass, null)) {
320               iterator.remove();
321             }
322           }
323           catch (IncorrectOperationException e) {
324             LOG.error(e);
325           }
326         }
327       }
328       if (!visible.contains(getVisibilityModifier())) {
329         if (visible.contains(PsiModifier.PUBLIC)) myJavaVisibilityPanel.setVisibility(PsiModifier.PUBLIC);
330         if (visible.contains(PsiModifier.PACKAGE_LOCAL)) myJavaVisibilityPanel.setVisibility(PsiModifier.PACKAGE_LOCAL);
331         if (visible.contains(PsiModifier.PROTECTED)) myJavaVisibilityPanel.setVisibility(PsiModifier.PROTECTED);
332         if (visible.contains(PsiModifier.PRIVATE)) myJavaVisibilityPanel.setVisibility(PsiModifier.PRIVATE);
333       }
334     }
335   }
336
337   private void updateOkStatus() {
338     String text = getName();
339     if (!GroovyNamesUtil.isIdentifier(text)) {
340       setOKActionEnabled(false);
341       return;
342     }
343
344     final String targetClassName = myTargetClassEditor.getText();
345     if (targetClassName.trim().length() == 0 && myDefaultTargetClass == null) {
346       setOKActionEnabled(false);
347       return;
348     }
349     final String trimmed = targetClassName.trim();
350     if (!JavaPsiFacade.getInstance(myContext.project).getNameHelper().isQualifiedName(trimmed)) {
351       setOKActionEnabled(false);
352       return;
353     }
354     setOKActionEnabled(true);
355   }
356
357   @Override
358   protected void doOKAction() {
359     final String targetClassName = getTargetClassName();
360     PsiClass newClass = myDefaultTargetClass;
361
362     if (myDefaultTargetClass == null ||
363         !"".equals(targetClassName) && !Comparing.strEqual(targetClassName, myDefaultTargetClass.getQualifiedName())) {
364       final Module module = ModuleUtil.findModuleForPsiElement(myContext.expression);
365       newClass = JavaPsiFacade.getInstance(myContext.project).findClass(targetClassName, module.getModuleScope());
366       if (newClass == null) {
367         if (Messages.showOkCancelDialog(myContext.project, GroovyRefactoringBundle.message("class.does.not.exist.in.the.module"),
368                                         IntroduceConstantHandler.REFACTORING_NAME, Messages.getErrorIcon()) != OK_EXIT_CODE) {
369           return;
370         }
371         myTargetClass =
372           getTargetClass(targetClassName, myContext.expression.getContainingFile().getContainingDirectory(), myContext.project, module);
373         if (myTargetClass == null) return;
374       }
375       else {
376         myTargetClass =
377           getTargetClass(targetClassName, myContext.expression.getContainingFile().getContainingDirectory(), myContext.project, module);
378       }
379     }
380
381     final GrIntroduceConstantHandler introduceConstantHandler = new GrIntroduceConstantHandler();
382     String errorString = check();
383     if (errorString != null) {
384       CommonRefactoringUtil.showErrorMessage(introduceConstantHandler.getRefactoringName(),
385                                              RefactoringBundle.getCannotRefactorMessage(errorString), introduceConstantHandler.getHelpID(),
386                                              myContext.project);
387       return;
388     }
389
390     String fieldName = getName();
391     if (newClass != null) {
392       PsiField oldField = newClass.findFieldByName(fieldName, true);
393
394       if (oldField != null) {
395         int answer = Messages.showYesNoDialog(myContext.project, RefactoringBundle
396           .message("field.exists", fieldName, oldField.getContainingClass().getQualifiedName()),
397                                               introduceConstantHandler.getRefactoringName(), Messages.getWarningIcon());
398         if (answer != 0) {
399           return;
400         }
401       }
402     }
403
404     JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY = getVisibilityModifier();
405
406     RecentsManager.getInstance(myContext.project).registerRecentEntry(RECENTS_KEY, targetClassName);
407
408     super.doOKAction();
409   }
410
411   @Nullable
412   private String check() {
413     if (myTargetClass != null && !GroovyFileType.GROOVY_LANGUAGE.equals(myTargetClass.getLanguage())) {
414       return GroovyRefactoringBundle.message("class.language.is.not.groovy");
415     }
416
417     String fieldName = getName();
418     final JavaPsiFacade facade = JavaPsiFacade.getInstance(myContext.project);
419
420     if ("".equals(fieldName)) {
421       return RefactoringBundle.message("no.field.name.specified");
422     }
423
424     else if (!facade.getNameHelper().isIdentifier(fieldName)) {
425       return RefactoringMessageUtil.getIncorrectIdentifierMessage(fieldName);
426     }
427
428     final String targetClassName = getTargetClassName();
429     if (myDefaultTargetClass == null && "".equals(targetClassName)) {
430       return GroovyRefactoringBundle.message("target.class.is.not.specified");
431     }
432
433     if (myTargetClass instanceof GroovyScriptClass) {
434       return GroovyRefactoringBundle.message("target.class.must.not.be.script");
435     }
436
437     return null;
438   }
439
440   @Nullable
441   private static PsiClass getTargetClass(String qualifiedName, PsiDirectory baseDirectory, Project project, Module module) {
442     GlobalSearchScope scope = module.getModuleScope();
443     PsiClass targetClass = JavaPsiFacade.getInstance(project).findClass(qualifiedName, scope);
444     if (targetClass != null) return targetClass;
445
446     final String packageName = StringUtil.getPackageName(qualifiedName);
447     PsiPackage psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName);
448     final PsiDirectory psiDirectory;
449     if (psiPackage != null) {
450       final PsiDirectory[] directories = psiPackage.getDirectories(GlobalSearchScope.allScope(project));
451       psiDirectory = directories.length > 1 ? DirectoryChooserUtil
452         .chooseDirectory(directories, null, project, new HashMap<PsiDirectory, String>()) : directories[0];
453     }
454     else {
455       psiDirectory = PackageUtil.findOrCreateDirectoryForPackage(module, packageName, baseDirectory, false);
456     }
457     if (psiDirectory == null) return null;
458     final String shortName = StringUtil.getShortName(qualifiedName);
459     final String fileName = shortName + NewGroovyActionBase.GROOVY_EXTENSION;
460     final GroovyFile file = (GroovyFile)GroovyTemplatesFactory.createFromTemplate(psiDirectory, shortName, fileName, "GroovyClass.groovy");
461     return file.getTypeDefinitions()[0];
462   }
463
464 }