Support for indexing into any Collection/Iterable
Closes gh-907
This commit is contained in:
parent
e048b093b5
commit
c20a2e4763
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -30,7 +30,6 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
@ -657,22 +656,32 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
|
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
|
||||||
value = list.get(index);
|
value = list.get(index);
|
||||||
}
|
}
|
||||||
else if (value instanceof Set set) {
|
else if (value instanceof Iterable iterable) {
|
||||||
// Apply index to Iterator in case of a Set.
|
// Apply index to Iterator in case of a Set/Collection/Iterable.
|
||||||
int index = Integer.parseInt(key);
|
int index = Integer.parseInt(key);
|
||||||
if (index < 0 || index >= set.size()) {
|
if (value instanceof Collection<?> coll) {
|
||||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
if (index < 0 || index >= coll.size()) {
|
||||||
"Cannot get element with index " + index + " from Set of size " +
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
set.size() + ", accessed using property path '" + propertyName + "'");
|
"Cannot get element with index " + index + " from Collection of size " +
|
||||||
|
coll.size() + ", accessed using property path '" + propertyName + "'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Iterator<Object> it = set.iterator();
|
Iterator<Object> it = iterable.iterator();
|
||||||
for (int j = 0; it.hasNext(); j++) {
|
boolean found = false;
|
||||||
|
int currIndex = 0;
|
||||||
|
for (; it.hasNext(); currIndex++) {
|
||||||
Object elem = it.next();
|
Object elem = it.next();
|
||||||
if (j == index) {
|
if (currIndex == index) {
|
||||||
value = elem;
|
value = elem;
|
||||||
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!found) {
|
||||||
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
|
"Cannot get element with index " + index + " from Iterable of size " +
|
||||||
|
currIndex + ", accessed using property path '" + propertyName + "'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (value instanceof Map map) {
|
else if (value instanceof Map map) {
|
||||||
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
|
||||||
|
@ -685,13 +694,17 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
|
||||||
else {
|
else {
|
||||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Property referenced in indexed property path '" + propertyName +
|
"Property referenced in indexed property path '" + propertyName +
|
||||||
"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
|
"' is neither an array nor a List/Set/Collection/Iterable nor a Map; " +
|
||||||
|
"returned value was [" + value + "]");
|
||||||
}
|
}
|
||||||
indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);
|
indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
catch (InvalidPropertyException ex) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
catch (IndexOutOfBoundsException ex) {
|
catch (IndexOutOfBoundsException ex) {
|
||||||
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
|
||||||
"Index of out of bounds in property path '" + propertyName + "'", ex);
|
"Index of out of bounds in property path '" + propertyName + "'", ex);
|
||||||
|
|
|
@ -139,12 +139,14 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
assertThat(accessor.isReadableProperty("list")).isTrue();
|
assertThat(accessor.isReadableProperty("list")).isTrue();
|
||||||
assertThat(accessor.isReadableProperty("set")).isTrue();
|
assertThat(accessor.isReadableProperty("set")).isTrue();
|
||||||
assertThat(accessor.isReadableProperty("map")).isTrue();
|
assertThat(accessor.isReadableProperty("map")).isTrue();
|
||||||
|
assertThat(accessor.isReadableProperty("myTestBeans")).isTrue();
|
||||||
assertThat(accessor.isReadableProperty("xxx")).isFalse();
|
assertThat(accessor.isReadableProperty("xxx")).isFalse();
|
||||||
|
|
||||||
assertThat(accessor.isWritableProperty("array")).isTrue();
|
assertThat(accessor.isWritableProperty("array")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("list")).isTrue();
|
assertThat(accessor.isWritableProperty("list")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("set")).isTrue();
|
assertThat(accessor.isWritableProperty("set")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("map")).isTrue();
|
assertThat(accessor.isWritableProperty("map")).isTrue();
|
||||||
|
assertThat(accessor.isWritableProperty("myTestBeans")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("xxx")).isFalse();
|
assertThat(accessor.isWritableProperty("xxx")).isFalse();
|
||||||
|
|
||||||
assertThat(accessor.isReadableProperty("array[0]")).isTrue();
|
assertThat(accessor.isReadableProperty("array[0]")).isTrue();
|
||||||
|
@ -159,6 +161,8 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
|
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
|
||||||
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
|
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
|
||||||
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
|
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
|
||||||
|
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||||
|
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||||
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
|
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
|
||||||
|
|
||||||
assertThat(accessor.isWritableProperty("array[0]")).isTrue();
|
assertThat(accessor.isWritableProperty("array[0]")).isTrue();
|
||||||
|
@ -173,6 +177,8 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
|
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
|
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
|
||||||
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
|
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
|
||||||
|
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
|
||||||
|
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
|
||||||
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
|
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1388,6 +1394,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
|
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
|
||||||
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
|
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
|
||||||
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
|
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
|
||||||
|
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZ");
|
||||||
|
|
||||||
MutablePropertyValues pvs = new MutablePropertyValues();
|
MutablePropertyValues pvs = new MutablePropertyValues();
|
||||||
pvs.add("array[0].name", "name5");
|
pvs.add("array[0].name", "name5");
|
||||||
|
@ -1401,6 +1408,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
pvs.add("map[key4][0].name", "nameA");
|
pvs.add("map[key4][0].name", "nameA");
|
||||||
pvs.add("map[key4][1].name", "nameB");
|
pvs.add("map[key4][1].name", "nameB");
|
||||||
pvs.add("map[key5[foo]].name", "name10");
|
pvs.add("map[key5[foo]].name", "name10");
|
||||||
|
pvs.add("myTestBeans[0].name", "nameZZ");
|
||||||
accessor.setPropertyValues(pvs);
|
accessor.setPropertyValues(pvs);
|
||||||
assertThat(tb0.getName()).isEqualTo("name5");
|
assertThat(tb0.getName()).isEqualTo("name5");
|
||||||
assertThat(tb1.getName()).isEqualTo("name4");
|
assertThat(tb1.getName()).isEqualTo("name4");
|
||||||
|
@ -1419,6 +1427,7 @@ abstract class AbstractPropertyAccessorTests {
|
||||||
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
|
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
|
||||||
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
|
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
|
||||||
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
|
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
|
||||||
|
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2023 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -17,8 +17,10 @@
|
||||||
package org.springframework.beans.testfixture.beans;
|
package org.springframework.beans.testfixture.beans;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -47,6 +49,8 @@ public class IndexedTestBean {
|
||||||
|
|
||||||
private SortedMap sortedMap;
|
private SortedMap sortedMap;
|
||||||
|
|
||||||
|
private MyTestBeans myTestBeans;
|
||||||
|
|
||||||
|
|
||||||
public IndexedTestBean() {
|
public IndexedTestBean() {
|
||||||
this(true);
|
this(true);
|
||||||
|
@ -71,6 +75,7 @@ public class IndexedTestBean {
|
||||||
TestBean tb8 = new TestBean("name8", 0);
|
TestBean tb8 = new TestBean("name8", 0);
|
||||||
TestBean tbX = new TestBean("nameX", 0);
|
TestBean tbX = new TestBean("nameX", 0);
|
||||||
TestBean tbY = new TestBean("nameY", 0);
|
TestBean tbY = new TestBean("nameY", 0);
|
||||||
|
TestBean tbZ = new TestBean("nameZ", 0);
|
||||||
this.array = new TestBean[] {tb0, tb1};
|
this.array = new TestBean[] {tb0, tb1};
|
||||||
this.list = new ArrayList<>();
|
this.list = new ArrayList<>();
|
||||||
this.list.add(tb2);
|
this.list.add(tb2);
|
||||||
|
@ -87,6 +92,7 @@ public class IndexedTestBean {
|
||||||
list.add(tbY);
|
list.add(tbY);
|
||||||
this.map.put("key4", list);
|
this.map.put("key4", list);
|
||||||
this.map.put("key5[foo]", tb8);
|
this.map.put("key5[foo]", tb8);
|
||||||
|
this.myTestBeans = new MyTestBeans(tbZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -146,4 +152,27 @@ public class IndexedTestBean {
|
||||||
this.sortedMap = sortedMap;
|
this.sortedMap = sortedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MyTestBeans getMyTestBeans() {
|
||||||
|
return myTestBeans;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMyTestBeans(MyTestBeans myTestBeans) {
|
||||||
|
this.myTestBeans = myTestBeans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class MyTestBeans implements Iterable<TestBean> {
|
||||||
|
|
||||||
|
private final Collection<TestBean> testBeans;
|
||||||
|
|
||||||
|
public MyTestBeans(TestBean... testBeans) {
|
||||||
|
this.testBeans = Arrays.asList(testBeans);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<TestBean> iterator() {
|
||||||
|
return this.testBeans.iterator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue