blob: a6f9673fbe62683ba6159e0e52b0eef4caf876d6 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.refactoring.introduce.constant;
import com.intellij.ide.util.*;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceField.IntroduceConstantHandler;
import com.intellij.refactoring.ui.JavaVisibilityPanel;
import com.intellij.refactoring.ui.NameSuggestionsField;
import com.intellij.ui.RecentsManager;
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.actions.GroovyTemplates;
import org.jetbrains.plugins.groovy.actions.GroovyTemplatesFactory;
import org.jetbrains.plugins.groovy.actions.NewGroovyActionBase;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyNamesUtil;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceContext;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceDialog;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
import org.jetbrains.plugins.groovy.refactoring.introduce.StringPartInfo;
import org.jetbrains.plugins.groovy.refactoring.introduce.field.GrFieldNameSuggester;
import org.jetbrains.plugins.groovy.refactoring.introduce.variable.GroovyVariableValidator;
import org.jetbrains.plugins.groovy.refactoring.ui.GrTypeComboBox;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.*;
import java.util.List;
/**
* @author Maxim.Medvedev
*/
public class GrIntroduceConstantDialog extends DialogWrapper
implements GrIntroduceConstantSettings, GrIntroduceDialog<GrIntroduceConstantSettings> {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.introduce.constant.GrIntroduceConstantDialog");
private final GrIntroduceContext myContext;
private JLabel myNameLabel;
private JCheckBox myReplaceAllOccurrences;
private JPanel myPanel;
private GrTypeComboBox myTypeCombo;
private ReferenceEditorComboWithBrowseButton myTargetClassEditor;
private NameSuggestionsField myNameField;
private JavaVisibilityPanel myJavaVisibilityPanel;
private JPanel myTargetClassPanel;
private JLabel myTargetClassLabel;
@Nullable private PsiClass myTargetClass;
@Nullable private final PsiClass myDefaultTargetClass;
private TargetClassInfo myTargetClassInfo;
public GrIntroduceConstantDialog(GrIntroduceContext context, @Nullable PsiClass defaultTargetClass) {
super(context.getProject());
myContext = context;
myTargetClass = defaultTargetClass;
myDefaultTargetClass = defaultTargetClass;
setTitle(GrIntroduceConstantHandler.REFACTORING_NAME);
myJavaVisibilityPanel.setVisibility(JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY);
updateVisibilityPanel();
updateOkStatus();
init();
}
@Nullable
public static PsiClass getParentClass(PsiElement occurrence) {
PsiElement cur = occurrence;
while (true) {
final PsiClass parentClass = PsiTreeUtil.getParentOfType(cur, PsiClass.class, true);
if (parentClass == null || parentClass.hasModifierProperty(PsiModifier.STATIC)) return parentClass;
cur = parentClass;
}
}
@Override
public JComponent getPreferredFocusedComponent() {
return myNameField;
}
@Override
protected JComponent createCenterPanel() {
return null;
}
@Override
protected JComponent createNorthPanel() {
initializeName();
initializeTargetClassEditor();
if (GrIntroduceHandlerBase.resolveLocalVar(myContext) != null) {
myReplaceAllOccurrences.setEnabled(false);
myReplaceAllOccurrences.setSelected(true);
}
else if (myContext.getOccurrences().length < 2) {
myReplaceAllOccurrences.setVisible(false);
}
return myPanel;
}
private void initializeTargetClassEditor() {
myTargetClassEditor =
new ReferenceEditorComboWithBrowseButton(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myContext.getProject())
.createWithInnerClassesScopeChooser(RefactoringBundle.message("choose.destination.class"),
GlobalSearchScope.projectScope(myContext.getProject()), new ClassFilter() {
@Override
public boolean isAccepted(PsiClass aClass) {
return aClass.getParent() instanceof GroovyFile || aClass.hasModifierProperty(PsiModifier.STATIC);
}
}, null);
if (myTargetClass != null) {
chooser.selectDirectory(myTargetClass.getContainingFile().getContainingDirectory());
}
chooser.showDialog();
PsiClass aClass = chooser.getSelected();
if (aClass != null) {
myTargetClassEditor.setText(aClass.getQualifiedName());
}
}
}, "", myContext.getProject(), true, RECENTS_KEY);
myTargetClassPanel.setLayout(new BorderLayout());
myTargetClassPanel.add(myTargetClassLabel, BorderLayout.NORTH);
myTargetClassPanel.add(myTargetClassEditor, BorderLayout.CENTER);
Set<String> possibleClassNames = new LinkedHashSet<String>();
for (final PsiElement occurrence : myContext.getOccurrences()) {
final PsiClass parentClass = getParentClass(occurrence);
if (parentClass != null && parentClass.getQualifiedName() != null) {
possibleClassNames.add(parentClass.getQualifiedName());
}
}
for (String possibleClassName : possibleClassNames) {
myTargetClassEditor.prependItem(possibleClassName);
}
if (myDefaultTargetClass != null) {
myTargetClassEditor.prependItem(myDefaultTargetClass.getQualifiedName());
}
myTargetClassEditor.getChildComponent().addDocumentListener(new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent e) {
targetClassChanged();
updateOkStatus();
// enableEnumDependant(introduceEnumConstant());
}
});
}
private void initializeName() {
myNameLabel.setLabelFor(myNameField);
myPanel.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myNameField.requestFocus();
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.ALT_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
myNameField.addDataChangedListener(new NameSuggestionsField.DataChanged() {
@Override
public void dataChanged() {
updateOkStatus();
}
});
}
@Override
public String getVisibilityModifier() {
return myJavaVisibilityPanel.getVisibility();
}
@Nullable
@Override
public PsiClass getTargetClass() {
return myTargetClassInfo.getTargetClass();
}
@NotNull
public String getTargetClassName() {
return myTargetClassEditor.getText();
}
@Override
public GrIntroduceConstantSettings getSettings() {
return this;
}
@NotNull
@Override
public LinkedHashSet<String> suggestNames() {
return new GrFieldNameSuggester(myContext, new GroovyVariableValidator(myContext), true).suggestNames();
}
@Nullable
@Override
public String getName() {
return myNameField.getEnteredName();
}
@Override
public boolean replaceAllOccurrences() {
return myReplaceAllOccurrences.isSelected();
}
@Override
public PsiType getSelectedType() {
return myTypeCombo.getSelectedType();
}
@NonNls private static final String RECENTS_KEY = "GrIntroduceConstantDialog.RECENTS_KEY";
private void createUIComponents() {
myJavaVisibilityPanel = new JavaVisibilityPanel(false, true);
final GrVariable var = myContext.getVar();
final GrExpression expression = myContext.getExpression();
final StringPartInfo stringPart = myContext.getStringPart();
if (expression != null) {
myTypeCombo = GrTypeComboBox.createTypeComboBoxFromExpression(expression);
}
else if (stringPart != null) {
myTypeCombo = GrTypeComboBox.createTypeComboBoxFromExpression(stringPart.getLiteral());
}
else {
assert var != null;
myTypeCombo = GrTypeComboBox.createTypeComboBoxWithDefType(var.getDeclaredType(), var);
}
List<String> names = new ArrayList<String>();
if (var != null) {
names.add(var.getName());
}
if (expression != null) {
ContainerUtil.addAll(names, suggestNames());
}
myNameField = new NameSuggestionsField(ArrayUtil.toStringArray(names), myContext.getProject(), GroovyFileType.GROOVY_FILE_TYPE);
GrTypeComboBox.registerUpDownHint(myNameField, myTypeCombo);
}
private void targetClassChanged() {
final String targetClassName = getTargetClassName();
myTargetClass =
JavaPsiFacade.getInstance(myContext.getProject()).findClass(targetClassName, GlobalSearchScope.projectScope(myContext.getProject()));
updateVisibilityPanel();
// myIntroduceEnumConstantCb.setEnabled(EnumConstantsUtil.isSuitableForEnumConstant(getSelectedType(), myTargetClassEditor));
}
private void updateVisibilityPanel() {
if (myTargetClass != null && myTargetClass.isInterface()) {
myJavaVisibilityPanel.disableAllButPublic();
}
else {
UIUtil.setEnabled(myJavaVisibilityPanel, true, true);
// exclude all modifiers not visible from all occurrences
final Set<String> visible = new THashSet<String>();
visible.add(PsiModifier.PRIVATE);
visible.add(PsiModifier.PROTECTED);
visible.add(PsiModifier.PACKAGE_LOCAL);
visible.add(PsiModifier.PUBLIC);
for (PsiElement occurrence : myContext.getOccurrences()) {
final PsiManager psiManager = PsiManager.getInstance(myContext.getProject());
for (Iterator<String> iterator = visible.iterator(); iterator.hasNext();) {
String modifier = iterator.next();
try {
final String modifierText = PsiModifier.PACKAGE_LOCAL.equals(modifier) ? "" : modifier + " ";
final PsiField field = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory().createFieldFromText(modifierText + "int xxx;", myTargetClass);
if (!JavaResolveUtil.isAccessible(field, myTargetClass, field.getModifierList(), occurrence, myTargetClass, null)) {
iterator.remove();
}
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
}
}
if (!visible.contains(getVisibilityModifier())) {
if (visible.contains(PsiModifier.PUBLIC)) myJavaVisibilityPanel.setVisibility(PsiModifier.PUBLIC);
if (visible.contains(PsiModifier.PACKAGE_LOCAL)) myJavaVisibilityPanel.setVisibility(PsiModifier.PACKAGE_LOCAL);
if (visible.contains(PsiModifier.PROTECTED)) myJavaVisibilityPanel.setVisibility(PsiModifier.PROTECTED);
if (visible.contains(PsiModifier.PRIVATE)) myJavaVisibilityPanel.setVisibility(PsiModifier.PRIVATE);
}
}
}
private void updateOkStatus() {
if (myTargetClassEditor == null) return; //dialog is not initialized yet
String text = getName();
if (!GroovyNamesUtil.isIdentifier(text)) {
setOKActionEnabled(false);
return;
}
final String targetClassName = myTargetClassEditor.getText();
if (targetClassName.trim().isEmpty() && myDefaultTargetClass == null) {
setOKActionEnabled(false);
return;
}
final String trimmed = targetClassName.trim();
if (!PsiNameHelper.getInstance(myContext.getProject()).isQualifiedName(trimmed)) {
setOKActionEnabled(false);
return;
}
setOKActionEnabled(true);
}
@Override
protected void doOKAction() {
final String targetClassName = getTargetClassName();
if (myDefaultTargetClass == null || !targetClassName.isEmpty() && !Comparing.strEqual(targetClassName, myDefaultTargetClass.getQualifiedName())) {
final Module module = ModuleUtilCore.findModuleForPsiElement(myContext.getPlace());
JavaPsiFacade facade = JavaPsiFacade.getInstance(myContext.getProject());
PsiClass newClass = facade.findClass(targetClassName, GlobalSearchScope.projectScope(myContext.getProject()));
if (newClass == null &&
Messages.showOkCancelDialog(myContext.getProject(), GroovyRefactoringBundle.message("class.does.not.exist.in.the.module"),
IntroduceConstantHandler.REFACTORING_NAME, Messages.getErrorIcon()) != Messages.OK) {
return;
}
myTargetClassInfo = new TargetClassInfo(targetClassName, myContext.getPlace().getContainingFile().getContainingDirectory(), module, myContext.getProject());
}
else {
myTargetClassInfo = new TargetClassInfo(myDefaultTargetClass);
}
JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY = getVisibilityModifier();
RecentsManager.getInstance(myContext.getProject()).registerRecentEntry(RECENTS_KEY, targetClassName);
super.doOKAction();
}
private static class TargetClassInfo {
private PsiClass myTargetClass;
String myQualifiedName;
PsiDirectory myBaseDirectory;
Module myModule;
Project myProject;
private TargetClassInfo(PsiClass targetClass) {
myTargetClass = targetClass;
}
private TargetClassInfo(String qualifiedName, PsiDirectory baseDirectory, Module module, Project project) {
myQualifiedName = qualifiedName;
myBaseDirectory = baseDirectory;
myModule = module;
myProject = project;
}
@Nullable
public PsiClass getTargetClass() {
if (myTargetClass == null) {
myTargetClass = getTargetClass(myQualifiedName, myBaseDirectory, myProject, myModule);
}
return myTargetClass;
}
@Nullable
private static PsiClass getTargetClass(String qualifiedName, PsiDirectory baseDirectory, Project project, Module module) {
GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
PsiClass targetClass = JavaPsiFacade.getInstance(project).findClass(qualifiedName, scope);
if (targetClass != null) return targetClass;
final String packageName = StringUtil.getPackageName(qualifiedName);
PsiPackage psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName);
final PsiDirectory psiDirectory;
if (psiPackage != null) {
final PsiDirectory[] directories = psiPackage.getDirectories(GlobalSearchScope.allScope(project));
psiDirectory = directories.length > 1 ? DirectoryChooserUtil
.chooseDirectory(directories, null, project, new HashMap<PsiDirectory, String>()) : directories[0];
}
else {
psiDirectory = PackageUtil.findOrCreateDirectoryForPackage(module, packageName, baseDirectory, false);
}
if (psiDirectory == null) return null;
final String shortName = StringUtil.getShortName(qualifiedName);
final String fileName = shortName + NewGroovyActionBase.GROOVY_EXTENSION;
final AccessToken lock = ApplicationManager.getApplication().acquireWriteActionLock(GrIntroduceConstantDialog.class);
try {
final GroovyFile file =
(GroovyFile)GroovyTemplatesFactory.createFromTemplate(psiDirectory, shortName, fileName, GroovyTemplates.GROOVY_CLASS, true);
return file.getTypeDefinitions()[0];
}
finally {
lock.finish();
}
}
}
}