blob: 70d7d3017a957023053c81455ec62698fa5367f5 [file] [log] [blame]
/*
* 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;
}
}