diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/bind/RelaxedDataBinder.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/bind/RelaxedDataBinder.java
index a4f590e5fa9..bd3d4fdd06c 100644
--- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/bind/RelaxedDataBinder.java
+++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/bind/RelaxedDataBinder.java
@@ -139,160 +139,35 @@ public class RelaxedDataBinder extends DataBinder {
}
}
+ /**
+ * Normalize a bean property path to a format understood by a BeanWrapper. This is
+ * used so that
+ *
+ * - Fuzzy matching can be employed for bean property names
+ * - Period separators can be used instead of indexing ([...]) for map keys
+ *
+ *
+ * @param wrapper a bean wrapper for the object to bind
+ * @param path the bean path to bind
+ * @return a transformed path with correct bean wrapper syntax
+ */
protected String normalizePath(BeanWrapper wrapper, String path) {
return initializePath(wrapper, new BeanPath(path), 0);
}
- private static class BeanPath {
-
- private List nodes;
-
- public BeanPath(String path) {
- this.nodes = splitPath(path);
- }
-
- public void mapIndex(int index) {
- PathNode node = this.nodes.get(index);
- if (node instanceof PropertyNode) {
- node = ((PropertyNode) node).mapIndex();
- }
- this.nodes.set(index, node);
- }
-
- public String prefix(int index) {
- return range(0, index);
- }
-
- public void rename(int index, String name) {
- this.nodes.get(index).name = name;
- }
-
- public String name(int index) {
- if (index < this.nodes.size()) {
- return this.nodes.get(index).name;
- }
- return null;
- }
-
- public int length() {
- return this.nodes.size();
- }
-
- private String range(int start, int end) {
- StringBuilder builder = new StringBuilder();
- for (int i = start; i < end; i++) {
- PathNode node = this.nodes.get(i);
- builder.append(node);
- }
- if (builder.toString().startsWith(("."))) {
- builder.replace(0, 1, "");
- }
- return builder.toString();
- }
-
- public boolean isArrayIndex(int index) {
- return this.nodes.get(index) instanceof ArrayIndexNode;
- }
-
- public boolean isProperty(int index) {
- return this.nodes.get(index) instanceof PropertyNode;
- }
-
- @Override
- public String toString() {
- return prefix(this.nodes.size());
- }
-
- private static class PathNode {
-
- protected String name;
-
- public PathNode(String name) {
- this.name = name;
- }
-
- }
-
- private static class ArrayIndexNode extends PathNode {
-
- public ArrayIndexNode(String name) {
- super(name);
- }
-
- @Override
- public String toString() {
- return "[" + this.name + "]";
- }
-
- }
-
- private static class MapIndexNode extends PathNode {
-
- public MapIndexNode(String name) {
- super(name);
- }
-
- @Override
- public String toString() {
- return "[" + this.name + "]";
- }
- }
-
- private static class PropertyNode extends PathNode {
-
- public PropertyNode(String name) {
- super(name);
- }
-
- public MapIndexNode mapIndex() {
- return new MapIndexNode(this.name);
- }
-
- @Override
- public String toString() {
- return "." + this.name;
- }
- }
-
- private List splitPath(String path) {
- List nodes = new ArrayList();
- for (String name : StringUtils.delimitedListToStringArray(path, ".")) {
- for (String sub : StringUtils.delimitedListToStringArray(name, "[")) {
- if (StringUtils.hasText(sub)) {
- if (sub.endsWith("]")) {
- sub = sub.substring(0, sub.length() - 1);
- if (sub.matches("[0-9]+")) {
- nodes.add(new ArrayIndexNode(sub));
- }
- else {
- nodes.add(new MapIndexNode(sub));
- }
- }
- else {
- nodes.add(new PropertyNode(sub));
- }
- }
- }
- }
- return nodes;
- }
-
- }
-
private String initializePath(BeanWrapper wrapper, BeanPath path, int index) {
+
String prefix = path.prefix(index);
String key = path.name(index);
- if (key == null) {
- return path.toString();
- }
if (path.isProperty(index)) {
key = getActualPropertyName(wrapper, prefix, key);
path.rename(index, key);
}
- if (index >= path.length() - 1) {
+ if (path.name(++index) == null) {
return path.toString();
}
- String name = path.prefix(++index);
+
+ String name = path.prefix(index);
TypeDescriptor descriptor = wrapper.getPropertyTypeDescriptor(name);
if (descriptor == null || descriptor.isMap()) {
if (descriptor != null) {
@@ -302,20 +177,18 @@ public class RelaxedDataBinder extends DataBinder {
extendMapIfNecessary(wrapper, path, index);
}
else if (descriptor.isCollection()) {
- // TODO: test collection extension
extendCollectionIfNecessary(wrapper, path, index);
}
else if (descriptor.getType().equals(Object.class)) {
path.mapIndex(index);
- name = path.prefix(index + 1);
- if (wrapper.getPropertyValue(name) == null) {
- wrapper.setPropertyValue(name, new LinkedHashMap());
+ String next = path.prefix(index + 1);
+ if (wrapper.getPropertyValue(next) == null) {
+ wrapper.setPropertyValue(next, new LinkedHashMap());
}
}
- if (index < path.length()) {
- return initializePath(wrapper, path, index);
- }
- return path.toString();
+
+ return initializePath(wrapper, path, index);
+
}
private void extendCollectionIfNecessary(BeanWrapper wrapper, BeanPath path, int index) {
@@ -439,4 +312,136 @@ public class RelaxedDataBinder extends DataBinder {
}
}
+ private static class BeanPath {
+
+ private List nodes;
+
+ public BeanPath(String path) {
+ this.nodes = splitPath(path);
+ }
+
+ public void mapIndex(int index) {
+ PathNode node = this.nodes.get(index);
+ if (node instanceof PropertyNode) {
+ node = ((PropertyNode) node).mapIndex();
+ }
+ this.nodes.set(index, node);
+ }
+
+ public String prefix(int index) {
+ return range(0, index);
+ }
+
+ public void rename(int index, String name) {
+ this.nodes.get(index).name = name;
+ }
+
+ public String name(int index) {
+ if (index < this.nodes.size()) {
+ return this.nodes.get(index).name;
+ }
+ return null;
+ }
+
+ private String range(int start, int end) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = start; i < end; i++) {
+ PathNode node = this.nodes.get(i);
+ builder.append(node);
+ }
+ if (builder.toString().startsWith(("."))) {
+ builder.replace(0, 1, "");
+ }
+ return builder.toString();
+ }
+
+ public boolean isArrayIndex(int index) {
+ return this.nodes.get(index) instanceof ArrayIndexNode;
+ }
+
+ public boolean isProperty(int index) {
+ return this.nodes.get(index) instanceof PropertyNode;
+ }
+
+ @Override
+ public String toString() {
+ return prefix(this.nodes.size());
+ }
+
+ private static class PathNode {
+
+ protected String name;
+
+ public PathNode(String name) {
+ this.name = name;
+ }
+
+ }
+
+ private static class ArrayIndexNode extends PathNode {
+
+ public ArrayIndexNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + this.name + "]";
+ }
+
+ }
+
+ private static class MapIndexNode extends PathNode {
+
+ public MapIndexNode(String name) {
+ super(name);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + this.name + "]";
+ }
+ }
+
+ private static class PropertyNode extends PathNode {
+
+ public PropertyNode(String name) {
+ super(name);
+ }
+
+ public MapIndexNode mapIndex() {
+ return new MapIndexNode(this.name);
+ }
+
+ @Override
+ public String toString() {
+ return "." + this.name;
+ }
+ }
+
+ private List splitPath(String path) {
+ List nodes = new ArrayList();
+ for (String name : StringUtils.delimitedListToStringArray(path, ".")) {
+ for (String sub : StringUtils.delimitedListToStringArray(name, "[")) {
+ if (StringUtils.hasText(sub)) {
+ if (sub.endsWith("]")) {
+ sub = sub.substring(0, sub.length() - 1);
+ if (sub.matches("[0-9]+")) {
+ nodes.add(new ArrayIndexNode(sub));
+ }
+ else {
+ nodes.add(new MapIndexNode(sub));
+ }
+ }
+ else {
+ nodes.add(new PropertyNode(sub));
+ }
+ }
+ }
+ }
+ return nodes;
+ }
+
+ }
+
}
diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/bind/BindingPreparationTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/bind/BindingPreparationTests.java
index 612c9e03343..3bed4c66c08 100644
--- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/bind/BindingPreparationTests.java
+++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/bind/BindingPreparationTests.java
@@ -132,6 +132,28 @@ public class BindingPreparationTests {
assertNotNull(wrapper.getPropertyValue("nested[foo]"));
}
+ @Test
+ public void testAutoGrowListOfMaps() throws Exception {
+ TargetWithNestedListOfMaps target = new TargetWithNestedListOfMaps();
+ BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
+ wrapper.setAutoGrowNestedPaths(true);
+ RelaxedDataBinder binder = new RelaxedDataBinder(target);
+ binder.normalizePath(wrapper, "nested[0][foo]");
+ assertNotNull(wrapper.getPropertyValue("nested"));
+ assertNotNull(wrapper.getPropertyValue("nested[0]"));
+ }
+
+ @Test
+ public void testAutoGrowListOfLists() throws Exception {
+ TargetWithNestedListOfLists target = new TargetWithNestedListOfLists();
+ BeanWrapperImpl wrapper = new BeanWrapperImpl(target);
+ wrapper.setAutoGrowNestedPaths(true);
+ RelaxedDataBinder binder = new RelaxedDataBinder(target);
+ binder.normalizePath(wrapper, "nested[0][1]");
+ assertNotNull(wrapper.getPropertyValue("nested"));
+ assertNotNull(wrapper.getPropertyValue("nested[0][1]"));
+ }
+
@Test
public void testBeanWrapperCreatesNewNestedMaps() throws Exception {
TargetWithNestedMap target = new TargetWithNestedMap();
@@ -213,6 +235,30 @@ public class BindingPreparationTests {
}
}
+ public static class TargetWithNestedListOfMaps {
+ private List