Announcement Announcement Module
Collapse
No announcement yet.
registerCustomEditor to a collection of custom classes Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • registerCustomEditor to a collection of custom classes

    I have a collection of a custom class (with it's own custom PropertyEditor) in my command class.

    My JSP has a multi-select box. I want the values selected to be converted into a List of my custom class objects. I followed this posting:

    http://forum.springframework.org/showthread.php?t=17646

    Which works fine when using an array as the command property, but fails when using a collection.

    My Command Class looks like this:
    Code:
    public class MyBackingObject {
    
        List myClasses;
        // accessors
    }
    My Form Controller registers the custom property editor like this:
    Code:
    ...
    binder.registerCustomEditor(
        MyClass.class,
        "myClasses",
        new MyClassPropertyEditor());
    ...
    According to the JavaDoc for DataBinder.registerCustomEditor:
    If the field denotes an array or Collection, the PropertyEditor will get applied either to the array/Collection itself (the PropertyEditor has to create an array or Collection value) or to each element (the PropertyEditor has to create the element type), depending on the specified required type.
    I interpreted this as meaning that since the field I specified ("myClasses") is a collection, but the class I specified is not a collection "MyClass.class", then DataBinder would use the PropertyEditor to create the elements. But this is not the case. DataBinder basically ignores my customEditor because the type registered "Role.class" doesn't match the actual type "List.class". The list ends up getting populated with Strings, and I end up with a ClassCastException.

    So my question is, am I interpreting the JavaDoc right?


    Note: as a work around, I created a custom CustomCollectionEditor that takes an instance of a PropertyEditor which it uses to populate a collection.
    Code:
    public class MyCollectionPropertyEditor extends CustomCollectionEditor {
    
        PropertyEditor propEditor;
        
        public CollectionPropertyEditor(Class collectionType, PropertyEditor propEditor) {
            super(collectionType);
            this.propEditor = propEditor;
        }
    
        protected Object convertElement(Object element) {
            Object returnMe = null;
            if(element instanceof String) {
                //not thread safe, is that a problem?
                propEditor.setAsText((String)element);
                returnMe = propEditor.getValue();
            }
            else {
                returnMe = element;
            }
            return returnMe;
        }
    }

    And I register like this:
    Code:
    ...
    binder.registerCustomEditor(
        List.class,
        "myClasses",
        new MyCollectionPropertyEditor(
                 List.class, new MyClassPropertyEditor()));
    ...
    Any opinions on this solution?
    Last edited by robyn; May 14th, 2006, 10:59 AM.

  • #2
    SELECT form elements are not implicitly treated as collectio

    This is a very subtle issue that relates ultimately to the way that form data is marshalled into strings by the browser.

    If you have form data whose path indicates that the value is part of a collection, it will be handled as you expect in your post. Example:
    Code:
    <spring&#58;bind path="command.mycollection&#91;3&#93;">
      <input type="text" name="$&#123;status.expression&#125;">
    </spring&#58;bind>
    The name will ultimately resolve to mycollection[3] and it's clear from this notation that the value should be inserted by a PropertyEditor into the 3rd element of the mycollection collection. Each value of the mycollection object will correspond to a single request parameterName / Value pair.


    I falsely believed that multiple-select form input elements would be handled by the DataBinder similarly, as you did. Keep in mind that this kind of form input element:
    Code:
    <spring&#58;bind path="command.mycollection">
      <select name="$&#123;status.expression&#125;">
    ....
      </select>
    </spring&#58;bind>
    Will result in only one parameter being passed back to the server: a CSV list of the selected elements. It will be a single parameter name / value pair, and as a GET parameter it would look like this:
    Code:
      http&#58;//foo.com?mycollection=22,234,42,2,234,11,232
    There is no way to register a CustomPropertyEditor that will operate on each of the elements implicitly, as someone must first unmarshall that CSV string into individual values.

    You can of course register an editor that accepts a string of CSV values as input, unmarshalls the values, and then operates on each value individually. You can find various solutions around this forum that do exactly that.


    You would likely register such an editor like so:
    Code:
      registerCustomEditor&#40;Set.class, "myImplicitCollection", new CustomUnMarshallingMojoCollectionEditor&#40;&#41;&#41;;

    Comment


    • #3
      I made a sweet class that munges SELECT form element selections into Collections of Values in a fairly generic way.

      I took a look at the way Jurgen coded CustomNumberEditor and based it on that, so I have some faith in the way the code is structured.

      I can't vouch for it being 100% bug free but it works for me.

      Code:
      import org.springframework.util.StringUtils;
      import org.springframework.util.NumberUtils;
      
      import java.beans.PropertyEditorSupport;
      import java.util.*;
      import java.text.NumberFormat;
      
      /**
       * Created by IntelliJ IDEA.
       * User&#58; afleming
       * Date&#58; Oct 9, 2005
       * Time&#58; 6&#58;07&#58;53 PM
       * Copyright Adam Fleming 2005
       *
       * Based on Juergen Hoeller's mojo-rific CustomNumberEditor
       * This class is a property editor that converts between comma-separated list of numbers and a java collection
       * of said numbers.
       *
       * This is exceedingly useful for dealing with Multiple-Select Form Elements
       *
       * It allows for a user-defined numberFormat, much like Jurgen's CustomNumberEditor
       *
       * @see org.springframework.beans.propertyeditors.CustomNumberEditor
       * http&#58;//jsourcery.com/output/sourceforge/spring/1.2.1/org/springframework/beans/propertyeditors/CustomNumberEditor.source.html
       */
      public class CollectionOfNumbersEditor extends PropertyEditorSupport &#123;
         private final Class numberClass;
         private final Collection collectionInstance;
         private final NumberFormat numberFormat;
         public static final String separator = ",";
      
         public CollectionOfNumbersEditor&#40; Class collectionClass, Class numberClass&#41; &#123;
            super&#40;&#41;;
            collectionInstance = checkClasses&#40;numberClass, collectionClass&#41;;
            this.numberClass = numberClass;
            this.numberFormat = null;
         &#125;
      
         public CollectionOfNumbersEditor&#40;Class collectionClass, Class numberClass, NumberFormat numberFormat&#41; &#123;
            super&#40;&#41;;
            collectionInstance = checkClasses&#40;numberClass, collectionClass&#41;;
            this.numberClass = numberClass;
            this.numberFormat = numberFormat;
         &#125;
      
         private Collection checkClasses&#40;Class numberClass, Class collectionClass&#41; throws IllegalArgumentException&#123;
            Collection collectionInstance;
            // Ensure we have a subclass of Number
            if &#40;numberClass == null || !Number.class.isAssignableFrom&#40;numberClass&#41;&#41; &#123;
               throw new IllegalArgumentException&#40;"Number class must be a subclass of "+Number.class.getName&#40;&#41;&#41;;
            &#125;
            // Ensure we have a subclass of Collection
            if &#40;collectionClass == null || !Collection.class.isAssignableFrom&#40;collectionClass&#41;&#41; &#123;
               throw new IllegalArgumentException&#40;"Collection class must be a subclass of "+Collection.class.getName&#40;&#41;&#41;;
            &#125;
            // Ensure we have a instantiatable subclass of Collection
            try &#123;
               collectionInstance = &#40;Collection&#41; collectionClass.newInstance&#40;&#41;;
            &#125; catch &#40;InstantiationException e&#41; &#123;
               throw new IllegalArgumentException&#40;"Could no instantiate "+collectionClass.getName&#40;&#41;+".\n"+
                     "The Collection class must be instantiatable."&#41;;
            &#125; catch &#40;IllegalAccessException e&#41; &#123;
               throw new IllegalArgumentException&#40;"Could no instantiate "+collectionClass.getName&#40;&#41;+".\n"+
                     "The Collection class must be instantiatable."&#41;;
            &#125;
            return collectionInstance;
         &#125;
      
         /**
          * This method takes a CSV list of values as input and creates a collection of the specified collection type,
          * containing elements of the specified numberType
          */
         public void setAsText&#40;String string&#41; throws IllegalArgumentException &#123;
            // Set the Value.  We start off with an empty collection, and
            // can continue working with the collection instance.
            setValue&#40;collectionInstance&#41;;
            if&#40;!StringUtils.hasText&#40;string&#41;&#41;return;
            for &#40;StringTokenizer tk = new StringTokenizer&#40;string, separator&#41;; tk.hasMoreTokens&#40;&#41;;&#41; &#123;
               Number n = parseNumber&#40;tk.nextToken&#40;&#41;&#41;;
               if&#40;n == null&#41;continue;
               collectionInstance.add&#40; n &#41;;
            &#125;
         &#125;
      
         /**
          * This method creates a CSV of all the values in the underlying collection
          */
         public final String getAsText&#40;&#41; &#123;
            if&#40;getValue&#40;&#41;==null&#41; return "";
            StringBuilder sb = new StringBuilder&#40;&#41;;
            for &#40;Iterator iterator = &#40;&#40;Collection&#41;getValue&#40;&#41;&#41;.iterator&#40;&#41;; iterator.hasNext&#40;&#41;;&#41; &#123;
               if&#40;numberFormat==null&#41;
                  sb.append&#40;iterator.next&#40;&#41;&#41;;
               else
                  sb.append&#40;numberFormat.format&#40;iterator.next&#40;&#41;&#41;&#41;;
               if&#40;iterator.hasNext&#40;&#41;&#41; sb.append&#40;separator&#41;;
            &#125;
            return sb.toString&#40;&#41;;
         &#125;
      
         private Number parseNumber&#40;String text&#41;&#123;
            if&#40;!StringUtils.hasText&#40;text&#41;&#41; return null;
            if&#40;numberFormat==null&#41;
               return NumberUtils.parseNumber&#40;text, this.numberClass&#41;;
            return NumberUtils.parseNumber&#40;text, this.numberClass, numberFormat&#41;;
         &#125;
      &#125;

      Comment


      • #4
        how do you solve the problem???? i can't do that too!
        look at this
        http://forum.springframework.org/showthread.php?t=19808

        Comment

        Working...
        X