@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collection ;
import java.util.Collection ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.LinkedHashMap ;
import java.util.LinkedHashMap ;
import java.util.LinkedList ;
import java.util.List ;
import java.util.List ;
import java.util.Map ;
import java.util.Map ;
@ -46,6 +47,8 @@ import org.springframework.validation.DataBinder;
* /
* /
public class RelaxedDataBinder extends DataBinder {
public class RelaxedDataBinder extends DataBinder {
private static final Object BLANK = new Object ( ) ;
private String namePrefix ;
private String namePrefix ;
private boolean ignoreNestedProperties ;
private boolean ignoreNestedProperties ;
@ -112,11 +115,10 @@ public class RelaxedDataBinder extends DataBinder {
@Override
@Override
protected void doBind ( MutablePropertyValues propertyValues ) {
protected void doBind ( MutablePropertyValues propertyValues ) {
propertyValues = modifyProperties ( propertyValues , getTarget ( ) ) ;
// Harmless additional property editor comes in very handy sometimes...
// Harmless additional property editor comes in very handy sometimes...
getPropertyEditorRegistry ( ) . registerCustomEditor ( InetAddress . class ,
getPropertyEditorRegistry ( ) . registerCustomEditor ( InetAddress . class ,
new InetAddressEditor ( ) ) ;
new InetAddressEditor ( ) ) ;
super . doBind ( propertyValues) ;
super . doBind ( modifyProperties( propertyValues, getTarget ( ) ) ) ;
}
}
/ * *
/ * *
@ -129,22 +131,52 @@ public class RelaxedDataBinder extends DataBinder {
* /
* /
private MutablePropertyValues modifyProperties ( MutablePropertyValues propertyValues ,
private MutablePropertyValues modifyProperties ( MutablePropertyValues propertyValues ,
Object target ) {
Object target ) {
propertyValues = getPropertyValuesForNamePrefix ( propertyValues ) ;
propertyValues = getPropertyValuesForNamePrefix ( propertyValues ) ;
if ( target instanceof MapHolder ) {
if ( target instanceof MapHolder ) {
propertyValues = addMapPrefix ( propertyValues ) ;
propertyValues = addMapPrefix ( propertyValues ) ;
}
}
BeanWrapper wrapper = new BeanWrapperImpl ( target ) ;
BeanWrapper wrapper = new BeanWrapperImpl ( target ) ;
wrapper . setConversionService ( new RelaxedConversionService ( getConversionService ( ) ) ) ;
wrapper . setConversionService ( new RelaxedConversionService ( getConversionService ( ) ) ) ;
wrapper . setAutoGrowNestedPaths ( true ) ;
wrapper . setAutoGrowNestedPaths ( true ) ;
List < PropertyValue > sortedValues = new ArrayList < PropertyValue > ( ) ;
List < String > sortedNames = getSortedPropertyNames ( propertyValues ) ;
for ( String name : sortedNames ) {
sortedValues . add ( modifyProperty ( wrapper ,
propertyValues . getPropertyValue ( name ) ) ) ;
}
return new MutablePropertyValues ( sortedValues ) ;
}
private List < String > getSortedPropertyNames ( MutablePropertyValues propertyValues ) {
List < String > names = new LinkedList < String > ( ) ;
for ( PropertyValue propertyValue : propertyValues . getPropertyValueList ( ) ) {
names . add ( propertyValue . getName ( ) ) ;
}
sortPropertyNames ( names ) ;
return names ;
}
List < PropertyValue > list = propertyValues . getPropertyValueList ( ) ;
/ * *
for ( int i = 0 ; i < list . size ( ) ; i + + ) {
* Sort by name so that parent properties get processed first ( e . g . ' foo . bar ' before
modifyProperty ( propertyValues , wrapper , list . get ( i ) , i ) ;
* ' foo . bar . spam ' ) . Don ' t use Collections . sort ( ) because the order might be
* significant for other property names ( it shouldn ' t be but who knows what people
* might be relying on , e . g . HSQL has a JDBCXADataSource where "databaseName" is a
* synonym for "url" ) .
* @param names the names to sort
* /
private void sortPropertyNames ( List < String > names ) {
for ( String name : new ArrayList < String > ( names ) ) {
int propertyIndex = names . indexOf ( name ) ;
BeanPath path = new BeanPath ( name ) ;
for ( String prefix : path . prefixes ( ) ) {
int prefixIndex = names . indexOf ( prefix ) ;
if ( prefixIndex > = propertyIndex ) {
// The child property has a parent in the list in the wrong order
names . remove ( name ) ;
names . add ( prefixIndex , name ) ;
}
}
}
}
return propertyValues ;
}
}
private MutablePropertyValues addMapPrefix ( MutablePropertyValues propertyValues ) {
private MutablePropertyValues addMapPrefix ( MutablePropertyValues propertyValues ) {
@ -175,14 +207,13 @@ public class RelaxedDataBinder extends DataBinder {
return rtn ;
return rtn ;
}
}
private void modifyProperty ( MutablePropertyValues propertyValues , BeanWrapper target ,
private PropertyValue modifyProperty ( BeanWrapper target , PropertyValue propertyValue ) {
PropertyValue propertyValue , int index ) {
String name = propertyValue . getName ( ) ;
String oldName = propertyValue . getName ( ) ;
String normalizedName = normalizePath ( target , name ) ;
String name = normalizePath ( target , oldName ) ;
if ( ! normalizedName . equals ( name ) ) {
if ( ! name . equals ( oldName ) ) {
return new PropertyValue ( normalizedName , propertyValue . getValue ( ) ) ;
propertyValues . setPropertyValueAt (
new PropertyValue ( name , propertyValue . getValue ( ) ) , index ) ;
}
}
return propertyValue ;
}
}
/ * *
/ * *
@ -214,15 +245,9 @@ public class RelaxedDataBinder extends DataBinder {
String name = path . prefix ( index ) ;
String name = path . prefix ( index ) ;
TypeDescriptor descriptor = wrapper . getPropertyTypeDescriptor ( name ) ;
TypeDescriptor descriptor = wrapper . getPropertyTypeDescriptor ( name ) ;
if ( descriptor = = null | | descriptor . isMap ( ) ) {
if ( descriptor = = null | | descriptor . isMap ( ) ) {
if ( descriptor ! = null ) {
if ( isMapValueStringType ( descriptor )
TypeDescriptor valueDescriptor = descriptor . getMapValueTypeDescriptor ( ) ;
| | isBlanked ( wrapper , name , path . name ( index ) ) ) {
if ( valueDescriptor ! = null ) {
path . collapseKeys ( index ) ;
Class < ? > valueType = valueDescriptor . getObjectType ( ) ;
if ( valueType ! = null
& & CharSequence . class . isAssignableFrom ( valueType ) ) {
path . collapseKeys ( index ) ;
}
}
}
}
path . mapIndex ( index ) ;
path . mapIndex ( index ) ;
extendMapIfNecessary ( wrapper , path , index ) ;
extendMapIfNecessary ( wrapper , path , index ) ;
@ -231,16 +256,43 @@ public class RelaxedDataBinder extends DataBinder {
extendCollectionIfNecessary ( wrapper , path , index ) ;
extendCollectionIfNecessary ( wrapper , path , index ) ;
}
}
else if ( descriptor . getType ( ) . equals ( Object . class ) ) {
else if ( descriptor . getType ( ) . equals ( Object . class ) ) {
if ( isBlanked ( wrapper , name , path . name ( index ) ) ) {
path . collapseKeys ( index ) ;
}
path . mapIndex ( index ) ;
path . mapIndex ( index ) ;
String next = path . prefix ( index + 1 ) ;
if ( path . isLastNode ( index ) ) {
if ( wrapper . getPropertyValue ( next ) = = null ) {
wrapper . setPropertyValue ( path . toString ( ) , BLANK ) ;
wrapper . setPropertyValue ( next , new LinkedHashMap < String , Object > ( ) ) ;
}
else {
String next = path . prefix ( index + 1 ) ;
if ( wrapper . getPropertyValue ( next ) = = null ) {
wrapper . setPropertyValue ( next , new LinkedHashMap < String , Object > ( ) ) ;
}
}
}
}
}
return initializePath ( wrapper , path , index ) ;
return initializePath ( wrapper , path , index ) ;
}
}
private boolean isMapValueStringType ( TypeDescriptor descriptor ) {
if ( descriptor = = null | | descriptor . getMapValueTypeDescriptor ( ) = = null ) {
return false ;
}
Class < ? > valueType = descriptor . getMapValueTypeDescriptor ( ) . getObjectType ( ) ;
return ( valueType ! = null & & CharSequence . class . isAssignableFrom ( valueType ) ) ;
}
@SuppressWarnings ( "rawtypes" )
private boolean isBlanked ( BeanWrapper wrapper , String propertyName , String key ) {
Object value = ( wrapper . isReadableProperty ( propertyName ) ? wrapper
. getPropertyValue ( propertyName ) : null ) ;
if ( value instanceof Map ) {
if ( ( ( Map ) value ) . get ( key ) = = BLANK ) {
return true ;
}
}
return false ;
}
private void extendCollectionIfNecessary ( BeanWrapper wrapper , BeanPath path , int index ) {
private void extendCollectionIfNecessary ( BeanWrapper wrapper , BeanPath path , int index ) {
String name = path . prefix ( index ) ;
String name = path . prefix ( index ) ;
TypeDescriptor elementDescriptor = wrapper . getPropertyTypeDescriptor ( name )
TypeDescriptor elementDescriptor = wrapper . getPropertyTypeDescriptor ( name )
@ -282,6 +334,9 @@ public class RelaxedDataBinder extends DataBinder {
if ( descriptor . isCollection ( ) ) {
if ( descriptor . isCollection ( ) ) {
extend = new ArrayList < Object > ( ) ;
extend = new ArrayList < Object > ( ) ;
}
}
if ( descriptor . getType ( ) . equals ( Object . class ) & & path . isLastNode ( index ) ) {
extend = BLANK ;
}
wrapper . setPropertyValue ( extensionName , extend ) ;
wrapper . setPropertyValue ( extensionName , extend ) ;
}
}
@ -382,6 +437,18 @@ public class RelaxedDataBinder extends DataBinder {
this . nodes = splitPath ( path ) ;
this . nodes = splitPath ( path ) ;
}
}
public List < String > prefixes ( ) {
List < String > prefixes = new ArrayList < String > ( ) ;
for ( int index = 1 ; index < this . nodes . size ( ) ; index + + ) {
prefixes . add ( prefix ( index ) ) ;
}
return prefixes ;
}
public boolean isLastNode ( int index ) {
return index > = this . nodes . size ( ) - 1 ;
}
private List < PathNode > splitPath ( String path ) {
private List < PathNode > splitPath ( String path ) {
List < PathNode > nodes = new ArrayList < PathNode > ( ) ;
List < PathNode > nodes = new ArrayList < PathNode > ( ) ;
for ( String name : StringUtils . delimitedListToStringArray ( path , "." ) ) {
for ( String name : StringUtils . delimitedListToStringArray ( path , "." ) ) {