blob: d39cc8a1035e60bd15a6aef8628caa29b030f5de [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 com.intellij.refactoring.introduceField;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.ide.util.ClassFilter;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.TreeClassChooser;
import com.intellij.ide.util.TreeClassChooserFactory;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.help.HelpManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Comparing;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.SuggestedNameInfo;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceParameter.AbstractJavaInplaceIntroducer;
import com.intellij.refactoring.ui.*;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.EnumConstantsUtil;
import com.intellij.refactoring.util.RefactoringMessageUtil;
import com.intellij.ui.RecentsManager;
import com.intellij.ui.ReferenceEditorComboWithBrowseButton;
import com.intellij.ui.StateRestoringCheckBox;
import com.intellij.usageView.UsageViewUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
class IntroduceConstantDialog extends DialogWrapper {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceField.IntroduceConstantDialog");
@NonNls private static final String RECENTS_KEY = "IntroduceConstantDialog.RECENTS_KEY";
@NonNls protected static final String NONNLS_SELECTED_PROPERTY = "INTRODUCE_CONSTANT_NONNLS";
private final Project myProject;
private final PsiClass myParentClass;
private final PsiExpression myInitializerExpression;
private final PsiLocalVariable myLocalVariable;
private final boolean myInvokedOnDeclaration;
private final PsiExpression[] myOccurrences;
private final String myEnteredName;
private final int myOccurrencesCount;
private PsiClass myTargetClass;
private final TypeSelectorManager myTypeSelectorManager;
private NameSuggestionsField myNameField;
private JCheckBox myCbReplaceAll;
private TypeSelector myTypeSelector;
private StateRestoringCheckBox myCbDeleteVariable;
private final JavaCodeStyleManager myCodeStyleManager;
private ReferenceEditorComboWithBrowseButton myTfTargetClassName;
private BaseExpressionToFieldHandler.TargetDestination myDestinationClass;
private JPanel myTypePanel;
private JPanel myTargetClassNamePanel;
private JPanel myPanel;
private JLabel myTypeLabel;
private JPanel myNameSuggestionPanel;
private JLabel myNameSuggestionLabel;
private JLabel myTargetClassNameLabel;
private JCheckBox myCbNonNls;
private JPanel myVisibilityPanel;
private final JavaVisibilityPanel myVPanel;
private final JCheckBox myIntroduceEnumConstantCb = new JCheckBox(RefactoringBundle.message("introduce.constant.enum.cb"), true);
IntroduceConstantDialog(Project project,
PsiClass parentClass,
PsiExpression initializerExpression,
PsiLocalVariable localVariable,
boolean isInvokedOnDeclaration,
PsiExpression[] occurrences,
PsiClass targetClass,
TypeSelectorManager typeSelectorManager, String enteredName) {
super(project, true);
myProject = project;
myParentClass = parentClass;
myInitializerExpression = initializerExpression;
myLocalVariable = localVariable;
myInvokedOnDeclaration = isInvokedOnDeclaration;
myOccurrences = occurrences;
myEnteredName = enteredName;
myOccurrencesCount = occurrences.length;
myTargetClass = targetClass;
myTypeSelectorManager = typeSelectorManager;
myDestinationClass = null;
setTitle(IntroduceConstantHandler.REFACTORING_NAME);
myCodeStyleManager = JavaCodeStyleManager.getInstance(myProject);
myVPanel = new JavaVisibilityPanel(false, true);
myVisibilityPanel.add(myVPanel, BorderLayout.CENTER);
init();
myVPanel.setVisibility(JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY);
myIntroduceEnumConstantCb.setEnabled(isSuitableForEnumConstant());
updateVisibilityPanel();
updateButtons();
}
public String getEnteredName() {
return myNameField.getEnteredName();
}
private String getTargetClassName() {
return myTfTargetClassName.getText().trim();
}
public BaseExpressionToFieldHandler.TargetDestination getDestinationClass () {
return myDestinationClass;
}
public boolean introduceEnumConstant() {
return myIntroduceEnumConstantCb.isEnabled() && myIntroduceEnumConstantCb.isSelected();
}
public String getFieldVisibility() {
return myVPanel.getVisibility();
}
public boolean isReplaceAllOccurrences() {
return myOccurrencesCount > 1 && myCbReplaceAll.isSelected();
}
public PsiType getSelectedType() {
return myTypeSelector.getSelectedType();
}
@NotNull
protected Action[] createActions() {
return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
}
protected void doHelpAction() {
HelpManager.getInstance().invokeHelp(HelpID.INTRODUCE_CONSTANT);
}
protected JComponent createNorthPanel() {
myTypeSelector = myTypeSelectorManager.getTypeSelector();
myTypePanel.setLayout(new BorderLayout());
myTypePanel.add(myTypeSelector.getComponent(), BorderLayout.CENTER);
if (myTypeSelector.getFocusableComponent() != null) {
myTypeLabel.setLabelFor(myTypeSelector.getFocusableComponent());
}
myNameField = new NameSuggestionsField(myProject);
myNameSuggestionPanel.setLayout(new BorderLayout());
myNameField.addDataChangedListener(new NameSuggestionsField.DataChanged() {
public void dataChanged() {
updateButtons();
}
});
myNameSuggestionPanel.add(myNameField.getComponent(), BorderLayout.CENTER);
myNameSuggestionLabel.setLabelFor(myNameField.getFocusableComponent());
Set<String> possibleClassNames = new LinkedHashSet<String>();
for (final PsiExpression occurrence : myOccurrences) {
final PsiClass parentClass = new IntroduceConstantHandler().getParentClass(occurrence);
if (parentClass != null && parentClass.getQualifiedName() != null) {
possibleClassNames.add(parentClass.getQualifiedName());
}
}
myTfTargetClassName =
new ReferenceEditorComboWithBrowseButton(new ChooseClassAction(), "", myProject, true, RECENTS_KEY);
myTargetClassNamePanel.setLayout(new BorderLayout());
myTargetClassNamePanel.add(myTfTargetClassName, BorderLayout.CENTER);
myTargetClassNameLabel.setLabelFor(myTfTargetClassName);
for (String possibleClassName : possibleClassNames) {
myTfTargetClassName.prependItem(possibleClassName);
}
myTfTargetClassName.getChildComponent().addDocumentListener(new DocumentAdapter() {
public void documentChanged(DocumentEvent e) {
targetClassChanged();
enableEnumDependant(introduceEnumConstant());
}
});
myIntroduceEnumConstantCb.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
enableEnumDependant(introduceEnumConstant());
}
});
final JPanel enumPanel = new JPanel(new BorderLayout());
enumPanel.add(myIntroduceEnumConstantCb, BorderLayout.EAST);
myTargetClassNamePanel.add(enumPanel, BorderLayout.SOUTH);
final String propertyName;
if (myLocalVariable != null) {
propertyName = myCodeStyleManager.variableNameToPropertyName(myLocalVariable.getName(), VariableKind.LOCAL_VARIABLE);
}
else {
propertyName = null;
}
final NameSuggestionsManager nameSuggestionsManager =
new NameSuggestionsManager(myTypeSelector, myNameField, createNameSuggestionGenerator(propertyName, myInitializerExpression,
myCodeStyleManager, myEnteredName, myParentClass));
nameSuggestionsManager.setLabelsFor(myTypeLabel, myNameSuggestionLabel);
//////////
if (myOccurrencesCount > 1) {
myCbReplaceAll.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
updateTypeSelector();
myNameField.requestFocusInWindow();
}
});
myCbReplaceAll.setText(RefactoringBundle.message("replace.all.occurences", myOccurrencesCount));
}
else {
myCbReplaceAll.setVisible(false);
}
if (myLocalVariable != null) {
if (myInvokedOnDeclaration) {
myCbDeleteVariable.setEnabled(false);
myCbDeleteVariable.setSelected(true);
}
else if (myCbReplaceAll != null) {
updateCbDeleteVariable();
myCbReplaceAll.addItemListener(
new ItemListener() {
public void itemStateChanged(ItemEvent e) {
updateCbDeleteVariable();
}
});
}
}
else {
myCbDeleteVariable.setVisible(false);
}
final PsiManager psiManager = PsiManager.getInstance(myProject);
if ((myTypeSelectorManager.isSuggestedType(CommonClassNames.JAVA_LANG_STRING) || (myLocalVariable != null && AnnotationUtil.isAnnotated(myLocalVariable, AnnotationUtil.NON_NLS, false, false)))&&
LanguageLevelProjectExtension.getInstance(psiManager.getProject()).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_5) &&
JavaPsiFacade.getInstance(psiManager.getProject()).findClass(AnnotationUtil.NON_NLS, myParentClass.getResolveScope()) != null) {
final PropertiesComponent component = PropertiesComponent.getInstance(myProject);
myCbNonNls.setSelected(component.isTrueValue(NONNLS_SELECTED_PROPERTY));
myCbNonNls.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
component.setValue(NONNLS_SELECTED_PROPERTY, Boolean.toString(myCbNonNls.isSelected()));
}
});
} else {
myCbNonNls.setVisible(false);
}
updateTypeSelector();
enableEnumDependant(introduceEnumConstant());
return myPanel;
}
public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
if (myCbReplaceAll != null) {
myCbReplaceAll.setSelected(replaceAllOccurrences);
}
}
protected static NameSuggestionsGenerator createNameSuggestionGenerator(final String propertyName,
final PsiExpression psiExpression,
final JavaCodeStyleManager codeStyleManager,
final String enteredName, final PsiClass parentClass) {
return new NameSuggestionsGenerator() {
public SuggestedNameInfo getSuggestedNameInfo(PsiType type) {
SuggestedNameInfo nameInfo =
codeStyleManager.suggestVariableName(VariableKind.STATIC_FINAL_FIELD, propertyName, psiExpression, type);
if (psiExpression != null) {
String[] names = nameInfo.names;
for (int i = 0, namesLength = names.length; i < namesLength; i++) {
String name = names[i];
if (parentClass.findFieldByName(name, false) != null) {
names[i] = codeStyleManager.suggestUniqueVariableName(name, psiExpression, true);
}
}
}
final String[] strings = AbstractJavaInplaceIntroducer.appendUnresolvedExprName(JavaCompletionUtil
.completeVariableNameForRefactoring(codeStyleManager, type, VariableKind.LOCAL_VARIABLE, nameInfo), psiExpression);
return new SuggestedNameInfo.Delegate(enteredName != null ? ArrayUtil.mergeArrays(new String[]{enteredName}, strings): strings, nameInfo);
}
};
}
private void updateButtons() {
setOKActionEnabled(PsiNameHelper.getInstance(myProject).isIdentifier(getEnteredName()));
}
private void targetClassChanged() {
final String targetClassName = getTargetClassName();
myTargetClass = JavaPsiFacade.getInstance(myProject).findClass(targetClassName, GlobalSearchScope.projectScope(myProject));
updateVisibilityPanel();
myIntroduceEnumConstantCb.setEnabled(isSuitableForEnumConstant());
}
private boolean isSuitableForEnumConstant() {
return EnumConstantsUtil.isSuitableForEnumConstant(getSelectedType(), myTargetClass) && PsiTreeUtil
.getParentOfType(myInitializerExpression,
PsiEnumConstant.class) == null;
}
private void enableEnumDependant(boolean enable) {
if (enable) {
myVPanel.disableAllButPublic();
} else {
updateVisibilityPanel();
}
myCbNonNls.setEnabled(!enable);
}
protected JComponent createCenterPanel() {
return new JPanel();
}
public boolean isDeleteVariable() {
return myInvokedOnDeclaration || myCbDeleteVariable != null && myCbDeleteVariable.isSelected();
}
public boolean isAnnotateAsNonNls() {
return myCbNonNls != null && myCbNonNls.isSelected();
}
private void updateCbDeleteVariable() {
if (!myCbReplaceAll.isSelected()) {
myCbDeleteVariable.makeUnselectable(false);
}
else {
myCbDeleteVariable.makeSelectable();
}
}
private void updateTypeSelector() {
if (myCbReplaceAll != null) {
myTypeSelectorManager.setAllOccurrences(myCbReplaceAll.isSelected());
}
else {
myTypeSelectorManager.setAllOccurrences(false);
}
}
private void updateVisibilityPanel() {
if (myTargetClass != null && myTargetClass.isInterface()) {
myVPanel.disableAllButPublic();
}
else {
UIUtil.setEnabled(myVisibilityPanel, true, true);
// exclude all modifiers not visible from all occurences
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 (PsiExpression occurrence : myOccurrences) {
final PsiManager psiManager = PsiManager.getInstance(myProject);
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(getFieldVisibility())) {
if (visible.contains(PsiModifier.PUBLIC)) myVPanel.setVisibility(PsiModifier.PUBLIC);
if (visible.contains(PsiModifier.PACKAGE_LOCAL)) myVPanel.setVisibility(PsiModifier.PACKAGE_LOCAL);
if (visible.contains(PsiModifier.PROTECTED)) myVPanel.setVisibility(PsiModifier.PROTECTED);
if (visible.contains(PsiModifier.PRIVATE)) myVPanel.setVisibility(PsiModifier.PRIVATE);
}
}
}
protected void doOKAction() {
final String targetClassName = getTargetClassName();
PsiClass newClass = myParentClass;
if (!"".equals (targetClassName) && !Comparing.strEqual(targetClassName, myParentClass.getQualifiedName())) {
newClass = JavaPsiFacade.getInstance(myProject).findClass(targetClassName, GlobalSearchScope.projectScope(myProject));
if (newClass == null) {
if (Messages.showOkCancelDialog(myProject, RefactoringBundle.message("class.does.not.exist.in.the.project"), IntroduceConstantHandler.REFACTORING_NAME, Messages.getErrorIcon()) != Messages.OK) {
return;
}
myDestinationClass = new BaseExpressionToFieldHandler.TargetDestination(targetClassName, myParentClass);
} else {
myDestinationClass = new BaseExpressionToFieldHandler.TargetDestination(newClass);
}
}
String fieldName = getEnteredName();
String errorString = null;
if ("".equals(fieldName)) {
errorString = RefactoringBundle.message("no.field.name.specified");
} else if (!PsiNameHelper.getInstance(myProject).isIdentifier(fieldName)) {
errorString = RefactoringMessageUtil.getIncorrectIdentifierMessage(fieldName);
} else if (newClass != null && !myParentClass.getLanguage().equals(newClass.getLanguage())) {
errorString = RefactoringBundle.message("move.to.different.language", UsageViewUtil.getType(myParentClass),
myParentClass.getQualifiedName(), newClass.getQualifiedName());
}
if (errorString != null) {
CommonRefactoringUtil.showErrorMessage(
IntroduceFieldHandler.REFACTORING_NAME,
errorString,
HelpID.INTRODUCE_FIELD,
myProject);
return;
}
if (newClass != null) {
PsiField oldField = newClass.findFieldByName(fieldName, true);
if (oldField != null) {
int answer = Messages.showYesNoDialog(
myProject,
RefactoringBundle.message("field.exists", fieldName, oldField.getContainingClass().getQualifiedName()),
IntroduceFieldHandler.REFACTORING_NAME,
Messages.getWarningIcon()
);
if (answer != Messages.YES) {
return;
}
}
}
JavaRefactoringSettings.getInstance().INTRODUCE_CONSTANT_VISIBILITY = getFieldVisibility();
RecentsManager.getInstance(myProject).registerRecentEntry(RECENTS_KEY, targetClassName);
super.doOKAction();
}
public JComponent getPreferredFocusedComponent() {
return myNameField.getFocusableComponent();
}
private class ChooseClassAction implements ActionListener {
public void actionPerformed(ActionEvent e) {
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject).createWithInnerClassesScopeChooser(RefactoringBundle.message("choose.destination.class"), GlobalSearchScope.projectScope(myProject), new ClassFilter() {
public boolean isAccepted(PsiClass aClass) {
return aClass.getParent() instanceof PsiJavaFile || aClass.hasModifierProperty(PsiModifier.STATIC);
}
}, null);
if (myTargetClass != null) {
chooser.selectDirectory(myTargetClass.getContainingFile().getContainingDirectory());
}
chooser.showDialog();
PsiClass aClass = chooser.getSelected();
if (aClass != null) {
myTfTargetClassName.setText(aClass.getQualifiedName());
}
}
}
}