| /* |
| * Copyright 2000-2013 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.uiDesigner.propertyInspector.properties; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileTypes.StdFileTypes; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.project.Project; |
| 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.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.refactoring.rename.RenameProcessor; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.uiDesigner.FormEditingUtil; |
| import com.intellij.uiDesigner.UIDesignerBundle; |
| import com.intellij.uiDesigner.compiler.AsmCodeGenerator; |
| import com.intellij.uiDesigner.designSurface.GuiEditor; |
| import com.intellij.uiDesigner.designSurface.InsertComponentProcessor; |
| import com.intellij.uiDesigner.inspections.FormInspectionUtil; |
| import com.intellij.uiDesigner.propertyInspector.DesignerToolWindowManager; |
| import com.intellij.uiDesigner.propertyInspector.Property; |
| import com.intellij.uiDesigner.propertyInspector.PropertyEditor; |
| import com.intellij.uiDesigner.propertyInspector.PropertyRenderer; |
| import com.intellij.uiDesigner.propertyInspector.editors.BindingEditor; |
| import com.intellij.uiDesigner.propertyInspector.renderers.LabelPropertyRenderer; |
| import com.intellij.uiDesigner.quickFixes.CreateFieldFix; |
| import com.intellij.uiDesigner.radComponents.RadComponent; |
| import com.intellij.uiDesigner.radComponents.RadRootContainer; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author Anton Katilin |
| * @author Vladimir Kondratyev |
| */ |
| public final class BindingProperty extends Property<RadComponent, String> { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.propertyInspector.properties.BindingProperty"); |
| |
| private final PropertyRenderer<String> myRenderer = new LabelPropertyRenderer<String>() { |
| protected void customize(@NotNull final String value) { |
| setText(value); |
| } |
| }; |
| private final BindingEditor myEditor; |
| @NonNls private static final String PREFIX_HTML = "<html>"; |
| |
| public BindingProperty(final Project project){ |
| super(null, "field name"); |
| myEditor = new BindingEditor(project); |
| } |
| |
| public PropertyEditor<String> getEditor(){ |
| return myEditor; |
| } |
| |
| @NotNull |
| public PropertyRenderer<String> getRenderer(){ |
| return myRenderer; |
| } |
| |
| public String getValue(final RadComponent component){ |
| return component.getBinding(); |
| } |
| |
| protected void setValueImpl(final RadComponent component, final String value) throws Exception { |
| if (Comparing.strEqual(value, component.getBinding(), true)) { |
| return; |
| } |
| |
| if (value.length() > 0 && !PsiNameHelper.getInstance(component.getProject()).isIdentifier(value)) { |
| throw new Exception("Value '" + value + "' is not a valid identifier"); |
| } |
| |
| final RadRootContainer root = (RadRootContainer) FormEditingUtil.getRoot(component); |
| final String oldBinding = getValue(component); |
| |
| // Check that binding remains unique |
| |
| if (value.length() > 0) { |
| if (!FormEditingUtil.isBindingUnique(component, value, root)) { |
| throw new Exception(UIDesignerBundle.message("error.binding.not.unique")); |
| } |
| |
| component.setBinding(value); |
| component.setDefaultBinding(false); |
| } |
| else { |
| if (component.isCustomCreateRequired()) { |
| throw new Exception(UIDesignerBundle.message("error.custom.create.binding.required")); |
| } |
| component.setBinding(null); |
| component.setCustomCreate(false); |
| } |
| |
| // Set new value or rename old one. It means that previous binding exists |
| // and the new one doesn't exist we need to ask user to create new field |
| // or rename old one. |
| |
| updateBoundFieldName(root, oldBinding, value, component.getComponentClassName()); |
| } |
| |
| public static void updateBoundFieldName(final RadRootContainer root, final String oldName, final String newName, final String fieldClassName) { |
| final String classToBind = root.getClassToBind(); |
| if (classToBind == null) return; |
| |
| final Project project = root.getProject(); |
| if (newName.length() == 0) { |
| checkRemoveUnusedField(root, oldName, FormEditingUtil.getNextSaveUndoGroupId(project)); |
| return; |
| } |
| |
| final PsiClass aClass = JavaPsiFacade.getInstance(project).findClass(classToBind, GlobalSearchScope.allScope(project)); |
| if(aClass == null){ |
| return; |
| } |
| |
| if(oldName == null) { |
| if (aClass.findFieldByName(newName, true) == null) { |
| CreateFieldFix.runImpl(project, root, aClass, fieldClassName, newName, false, |
| FormEditingUtil.getNextSaveUndoGroupId(project)); |
| } |
| return; |
| } |
| |
| final PsiField oldField = aClass.findFieldByName(oldName, true); |
| if(oldField == null){ |
| return; |
| } |
| |
| if(aClass.findFieldByName(newName, true) != null) { |
| checkRemoveUnusedField(root, oldName, FormEditingUtil.getNextSaveUndoGroupId(project)); |
| return; |
| } |
| |
| // Show question to the user |
| |
| if (!isFieldUnreferenced(oldField)) { |
| final int option = Messages.showYesNoDialog(project, |
| MessageFormat.format(UIDesignerBundle.message("message.rename.field"), oldName, newName), |
| UIDesignerBundle.message("title.rename"), |
| Messages.getQuestionIcon() |
| ); |
| |
| if(option != Messages.YES/*Yes*/){ |
| return; |
| } |
| } |
| |
| // Commit document before refactoring starts |
| GuiEditor editor = DesignerToolWindowManager.getInstance(project).getActiveFormEditor(); |
| if (editor != null) { |
| editor.refreshAndSave(false); |
| } |
| PsiDocumentManager.getInstance(project).commitAllDocuments(); |
| |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(project, aClass)) { |
| return; |
| } |
| |
| final RenameProcessor processor = new RenameProcessor(project, oldField, newName, true, true); |
| processor.run(); |
| } |
| |
| |
| @Override |
| public boolean isModified(final RadComponent component) { |
| return component.getBinding() != null; |
| } |
| |
| @Override |
| public void resetValue(final RadComponent component) throws Exception { |
| setValueImpl(component, ""); |
| } |
| |
| @Override |
| public boolean appliesToSelection(final List<RadComponent> selection) { |
| return selection.size() == 1; |
| } |
| |
| @Nullable |
| public static PsiField findBoundField(@NotNull final RadRootContainer root, final String fieldName) { |
| final Project project = root.getProject(); |
| final String classToBind = root.getClassToBind(); |
| if (classToBind != null) { |
| final PsiManager manager = PsiManager.getInstance(project); |
| PsiClass aClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(classToBind, GlobalSearchScope.allScope(project)); |
| if (aClass != null) { |
| final PsiField oldBindingField = aClass.findFieldByName(fieldName, false); |
| if (oldBindingField != null) { |
| return oldBindingField; |
| } |
| } |
| } |
| return null; |
| } |
| |
| public static void checkRemoveUnusedField(final RadRootContainer rootContainer, final String fieldName, final Object undoGroupId) { |
| final PsiField oldBindingField = findBoundField(rootContainer, fieldName); |
| if (oldBindingField == null) { |
| return; |
| } |
| final Project project = oldBindingField.getProject(); |
| final PsiClass aClass = oldBindingField.getContainingClass(); |
| if (isFieldUnreferenced(oldBindingField)) { |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(project, aClass)) { |
| return; |
| } |
| ApplicationManager.getApplication().runWriteAction( |
| new Runnable() { |
| public void run() { |
| CommandProcessor.getInstance().executeCommand( |
| project, |
| new Runnable() { |
| public void run() { |
| try { |
| oldBindingField.delete(); |
| } |
| catch (IncorrectOperationException e) { |
| Messages.showErrorDialog(project, UIDesignerBundle.message("error.cannot.delete.unused.field", e.getMessage()), |
| CommonBundle.getErrorTitle()); |
| } |
| } |
| }, |
| UIDesignerBundle.message("command.delete.unused.field"), undoGroupId |
| ); |
| } |
| } |
| ); |
| } |
| } |
| |
| private static boolean isFieldUnreferenced(final PsiField field) { |
| try { |
| return ReferencesSearch.search(field).forEach(new Processor<PsiReference>() { |
| public boolean process(final PsiReference t) { |
| PsiFile f = t.getElement().getContainingFile(); |
| if (f != null && f.getFileType().equals(StdFileTypes.GUI_DESIGNER_FORM)) { |
| return true; |
| } |
| PsiMethod method = PsiTreeUtil.getParentOfType(t.getElement(), PsiMethod.class); |
| if (method != null && method.getName().equals(AsmCodeGenerator.SETUP_METHOD_NAME)) { |
| return true; |
| } |
| return false; |
| } |
| }); |
| } |
| catch (IndexNotReadyException e) { |
| return false; |
| } |
| } |
| |
| public static void checkCreateBindingFromText(final RadComponent component, final String text) { |
| if (!component.isDefaultBinding()) { |
| return; |
| } |
| RadRootContainer root = (RadRootContainer)FormEditingUtil.getRoot(component); |
| PsiField boundField = findBoundField(root, component.getBinding()); |
| if (boundField == null || !isFieldUnreferenced(boundField)) { |
| return; |
| } |
| |
| String binding = suggestBindingFromText(component, text); |
| if (binding != null) { |
| new BindingProperty(component.getProject()).setValueEx(component, binding); |
| // keep the binding marked as default |
| component.setDefaultBinding(true); |
| } |
| } |
| |
| @Nullable |
| public static String suggestBindingFromText(final RadComponent component, String text) { |
| if (StringUtil.startsWithIgnoreCase(text, PREFIX_HTML)) { |
| text = Pattern.compile("<.+?>").matcher(text).replaceAll(""); |
| } |
| ArrayList<String> words = new ArrayList<String>(StringUtil.getWordsIn(text)); |
| if (words.size() > 0) { |
| StringBuilder nameBuilder = new StringBuilder(StringUtil.decapitalize(words.get(0))); |
| for(int i=1; i<words.size() && i < 4; i++) { |
| nameBuilder.append(StringUtil.capitalize(words.get(i))); |
| } |
| final String shortClassName = StringUtil.capitalize(InsertComponentProcessor.getShortClassName(component.getComponentClassName())); |
| if (shortClassName.equalsIgnoreCase(nameBuilder.toString())) { |
| // avoid "buttonButton" case |
| return null; |
| } |
| nameBuilder.append(shortClassName); |
| |
| RadRootContainer root = (RadRootContainer) FormEditingUtil.getRoot(component); |
| Project project = root.getProject(); |
| String binding = JavaCodeStyleManager.getInstance(project).propertyNameToVariableName(nameBuilder.toString(), VariableKind.FIELD); |
| if (FormEditingUtil.findComponentWithBinding(root, binding, component) != null) { |
| binding = InsertComponentProcessor.getUniqueBinding(root, nameBuilder.toString()); |
| } |
| return binding; |
| } |
| return null; |
| } |
| |
| public static String getDefaultBinding(final RadComponent c) { |
| RadRootContainer root = (RadRootContainer) FormEditingUtil.getRoot(c); |
| String binding = null; |
| String text = FormInspectionUtil.getText(c.getModule(), c); |
| if (text != null) { |
| binding = suggestBindingFromText(c, text); |
| } |
| if (binding == null) { |
| binding = InsertComponentProcessor.suggestBinding(root, c.getComponentClassName()); |
| } |
| return binding; |
| } |
| } |