]> source.dussan.org Git - vaadin-framework.git/commitdiff
Case-insensitive FieldGroup binding #10426
authorJonni Nakari <jonni@vaadin.com>
Fri, 11 Jan 2013 15:22:19 +0000 (17:22 +0200)
committerVaadin Code Review <review@vaadin.com>
Wed, 13 Feb 2013 14:23:19 +0000 (14:23 +0000)
Modified buildAndBindMemberFields to use a new findPropertyId method
when searching propertyIds. Fixed BeanFieldGroup to work with the
modified property id searching.

Change-Id: Ib5d011db93f04d7a2c6dbb0990af7c35f0489193

server/src/com/vaadin/data/fieldgroup/BeanFieldGroup.java
server/src/com/vaadin/data/fieldgroup/FieldGroup.java
server/tests/src/com/vaadin/tests/server/component/fieldgroup/CaseInsensitiveBinding.java [new file with mode: 0644]

index 7e44c26c9edc2e6c21ca8146977c43340002d073..c748bd6c446c0432fc313bf0412896fa8af89a8e 100644 (file)
@@ -58,6 +58,22 @@ public class BeanFieldGroup<T> extends FieldGroup {
         }
     }
 
+    @Override
+    protected Object findPropertyId(java.lang.reflect.Field memberField) {
+        String fieldName = memberField.getName();
+        Item dataSource = getItemDataSource();
+        if (dataSource != null && dataSource.getItemProperty(fieldName) != null) {
+            return fieldName;
+        } else {
+            String minifiedFieldName = minifyFieldName(fieldName);
+            try {
+                return getFieldName(beanType, minifiedFieldName);
+            } catch (SecurityException | NoSuchFieldException e) {
+            }
+        }
+        return null;
+    }
+
     private static java.lang.reflect.Field getField(Class<?> cls,
             String propertyId) throws SecurityException, NoSuchFieldException {
         if (propertyId.contains(".")) {
@@ -75,7 +91,7 @@ public class BeanFieldGroup<T> extends FieldGroup {
             } catch (NoSuchFieldError e) {
                 // Try super classes until we reach Object
                 Class<?> superClass = cls.getSuperclass();
-                if (superClass != Object.class) {
+                if (superClass != null && superClass != Object.class) {
                     return getField(superClass, propertyId);
                 } else {
                     throw e;
@@ -84,6 +100,22 @@ public class BeanFieldGroup<T> extends FieldGroup {
         }
     }
 
+    private static String getFieldName(Class<?> cls, String propertyId)
+            throws SecurityException, NoSuchFieldException {
+        for (java.lang.reflect.Field field1 : cls.getDeclaredFields()) {
+            if (propertyId.equals(minifyFieldName(field1.getName()))) {
+                return field1.getName();
+            }
+        }
+        // Try super classes until we reach Object
+        Class<?> superClass = cls.getSuperclass();
+        if (superClass != null && superClass != Object.class) {
+            return getFieldName(superClass, propertyId);
+        } else {
+            throw new NoSuchFieldException();
+        }
+    }
+
     /**
      * Helper method for setting the data source directly using a bean. This
      * method wraps the bean in a {@link BeanItem} and calls
@@ -166,4 +198,4 @@ public class BeanFieldGroup<T> extends FieldGroup {
         }
         return beanValidationImplementationAvailable;
     }
-}
\ No newline at end of file
+}
index dc1fdbb78d824d09527eb2420b0089d9a125b912..6c515dbdeeabb61f0243ade5e0db1e535f1de66e 100644 (file)
@@ -733,11 +733,12 @@ public class FieldGroup implements Serializable {
      * that have not been initialized.
      * <p>
      * This method processes all (Java) member fields whose type extends
-     * {@link Field} and that can be mapped to a property id. Property id
-     * mapping is done based on the field name or on a @{@link PropertyId}
-     * annotation on the field. Fields that are not initialized (null) are built
-     * using the field factory. All non-null fields for which a property id can
-     * be determined are bound to the property id.
+     * {@link Field} and that can be mapped to a property id. Property ids are
+     * searched in the following order: @{@link PropertyId} annotations, exact
+     * field name matches and the case-insensitive matching that ignores
+     * underscores. Fields that are not initialized (null) are built using the
+     * field factory. All non-null fields for which a property id can be
+     * determined are bound to the property id.
      * </p>
      * <p>
      * For example:
@@ -777,11 +778,12 @@ public class FieldGroup implements Serializable {
      * member fields that have not been initialized.
      * <p>
      * This method processes all (Java) member fields whose type extends
-     * {@link Field} and that can be mapped to a property id. Property id
-     * mapping is done based on the field name or on a @{@link PropertyId}
-     * annotation on the field. Fields that are not initialized (null) are built
-     * using the field factory is buildFields is true. All non-null fields for
-     * which a property id can be determined are bound to the property id.
+     * {@link Field} and that can be mapped to a property id. Property ids are
+     * searched in the following order: @{@link PropertyId} annotations, exact
+     * field name matches and the case-insensitive matching that ignores
+     * underscores. Fields that are not initialized (null) are built using the
+     * field factory is buildFields is true. All non-null fields for which a
+     * property id can be determined are bound to the property id.
      * </p>
      * 
      * @param objectWithMemberFields
@@ -812,7 +814,16 @@ public class FieldGroup implements Serializable {
                 // @PropertyId(propertyId) always overrides property id
                 propertyId = propertyIdAnnotation.value();
             } else {
-                propertyId = memberField.getName();
+                try {
+                    propertyId = findPropertyId(memberField);
+                } catch (SearchException e) {
+                    // Property id was not found, skip this field
+                    continue;
+                }
+                if (propertyId == null) {
+                    // Property id was not found, skip this field
+                    continue;
+                }
             }
 
             // Ensure that the property id exists
@@ -873,6 +884,54 @@ public class FieldGroup implements Serializable {
         }
     }
 
+    /**
+     * Searches for a property id from the current itemDataSource that matches
+     * the given memberField.
+     * <p>
+     * If perfect match is not found, uses a case insensitive search that also
+     * ignores underscores. Returns null if no match is found. Throws a
+     * SearchException if no item data source has been set.
+     * </p>
+     * <p>
+     * The propertyId search logic used by
+     * {@link #buildAndBindMemberFields(Object, boolean)
+     * buildAndBindMemberFields} can easily be customized by overriding this
+     * method. No other changes are needed.
+     * </p>
+     * 
+     * @param memberField
+     *            The field an object id is searched for
+     * @return
+     */
+    protected Object findPropertyId(java.lang.reflect.Field memberField) {
+        String fieldName = memberField.getName();
+        if (getItemDataSource() == null) {
+            throw new SearchException(
+                    "Property id type for field '"
+                            + fieldName
+                            + "' could not be determined. No item data source has been set.");
+        }
+        Item dataSource = getItemDataSource();
+        if (dataSource.getItemProperty(fieldName) != null) {
+            return fieldName;
+        } else {
+            String minifiedFieldName = minifyFieldName(fieldName);
+            for (Object itemPropertyId : dataSource.getItemPropertyIds()) {
+                if (itemPropertyId instanceof String) {
+                    String itemPropertyName = (String) itemPropertyId;
+                    if (minifiedFieldName.equals(minifyFieldName(itemPropertyName))) {
+                        return itemPropertyName;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    protected static String minifyFieldName(String fieldName) {
+        return fieldName.toLowerCase().replace("_", "");
+    }
+
     public static class CommitException extends Exception {
 
         public CommitException() {
@@ -909,6 +968,18 @@ public class FieldGroup implements Serializable {
 
     }
 
+    public static class SearchException extends RuntimeException {
+
+        public SearchException(String message) {
+            super(message);
+        }
+
+        public SearchException(String message, Throwable t) {
+            super(message, t);
+        }
+
+    }
+
     /**
      * Builds a field and binds it to the given property id using the field
      * binder.
diff --git a/server/tests/src/com/vaadin/tests/server/component/fieldgroup/CaseInsensitiveBinding.java b/server/tests/src/com/vaadin/tests/server/component/fieldgroup/CaseInsensitiveBinding.java
new file mode 100644 (file)
index 0000000..3f4368c
--- /dev/null
@@ -0,0 +1,84 @@
+package com.vaadin.tests.server.component.fieldgroup;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.vaadin.data.fieldgroup.FieldGroup;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+import com.vaadin.ui.FormLayout;
+import com.vaadin.ui.TextField;
+
+public class CaseInsensitiveBinding {
+
+    @Test
+    public void caseInsensitivityAndUnderscoreRemoval() {
+        PropertysetItem item = new PropertysetItem();
+        item.addItemProperty("LastName", new ObjectProperty<String>("Sparrow"));
+
+        class MyForm extends FormLayout {
+            TextField lastName = new TextField("Last name");
+
+            public MyForm() {
+
+                // Should bind to the LastName property
+                addComponent(lastName);
+            }
+        }
+
+        MyForm form = new MyForm();
+
+        FieldGroup binder = new FieldGroup(item);
+        binder.bindMemberFields(form);
+
+        assertTrue("Sparrow".equals(form.lastName.getValue()));
+    }
+
+    @Test
+    public void UnderscoreRemoval() {
+        PropertysetItem item = new PropertysetItem();
+        item.addItemProperty("first_name", new ObjectProperty<String>("Jack"));
+
+        class MyForm extends FormLayout {
+            TextField firstName = new TextField("First name");
+
+            public MyForm() {
+                // Should bind to the first_name property
+                addComponent(firstName);
+            }
+        }
+
+        MyForm form = new MyForm();
+
+        FieldGroup binder = new FieldGroup(item);
+        binder.bindMemberFields(form);
+
+        assertTrue("Jack".equals(form.firstName.getValue()));
+    }
+
+    @Test
+    public void perfectMatchPriority() {
+        PropertysetItem item = new PropertysetItem();
+        item.addItemProperty("first_name", new ObjectProperty<String>(
+                "Not this"));
+        item.addItemProperty("firstName", new ObjectProperty<String>("This"));
+
+        class MyForm extends FormLayout {
+            TextField firstName = new TextField("First name");
+
+            public MyForm() {
+                // should bind to the firstName property, not first_name property
+                addComponent(firstName);
+            }
+        }
+
+        MyForm form = new MyForm();
+
+        FieldGroup binder = new FieldGroup(item);
+        binder.bindMemberFields(form);
+
+        assertTrue("This".equals(form.firstName.getValue()));
+    }
+
+}
\ No newline at end of file