Skip to content

Commit c20a2e4

Browse files
committed
Support for indexing into any Collection/Iterable
Closes gh-907
1 parent e048b09 commit c20a2e4

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java

+25-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,6 @@
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.Optional;
33-
import java.util.Set;
3433

3534
import org.apache.commons.logging.Log;
3635
import org.apache.commons.logging.LogFactory;
@@ -657,22 +656,32 @@ else if (value instanceof List list) {
657656
growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1);
658657
value = list.get(index);
659658
}
660-
else if (value instanceof Set set) {
661-
// Apply index to Iterator in case of a Set.
659+
else if (value instanceof Iterable iterable) {
660+
// Apply index to Iterator in case of a Set/Collection/Iterable.
662661
int index = Integer.parseInt(key);
663-
if (index < 0 || index >= set.size()) {
664-
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
665-
"Cannot get element with index " + index + " from Set of size " +
666-
set.size() + ", accessed using property path '" + propertyName + "'");
662+
if (value instanceof Collection<?> coll) {
663+
if (index < 0 || index >= coll.size()) {
664+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
665+
"Cannot get element with index " + index + " from Collection of size " +
666+
coll.size() + ", accessed using property path '" + propertyName + "'");
667+
}
667668
}
668-
Iterator<Object> it = set.iterator();
669-
for (int j = 0; it.hasNext(); j++) {
669+
Iterator<Object> it = iterable.iterator();
670+
boolean found = false;
671+
int currIndex = 0;
672+
for (; it.hasNext(); currIndex++) {
670673
Object elem = it.next();
671-
if (j == index) {
674+
if (currIndex == index) {
672675
value = elem;
676+
found = true;
673677
break;
674678
}
675679
}
680+
if (!found) {
681+
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
682+
"Cannot get element with index " + index + " from Iterable of size " +
683+
currIndex + ", accessed using property path '" + propertyName + "'");
684+
}
676685
}
677686
else if (value instanceof Map map) {
678687
Class<?> mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0);
@@ -685,13 +694,17 @@ else if (value instanceof Map map) {
685694
else {
686695
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
687696
"Property referenced in indexed property path '" + propertyName +
688-
"' is neither an array nor a List nor a Set nor a Map; returned value was [" + value + "]");
697+
"' is neither an array nor a List/Set/Collection/Iterable nor a Map; " +
698+
"returned value was [" + value + "]");
689699
}
690700
indexedPropertyName.append(PROPERTY_KEY_PREFIX).append(key).append(PROPERTY_KEY_SUFFIX);
691701
}
692702
}
693703
return value;
694704
}
705+
catch (InvalidPropertyException ex) {
706+
throw ex;
707+
}
695708
catch (IndexOutOfBoundsException ex) {
696709
throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName,
697710
"Index of out of bounds in property path '" + propertyName + "'", ex);

spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java

+9
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,14 @@ void isReadableWritableForIndexedProperties() {
139139
assertThat(accessor.isReadableProperty("list")).isTrue();
140140
assertThat(accessor.isReadableProperty("set")).isTrue();
141141
assertThat(accessor.isReadableProperty("map")).isTrue();
142+
assertThat(accessor.isReadableProperty("myTestBeans")).isTrue();
142143
assertThat(accessor.isReadableProperty("xxx")).isFalse();
143144

144145
assertThat(accessor.isWritableProperty("array")).isTrue();
145146
assertThat(accessor.isWritableProperty("list")).isTrue();
146147
assertThat(accessor.isWritableProperty("set")).isTrue();
147148
assertThat(accessor.isWritableProperty("map")).isTrue();
149+
assertThat(accessor.isWritableProperty("myTestBeans")).isTrue();
148150
assertThat(accessor.isWritableProperty("xxx")).isFalse();
149151

150152
assertThat(accessor.isReadableProperty("array[0]")).isTrue();
@@ -159,6 +161,8 @@ void isReadableWritableForIndexedProperties() {
159161
assertThat(accessor.isReadableProperty("map[key4][0].name")).isTrue();
160162
assertThat(accessor.isReadableProperty("map[key4][1]")).isTrue();
161163
assertThat(accessor.isReadableProperty("map[key4][1].name")).isTrue();
164+
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
165+
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
162166
assertThat(accessor.isReadableProperty("array[key1]")).isFalse();
163167

164168
assertThat(accessor.isWritableProperty("array[0]")).isTrue();
@@ -173,6 +177,8 @@ void isReadableWritableForIndexedProperties() {
173177
assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue();
174178
assertThat(accessor.isWritableProperty("map[key4][1]")).isTrue();
175179
assertThat(accessor.isWritableProperty("map[key4][1].name")).isTrue();
180+
assertThat(accessor.isReadableProperty("myTestBeans[0]")).isTrue();
181+
assertThat(accessor.isReadableProperty("myTestBeans[1]")).isFalse();
176182
assertThat(accessor.isWritableProperty("array[key1]")).isFalse();
177183
}
178184

@@ -1388,6 +1394,7 @@ void getAndSetIndexedProperties() {
13881394
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name8");
13891395
assertThat(accessor.getPropertyValue("map['key5[foo]'].name")).isEqualTo("name8");
13901396
assertThat(accessor.getPropertyValue("map[\"key5[foo]\"].name")).isEqualTo("name8");
1397+
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZ");
13911398

13921399
MutablePropertyValues pvs = new MutablePropertyValues();
13931400
pvs.add("array[0].name", "name5");
@@ -1401,6 +1408,7 @@ void getAndSetIndexedProperties() {
14011408
pvs.add("map[key4][0].name", "nameA");
14021409
pvs.add("map[key4][1].name", "nameB");
14031410
pvs.add("map[key5[foo]].name", "name10");
1411+
pvs.add("myTestBeans[0].name", "nameZZ");
14041412
accessor.setPropertyValues(pvs);
14051413
assertThat(tb0.getName()).isEqualTo("name5");
14061414
assertThat(tb1.getName()).isEqualTo("name4");
@@ -1419,6 +1427,7 @@ void getAndSetIndexedProperties() {
14191427
assertThat(accessor.getPropertyValue("map[key4][0].name")).isEqualTo("nameA");
14201428
assertThat(accessor.getPropertyValue("map[key4][1].name")).isEqualTo("nameB");
14211429
assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10");
1430+
assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ");
14221431
}
14231432

14241433
@Test

spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java

+30-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,8 +17,10 @@
1717
package org.springframework.beans.testfixture.beans;
1818

1919
import java.util.ArrayList;
20+
import java.util.Arrays;
2021
import java.util.Collection;
2122
import java.util.HashMap;
23+
import java.util.Iterator;
2224
import java.util.List;
2325
import java.util.Map;
2426
import java.util.Set;
@@ -47,6 +49,8 @@ public class IndexedTestBean {
4749

4850
private SortedMap sortedMap;
4951

52+
private MyTestBeans myTestBeans;
53+
5054

5155
public IndexedTestBean() {
5256
this(true);
@@ -71,6 +75,7 @@ public void populate() {
7175
TestBean tb8 = new TestBean("name8", 0);
7276
TestBean tbX = new TestBean("nameX", 0);
7377
TestBean tbY = new TestBean("nameY", 0);
78+
TestBean tbZ = new TestBean("nameZ", 0);
7479
this.array = new TestBean[] {tb0, tb1};
7580
this.list = new ArrayList<>();
7681
this.list.add(tb2);
@@ -87,6 +92,7 @@ public void populate() {
8792
list.add(tbY);
8893
this.map.put("key4", list);
8994
this.map.put("key5[foo]", tb8);
95+
this.myTestBeans = new MyTestBeans(tbZ);
9096
}
9197

9298

@@ -146,4 +152,27 @@ public void setSortedMap(SortedMap sortedMap) {
146152
this.sortedMap = sortedMap;
147153
}
148154

155+
public MyTestBeans getMyTestBeans() {
156+
return myTestBeans;
157+
}
158+
159+
public void setMyTestBeans(MyTestBeans myTestBeans) {
160+
this.myTestBeans = myTestBeans;
161+
}
162+
163+
164+
public static class MyTestBeans implements Iterable<TestBean> {
165+
166+
private final Collection<TestBean> testBeans;
167+
168+
public MyTestBeans(TestBean... testBeans) {
169+
this.testBeans = Arrays.asList(testBeans);
170+
}
171+
172+
@Override
173+
public Iterator<TestBean> iterator() {
174+
return this.testBeans.iterator();
175+
}
176+
}
177+
149178
}

0 commit comments

Comments
 (0)