From c20a2e4763448ebb244682e539238d742576f774 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 11 Jul 2023 23:07:51 +0200 Subject: [PATCH] Support for indexing into any Collection/Iterable Closes gh-907 --- .../AbstractNestablePropertyAccessor.java | 37 +++++++++++++------ .../beans/AbstractPropertyAccessorTests.java | 9 +++++ .../testfixture/beans/IndexedTestBean.java | 31 +++++++++++++++- 3 files changed, 64 insertions(+), 13 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java index 5310bd4688e..51d2a5f7398 100644 --- a/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/AbstractNestablePropertyAccessor.java @@ -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"); * 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.Map; import java.util.Optional; -import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -657,22 +656,32 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA growCollectionIfNecessary(list, index, indexedPropertyName.toString(), ph, i + 1); value = list.get(index); } - else if (value instanceof Set set) { - // Apply index to Iterator in case of a Set. + else if (value instanceof Iterable iterable) { + // Apply index to Iterator in case of a Set/Collection/Iterable. int index = Integer.parseInt(key); - if (index < 0 || index >= set.size()) { - throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, - "Cannot get element with index " + index + " from Set of size " + - set.size() + ", accessed using property path '" + propertyName + "'"); + if (value instanceof Collection coll) { + if (index < 0 || index >= coll.size()) { + throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, + "Cannot get element with index " + index + " from Collection of size " + + coll.size() + ", accessed using property path '" + propertyName + "'"); + } } - Iterator it = set.iterator(); - for (int j = 0; it.hasNext(); j++) { + Iterator it = iterable.iterator(); + boolean found = false; + int currIndex = 0; + for (; it.hasNext(); currIndex++) { Object elem = it.next(); - if (j == index) { + if (currIndex == index) { value = elem; + found = true; 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) { Class mapKeyType = ph.getResolvableType().getNested(i + 1).asMap().resolveGeneric(0); @@ -685,13 +694,17 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA else { throw new InvalidPropertyException(getRootClass(), this.nestedPath + 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); } } return value; } + catch (InvalidPropertyException ex) { + throw ex; + } catch (IndexOutOfBoundsException ex) { throw new InvalidPropertyException(getRootClass(), this.nestedPath + propertyName, "Index of out of bounds in property path '" + propertyName + "'", ex); diff --git a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java index 12c6a73c37e..91155ff3227 100644 --- a/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/AbstractPropertyAccessorTests.java @@ -139,12 +139,14 @@ abstract class AbstractPropertyAccessorTests { assertThat(accessor.isReadableProperty("list")).isTrue(); assertThat(accessor.isReadableProperty("set")).isTrue(); assertThat(accessor.isReadableProperty("map")).isTrue(); + assertThat(accessor.isReadableProperty("myTestBeans")).isTrue(); assertThat(accessor.isReadableProperty("xxx")).isFalse(); assertThat(accessor.isWritableProperty("array")).isTrue(); assertThat(accessor.isWritableProperty("list")).isTrue(); assertThat(accessor.isWritableProperty("set")).isTrue(); assertThat(accessor.isWritableProperty("map")).isTrue(); + assertThat(accessor.isWritableProperty("myTestBeans")).isTrue(); assertThat(accessor.isWritableProperty("xxx")).isFalse(); 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][1]")).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.isWritableProperty("array[0]")).isTrue(); @@ -173,6 +177,8 @@ abstract class AbstractPropertyAccessorTests { assertThat(accessor.isWritableProperty("map[key4][0].name")).isTrue(); assertThat(accessor.isWritableProperty("map[key4][1]")).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(); } @@ -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("myTestBeans[0].name")).isEqualTo("nameZ"); MutablePropertyValues pvs = new MutablePropertyValues(); pvs.add("array[0].name", "name5"); @@ -1401,6 +1408,7 @@ abstract class AbstractPropertyAccessorTests { pvs.add("map[key4][0].name", "nameA"); pvs.add("map[key4][1].name", "nameB"); pvs.add("map[key5[foo]].name", "name10"); + pvs.add("myTestBeans[0].name", "nameZZ"); accessor.setPropertyValues(pvs); assertThat(tb0.getName()).isEqualTo("name5"); 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][1].name")).isEqualTo("nameB"); assertThat(accessor.getPropertyValue("map[key5[foo]].name")).isEqualTo("name10"); + assertThat(accessor.getPropertyValue("myTestBeans[0].name")).isEqualTo("nameZZ"); } @Test diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java index 02948f7eb85..54d32af5453 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/testfixture/beans/IndexedTestBean.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,8 +17,10 @@ package org.springframework.beans.testfixture.beans; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -47,6 +49,8 @@ public class IndexedTestBean { private SortedMap sortedMap; + private MyTestBeans myTestBeans; + public IndexedTestBean() { this(true); @@ -71,6 +75,7 @@ public class IndexedTestBean { TestBean tb8 = new TestBean("name8", 0); TestBean tbX = new TestBean("nameX", 0); TestBean tbY = new TestBean("nameY", 0); + TestBean tbZ = new TestBean("nameZ", 0); this.array = new TestBean[] {tb0, tb1}; this.list = new ArrayList<>(); this.list.add(tb2); @@ -87,6 +92,7 @@ public class IndexedTestBean { list.add(tbY); this.map.put("key4", list); this.map.put("key5[foo]", tb8); + this.myTestBeans = new MyTestBeans(tbZ); } @@ -146,4 +152,27 @@ public class IndexedTestBean { this.sortedMap = sortedMap; } + public MyTestBeans getMyTestBeans() { + return myTestBeans; + } + + public void setMyTestBeans(MyTestBeans myTestBeans) { + this.myTestBeans = myTestBeans; + } + + + public static class MyTestBeans implements Iterable { + + private final Collection testBeans; + + public MyTestBeans(TestBean... testBeans) { + this.testBeans = Arrays.asList(testBeans); + } + + @Override + public Iterator iterator() { + return this.testBeans.iterator(); + } + } + }