21
21
import java .util .Collection ;
22
22
import java .util .Collections ;
23
23
import java .util .LinkedHashMap ;
24
+ import java .util .LinkedList ;
24
25
import java .util .List ;
25
26
import java .util .Map ;
26
27
46
47
*/
47
48
public class RelaxedDataBinder extends DataBinder {
48
49
50
+ private static final Object BLANK = new Object ();
51
+
49
52
private String namePrefix ;
50
53
51
54
private boolean ignoreNestedProperties ;
@@ -112,11 +115,10 @@ public void initBeanPropertyAccess() {
112
115
113
116
@ Override
114
117
protected void doBind (MutablePropertyValues propertyValues ) {
115
- propertyValues = modifyProperties (propertyValues , getTarget ());
116
118
// Harmless additional property editor comes in very handy sometimes...
117
119
getPropertyEditorRegistry ().registerCustomEditor (InetAddress .class ,
118
120
new InetAddressEditor ());
119
- super .doBind (propertyValues );
121
+ super .doBind (modifyProperties ( propertyValues , getTarget ()) );
120
122
}
121
123
122
124
/**
@@ -129,22 +131,52 @@ protected void doBind(MutablePropertyValues propertyValues) {
129
131
*/
130
132
private MutablePropertyValues modifyProperties (MutablePropertyValues propertyValues ,
131
133
Object target ) {
132
-
133
134
propertyValues = getPropertyValuesForNamePrefix (propertyValues );
134
-
135
135
if (target instanceof MapHolder ) {
136
136
propertyValues = addMapPrefix (propertyValues );
137
137
}
138
-
139
138
BeanWrapper wrapper = new BeanWrapperImpl (target );
140
139
wrapper .setConversionService (new RelaxedConversionService (getConversionService ()));
141
140
wrapper .setAutoGrowNestedPaths (true );
141
+ List <PropertyValue > sortedValues = new ArrayList <PropertyValue >();
142
+ List <String > sortedNames = getSortedPropertyNames (propertyValues );
143
+ for (String name : sortedNames ) {
144
+ sortedValues .add (modifyProperty (wrapper ,
145
+ propertyValues .getPropertyValue (name )));
146
+ }
147
+ return new MutablePropertyValues (sortedValues );
148
+ }
149
+
150
+ private List <String > getSortedPropertyNames (MutablePropertyValues propertyValues ) {
151
+ List <String > names = new LinkedList <String >();
152
+ for (PropertyValue propertyValue : propertyValues .getPropertyValueList ()) {
153
+ names .add (propertyValue .getName ());
154
+ }
155
+ sortPropertyNames (names );
156
+ return names ;
157
+ }
142
158
143
- List <PropertyValue > list = propertyValues .getPropertyValueList ();
144
- for (int i = 0 ; i < list .size (); i ++) {
145
- modifyProperty (propertyValues , wrapper , list .get (i ), i );
159
+ /**
160
+ * Sort by name so that parent properties get processed first (e.g. 'foo.bar' before
161
+ * 'foo.bar.spam'). Don't use Collections.sort() because the order might be
162
+ * significant for other property names (it shouldn't be but who knows what people
163
+ * might be relying on, e.g. HSQL has a JDBCXADataSource where "databaseName" is a
164
+ * synonym for "url").
165
+ * @param names the names to sort
166
+ */
167
+ private void sortPropertyNames (List <String > names ) {
168
+ for (String name : new ArrayList <String >(names )) {
169
+ int propertyIndex = names .indexOf (name );
170
+ BeanPath path = new BeanPath (name );
171
+ for (String prefix : path .prefixes ()) {
172
+ int prefixIndex = names .indexOf (prefix );
173
+ if (prefixIndex >= propertyIndex ) {
174
+ // The child property has a parent in the list in the wrong order
175
+ names .remove (name );
176
+ names .add (prefixIndex , name );
177
+ }
178
+ }
146
179
}
147
- return propertyValues ;
148
180
}
149
181
150
182
private MutablePropertyValues addMapPrefix (MutablePropertyValues propertyValues ) {
@@ -175,14 +207,13 @@ private MutablePropertyValues getPropertyValuesForNamePrefix(
175
207
return rtn ;
176
208
}
177
209
178
- private void modifyProperty (MutablePropertyValues propertyValues , BeanWrapper target ,
179
- PropertyValue propertyValue , int index ) {
180
- String oldName = propertyValue .getName ();
181
- String name = normalizePath (target , oldName );
182
- if (!name .equals (oldName )) {
183
- propertyValues .setPropertyValueAt (
184
- new PropertyValue (name , propertyValue .getValue ()), index );
210
+ private PropertyValue modifyProperty (BeanWrapper target , PropertyValue propertyValue ) {
211
+ String name = propertyValue .getName ();
212
+ String normalizedName = normalizePath (target , name );
213
+ if (!normalizedName .equals (name )) {
214
+ return new PropertyValue (normalizedName , propertyValue .getValue ());
185
215
}
216
+ return propertyValue ;
186
217
}
187
218
188
219
/**
@@ -214,15 +245,9 @@ private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
214
245
String name = path .prefix (index );
215
246
TypeDescriptor descriptor = wrapper .getPropertyTypeDescriptor (name );
216
247
if (descriptor == null || descriptor .isMap ()) {
217
- if (descriptor != null ) {
218
- TypeDescriptor valueDescriptor = descriptor .getMapValueTypeDescriptor ();
219
- if (valueDescriptor != null ) {
220
- Class <?> valueType = valueDescriptor .getObjectType ();
221
- if (valueType != null
222
- && CharSequence .class .isAssignableFrom (valueType )) {
223
- path .collapseKeys (index );
224
- }
225
- }
248
+ if (isMapValueStringType (descriptor )
249
+ || isBlanked (wrapper , name , path .name (index ))) {
250
+ path .collapseKeys (index );
226
251
}
227
252
path .mapIndex (index );
228
253
extendMapIfNecessary (wrapper , path , index );
@@ -231,16 +256,43 @@ else if (descriptor.isCollection()) {
231
256
extendCollectionIfNecessary (wrapper , path , index );
232
257
}
233
258
else if (descriptor .getType ().equals (Object .class )) {
259
+ if (isBlanked (wrapper , name , path .name (index ))) {
260
+ path .collapseKeys (index );
261
+ }
234
262
path .mapIndex (index );
235
- String next = path .prefix (index + 1 );
236
- if (wrapper .getPropertyValue (next ) == null ) {
237
- wrapper .setPropertyValue (next , new LinkedHashMap <String , Object >());
263
+ if (path .isLastNode (index )) {
264
+ wrapper .setPropertyValue (path .toString (), BLANK );
265
+ }
266
+ else {
267
+ String next = path .prefix (index + 1 );
268
+ if (wrapper .getPropertyValue (next ) == null ) {
269
+ wrapper .setPropertyValue (next , new LinkedHashMap <String , Object >());
270
+ }
238
271
}
239
272
}
240
-
241
273
return initializePath (wrapper , path , index );
242
274
}
243
275
276
+ private boolean isMapValueStringType (TypeDescriptor descriptor ) {
277
+ if (descriptor == null || descriptor .getMapValueTypeDescriptor () == null ) {
278
+ return false ;
279
+ }
280
+ Class <?> valueType = descriptor .getMapValueTypeDescriptor ().getObjectType ();
281
+ return (valueType != null && CharSequence .class .isAssignableFrom (valueType ));
282
+ }
283
+
284
+ @ SuppressWarnings ("rawtypes" )
285
+ private boolean isBlanked (BeanWrapper wrapper , String propertyName , String key ) {
286
+ Object value = (wrapper .isReadableProperty (propertyName ) ? wrapper
287
+ .getPropertyValue (propertyName ) : null );
288
+ if (value instanceof Map ) {
289
+ if (((Map ) value ).get (key ) == BLANK ) {
290
+ return true ;
291
+ }
292
+ }
293
+ return false ;
294
+ }
295
+
244
296
private void extendCollectionIfNecessary (BeanWrapper wrapper , BeanPath path , int index ) {
245
297
String name = path .prefix (index );
246
298
TypeDescriptor elementDescriptor = wrapper .getPropertyTypeDescriptor (name )
@@ -282,6 +334,9 @@ private void extendMapIfNecessary(BeanWrapper wrapper, BeanPath path, int index)
282
334
if (descriptor .isCollection ()) {
283
335
extend = new ArrayList <Object >();
284
336
}
337
+ if (descriptor .getType ().equals (Object .class ) && path .isLastNode (index )) {
338
+ extend = BLANK ;
339
+ }
285
340
wrapper .setPropertyValue (extensionName , extend );
286
341
}
287
342
@@ -382,6 +437,18 @@ public BeanPath(String path) {
382
437
this .nodes = splitPath (path );
383
438
}
384
439
440
+ public List <String > prefixes () {
441
+ List <String > prefixes = new ArrayList <String >();
442
+ for (int index = 1 ; index < this .nodes .size (); index ++) {
443
+ prefixes .add (prefix (index ));
444
+ }
445
+ return prefixes ;
446
+ }
447
+
448
+ public boolean isLastNode (int index ) {
449
+ return index >= this .nodes .size () - 1 ;
450
+ }
451
+
385
452
private List <PathNode > splitPath (String path ) {
386
453
List <PathNode > nodes = new ArrayList <PathNode >();
387
454
for (String name : StringUtils .delimitedListToStringArray (path , "." )) {
0 commit comments