From 3346c594e4367f117ede1aa05f9b988c2ddc7ec9 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sun, 25 Sep 2016 21:05:40 +0200 Subject: [PATCH] YamlPropertiesFactoryBean consistently exposes String values Issue: SPR-14737 (cherry picked from commit 74c6188) --- .../factory/config/YamlMapFactoryBean.java | 23 +++++---- .../beans/factory/config/YamlProcessor.java | 18 ++++--- .../config/YamlPropertiesFactoryBean.java | 29 +++++++---- .../config/YamlMapFactoryBeanTests.java | 51 +++++++++++-------- .../factory/config/YamlProcessorTests.java | 42 +++++++-------- .../YamlPropertiesFactoryBeanTests.java | 42 ++++++++------- .../core/CollectionFactory.java | 25 +++++++-- .../springframework/util/CollectionUtils.java | 8 +-- 8 files changed, 146 insertions(+), 92 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java index 7428f997f93..755819a10a0 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlMapFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2016 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. @@ -25,12 +25,16 @@ import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; /** - * Factory for a Map that reads from a YAML source. YAML is a nice human-readable - * format for configuration, and it has some useful hierarchical properties. It's - * more or less a superset of JSON, so it has a lot of similar features. If - * multiple resources are provided the later ones will override entries in the - * earlier ones hierarchically - that is all entries with the same nested key of - * type Map at any depth are merged. For example: + * Factory for a {@code Map} that reads from a YAML source, preserving the + * YAML-declared value types and their structure. + * + *

YAML is a nice human-readable format for configuration, and it has some + * useful hierarchical properties. It's more or less a superset of JSON, so it + * has a lot of similar features. + * + *

If multiple resources are provided the later ones will override entries in + * the earlier ones hierarchically; that is, all entries with the same nested key + * of type {@code Map} at any depth are merged. For example: * *

  * foo:
@@ -62,6 +66,7 @@ import org.springframework.beans.factory.InitializingBean;
  * with the value in the second, but its nested values are merged.
  *
  * @author Dave Syer
+ * @author Juergen Hoeller
  * @since 4.1
  */
 public class YamlMapFactoryBean extends YamlProcessor implements FactoryBean>, InitializingBean {
@@ -104,10 +109,10 @@ public class YamlMapFactoryBean extends YamlProcessor implements FactoryBeanInvoked lazily the first time {@link #getObject()} is invoked in
 	 * case of a shared singleton; else, on each {@link #getObject()} call.
+	 * 

The default implementation returns the merged {@code Map} instance. * @return the object returned by this factory * @see #process(java.util.Map, MatchCallback) */ diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java index ac01eb74b7a..4064bf9f53e 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -37,6 +37,7 @@ import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.parser.ParserException; import org.yaml.snakeyaml.reader.UnicodeReader; +import org.springframework.core.CollectionFactory; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -45,6 +46,7 @@ import org.springframework.util.StringUtils; * Base class for YAML factories. * * @author Dave Syer + * @author Juergen Hoeller * @since 4.1 */ public abstract class YamlProcessor { @@ -217,7 +219,7 @@ public abstract class YamlProcessor { } private boolean process(Map map, MatchCallback callback) { - Properties properties = new Properties(); + Properties properties = CollectionFactory.createStringAdaptingProperties(); properties.putAll(getFlattenedMap(map)); if (this.documentMatchers.isEmpty()) { @@ -302,21 +304,23 @@ public abstract class YamlProcessor { } } else { - result.put(key, value != null ? value : ""); + result.put(key, (value != null ? value : "")); } } } /** - * Callback interface used to process properties in a resulting map. + * Callback interface used to process the YAML parsing results. */ public interface MatchCallback { /** - * Process the properties. - * @param properties the properties to process - * @param map a mutable result map + * Process the given representation of the parsing results. + * @param properties the properties to process (as a flattened + * representation with indexed keys in case of a collection or map) + * @param map the result map (preserving the original value structure + * in the YAML document) */ void process(Properties properties, Map map); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java index 0374ddbf550..951ef3183b7 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -21,13 +21,23 @@ import java.util.Properties; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.CollectionFactory; /** - * Factory for Java Properties that reads from a YAML source. YAML is a nice - * human-readable format for configuration, and it has some useful hierarchical - * properties. It's more or less a superset of JSON, so it has a lot of similar - * features. The Properties created by this factory have nested paths for - * hierarchical objects, so for instance this YAML + * Factory for {@link java.util.Properties} that reads from a YAML source, + * exposing a flat structure of String property values. + * + *

YAML is a nice human-readable format for configuration, and it has some + * useful hierarchical properties. It's more or less a superset of JSON, so it + * has a lot of similar features. + * + *

Note: All exposed values are of type {@code String} for access through + * the common {@link Properties#getProperty} method (e.g. in configuration property + * resolution through {@link PropertyResourceConfigurer#setProperties(Properties)}). + * If this is not desirable, use {@link YamlMapFactoryBean} instead. + * + *

The Properties created by this factory have nested paths for hierarchical + * objects, so for instance this YAML * *

  * environments:
@@ -39,7 +49,7 @@ import org.springframework.beans.factory.InitializingBean;
  *     name: My Cool App
  * 
* - * is transformed into these Properties: + * is transformed into these properties: * *
  * environments.dev.url=http://dev.bar.com
@@ -57,7 +67,7 @@ import org.springframework.beans.factory.InitializingBean;
  * - foo.bar.com
  * 
* - * becomes Java Properties like this: + * becomes properties like this: * *
  * servers[0]=dev.bar.com
@@ -66,6 +76,7 @@ import org.springframework.beans.factory.InitializingBean;
  *
  * @author Dave Syer
  * @author Stephane Nicoll
+ * @author Juergen Hoeller
  * @since 4.1
  */
 public class YamlPropertiesFactoryBean extends YamlProcessor implements FactoryBean, InitializingBean {
@@ -116,7 +127,7 @@ public class YamlPropertiesFactoryBean extends YamlProcessor implements FactoryB
 	 * @see #process(MatchCallback) ()
 	 */
 	protected Properties createProperties() {
-		final Properties result = new Properties();
+		final Properties result = CollectionFactory.createStringAdaptingProperties();
 		process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java
index 313b547b457..bd4f968c8f2 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlMapFactoryBeanTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -34,43 +34,40 @@ import static org.junit.Assert.*;
  * Tests for {@link YamlMapFactoryBean}.
  *
  * @author Dave Syer
+ * @author Juergen Hoeller
  */
 public class YamlMapFactoryBeanTests {
 
 	private final YamlMapFactoryBean factory = new YamlMapFactoryBean();
 
+
 	@Test
 	public void testSetIgnoreResourceNotFound() throws Exception {
-		this.factory
-				.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
-		this.factory.setResources(new FileSystemResource[] {new FileSystemResource(
-				"non-exsitent-file.yml")});
+		this.factory.setResolutionMethod(YamlMapFactoryBean.ResolutionMethod.OVERRIDE_AND_IGNORE);
+		this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
 		assertEquals(0, this.factory.getObject().size());
 	}
 
 	@Test(expected = IllegalStateException.class)
 	public void testSetBarfOnResourceNotFound() throws Exception {
-		this.factory.setResources(new FileSystemResource[] {new FileSystemResource(
-				"non-exsitent-file.yml")});
+		this.factory.setResources(new FileSystemResource("non-exsitent-file.yml"));
 		assertEquals(0, this.factory.getObject().size());
 	}
 
 	@Test
 	public void testGetObject() throws Exception {
-		this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource(
-				"foo: bar".getBytes())});
+		this.factory.setResources(new ByteArrayResource("foo: bar".getBytes()));
 		assertEquals(1, this.factory.getObject().size());
 	}
 
 	@SuppressWarnings("unchecked")
 	@Test
-	public void testOverrideAndremoveDefaults() throws Exception {
-		this.factory.setResources(new ByteArrayResource[] {
-				new ByteArrayResource("foo:\n  bar: spam".getBytes()),
-				new ByteArrayResource("foo:\n  spam: bar".getBytes())});
+	public void testOverrideAndRemoveDefaults() throws Exception {
+		this.factory.setResources(new ByteArrayResource("foo:\n  bar: spam".getBytes()),
+				new ByteArrayResource("foo:\n  spam: bar".getBytes()));
+
 		assertEquals(1, this.factory.getObject().size());
-		assertEquals(2,
-				((Map) this.factory.getObject().get("foo")).size());
+		assertEquals(2, ((Map) this.factory.getObject().get("foo")).size());
 	}
 
 	@Test
@@ -81,20 +78,20 @@ public class YamlMapFactoryBeanTests {
 			public String getDescription() {
 				return "non-existent";
 			}
-
 			@Override
 			public InputStream getInputStream() throws IOException {
 				throw new IOException("planned");
 			}
 		}, new ByteArrayResource("foo:\n  spam: bar".getBytes()));
+
 		assertEquals(1, this.factory.getObject().size());
 	}
 
 	@Test
 	public void testMapWithPeriodsInKey() throws Exception {
-		this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource(
-				"foo:\n  ? key1.key2\n  : value".getBytes())});
+		this.factory.setResources(new ByteArrayResource("foo:\n  ? key1.key2\n  : value".getBytes()));
 		Map map = this.factory.getObject();
+
 		assertEquals(1, map.size());
 		assertTrue(map.containsKey("foo"));
 		Object object = map.get("foo");
@@ -105,10 +102,24 @@ public class YamlMapFactoryBeanTests {
 		assertEquals("value", sub.get("key1.key2"));
 	}
 
+	@Test
+	public void testMapWithIntegerValue() throws Exception {
+		this.factory.setResources(new ByteArrayResource("foo:\n  ? key1.key2\n  : 3".getBytes()));
+		Map map = this.factory.getObject();
+
+		assertEquals(1, map.size());
+		assertTrue(map.containsKey("foo"));
+		Object object = map.get("foo");
+		assertTrue(object instanceof LinkedHashMap);
+		@SuppressWarnings("unchecked")
+		Map sub = (Map) object;
+		assertTrue(sub.containsKey("key1.key2"));
+		assertEquals(Integer.valueOf(3), sub.get("key1.key2"));
+	}
+
 	@Test(expected = ParserException.class)
 	public void testDuplicateKey() throws Exception {
-		this.factory.setResources(new ByteArrayResource[] {new ByteArrayResource(
-				"mymap:\n  foo: bar\nmymap:\n  bar: foo".getBytes())});
+		this.factory.setResources(new ByteArrayResource("mymap:\n  foo: bar\nmymap:\n  bar: foo".getBytes()));
 		this.factory.getObject().get("mymap");
 	}
 
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java
index fd90fbeb666..51740c74184 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlProcessorTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.springframework.beans.factory.config;
 
 import java.util.LinkedHashMap;
@@ -24,6 +25,7 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.yaml.snakeyaml.parser.ParserException;
 import org.yaml.snakeyaml.scanner.ScannerException;
+
 import org.springframework.core.io.ByteArrayResource;
 
 import static org.junit.Assert.*;
@@ -33,34 +35,38 @@ import static org.springframework.beans.factory.config.YamlProcessor.*;
  * Tests for {@link YamlProcessor}.
  *
  * @author Dave Syer
+ * @author Juergen Hoeller
  */
 public class YamlProcessorTests {
 
-	private final YamlProcessor processor = new YamlProcessor() {
-	};
+	private final YamlProcessor processor = new YamlProcessor() {};
 
 	@Rule
 	public ExpectedException exception = ExpectedException.none();
 
+
 	@Test
 	public void arrayConvertedToIndexedBeanReference() {
-		this.processor.setResources(new ByteArrayResource(
-				"foo: bar\nbar: [1,2,3]".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo: bar\nbar: [1,2,3]".getBytes()));
 		this.processor.process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
+				assertEquals(4, properties.size());
+				assertEquals("bar", properties.get("foo"));
+				assertEquals("bar", properties.getProperty("foo"));
 				assertEquals(1, properties.get("bar[0]"));
+				assertEquals("1", properties.getProperty("bar[0]"));
 				assertEquals(2, properties.get("bar[1]"));
+				assertEquals("2", properties.getProperty("bar[1]"));
 				assertEquals(3, properties.get("bar[2]"));
-				assertEquals(4, properties.size());
+				assertEquals("3", properties.getProperty("bar[2]"));
 			}
 		});
 	}
 
 	@Test
 	public void testStringResource() throws Exception {
-		this.processor.setResources(new ByteArrayResource(
-				"foo # a document that is a literal".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo # a document that is a literal".getBytes()));
 		this.processor.process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
@@ -71,8 +77,7 @@ public class YamlProcessorTests {
 
 	@Test
 	public void testBadDocumentStart() throws Exception {
-		this.processor.setResources(new ByteArrayResource(
-				"foo # a document\nbar: baz".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo # a document\nbar: baz".getBytes()));
 		this.exception.expect(ParserException.class);
 		this.exception.expectMessage("line 2, column 1");
 		this.processor.process(new MatchCallback() {
@@ -84,8 +89,7 @@ public class YamlProcessorTests {
 
 	@Test
 	public void testBadResource() throws Exception {
-		this.processor.setResources(new ByteArrayResource(
-				"foo: bar\ncd\nspam:\n  foo: baz".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo: bar\ncd\nspam:\n  foo: baz".getBytes()));
 		this.exception.expect(ScannerException.class);
 		this.exception.expectMessage("line 3, column 1");
 		this.processor.process(new MatchCallback() {
@@ -97,8 +101,7 @@ public class YamlProcessorTests {
 
 	@Test
 	public void mapConvertedToIndexedBeanReference() {
-		this.processor.setResources(new ByteArrayResource(
-				"foo: bar\nbar:\n spam: bucket".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
 		this.processor.process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
@@ -111,8 +114,7 @@ public class YamlProcessorTests {
 
 	@Test
 	public void integerKeyBehaves() {
-		this.processor.setResources(new ByteArrayResource(
-				"foo: bar\n1: bar".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo: bar\n1: bar".getBytes()));
 		this.processor.process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
@@ -124,10 +126,8 @@ public class YamlProcessorTests {
 
 	@Test
 	public void integerDeepKeyBehaves() {
-		this.processor.setResources(new ByteArrayResource(
-				"foo:\n  1: bar".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo:\n  1: bar".getBytes()));
 		this.processor.process(new MatchCallback() {
-
 			@Override
 			public void process(Properties properties, Map map) {
 				assertEquals("bar", properties.get("foo[1]"));
@@ -139,8 +139,7 @@ public class YamlProcessorTests {
 	@Test
 	@SuppressWarnings("unchecked")
 	public void flattenedMapIsSameAsPropertiesButOrdered() {
-		this.processor.setResources(new ByteArrayResource(
-				"foo: bar\nbar:\n spam: bucket".getBytes()));
+		this.processor.setResources(new ByteArrayResource("foo: bar\nbar:\n spam: bucket".getBytes()));
 		this.processor.process(new MatchCallback() {
 			@Override
 			public void process(Properties properties, Map map) {
@@ -155,4 +154,5 @@ public class YamlProcessorTests {
 			}
 		});
 	}
+
 }
diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java
index 855e479a641..0bfa5127776 100644
--- a/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java
+++ b/spring-beans/src/test/java/org/springframework/beans/factory/config/YamlPropertiesFactoryBeanTests.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2016 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.
@@ -38,12 +38,14 @@ import static org.springframework.beans.factory.config.YamlProcessor.*;
  * Tests for {@link YamlPropertiesFactoryBean}.
  *
  * @author Dave Syer
+ * @author Juergen Hoeller
  */
 public class YamlPropertiesFactoryBeanTests {
 
 	@Rule
 	public ExpectedException exception = ExpectedException.none();
 
+
 	@Test
 	public void testLoadResource() throws Exception {
 		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
@@ -113,8 +115,8 @@ public class YamlPropertiesFactoryBeanTests {
 		factory.setDocumentMatchers(new DocumentMatcher() {
 			@Override
 			public MatchStatus matches(Properties properties) {
-				return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
-						: MatchStatus.NOT_FOUND;
+				return ("bag".equals(properties.getProperty("foo")) ?
+						MatchStatus.FOUND : MatchStatus.NOT_FOUND);
 			}
 		});
 		Properties properties = factory.getObject();
@@ -134,8 +136,8 @@ public class YamlPropertiesFactoryBeanTests {
 				if (!properties.containsKey("foo")) {
 					return MatchStatus.ABSTAIN;
 				}
-				return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
-						: MatchStatus.NOT_FOUND;
+				return ("bag".equals(properties.getProperty("foo")) ?
+						MatchStatus.FOUND : MatchStatus.NOT_FOUND);
 			}
 		});
 		Properties properties = factory.getObject();
@@ -156,8 +158,8 @@ public class YamlPropertiesFactoryBeanTests {
 				if (!properties.containsKey("foo")) {
 					return MatchStatus.ABSTAIN;
 				}
-				return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
-						: MatchStatus.NOT_FOUND;
+				return ("bag".equals(properties.getProperty("foo")) ?
+						MatchStatus.FOUND : MatchStatus.NOT_FOUND);
 			}
 		});
 		Properties properties = factory.getObject();
@@ -178,8 +180,8 @@ public class YamlPropertiesFactoryBeanTests {
 				if (!properties.containsKey("foo")) {
 					return MatchStatus.ABSTAIN;
 				}
-				return "bag".equals(properties.getProperty("foo")) ? MatchStatus.FOUND
-						: MatchStatus.NOT_FOUND;
+				return ("bag".equals(properties.getProperty("foo")) ?
+						MatchStatus.FOUND : MatchStatus.NOT_FOUND);
 			}
 		});
 		Properties properties = factory.getObject();
@@ -200,8 +202,7 @@ public class YamlPropertiesFactoryBeanTests {
 	@Test
 	public void testLoadNull() throws Exception {
 		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
-		factory.setResources(new ByteArrayResource("foo: bar\nspam:"
-				.getBytes()));
+		factory.setResources(new ByteArrayResource("foo: bar\nspam:".getBytes()));
 		Properties properties = factory.getObject();
 		assertThat(properties.getProperty("foo"), equalTo("bar"));
 		assertThat(properties.getProperty("spam"), equalTo(""));
@@ -210,20 +211,28 @@ public class YamlPropertiesFactoryBeanTests {
 	@Test
 	public void testLoadArrayOfString() throws Exception {
 		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
-		factory.setResources(new ByteArrayResource("foo:\n- bar\n- baz"
-				.getBytes()));
+		factory.setResources(new ByteArrayResource("foo:\n- bar\n- baz".getBytes()));
 		Properties properties = factory.getObject();
 		assertThat(properties.getProperty("foo[0]"), equalTo("bar"));
 		assertThat(properties.getProperty("foo[1]"), equalTo("baz"));
 		assertThat(properties.get("foo"), is(nullValue()));
 	}
 
+	@Test
+	public void testLoadArrayOfInteger() throws Exception {
+		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+		factory.setResources(new ByteArrayResource("foo:\n- 1\n- 2".getBytes()));
+		Properties properties = factory.getObject();
+		assertThat(properties.getProperty("foo[0]"), equalTo("1"));
+		assertThat(properties.getProperty("foo[1]"), equalTo("2"));
+		assertThat(properties.get("foo"), is(nullValue()));
+	}
+
 	@Test
 	public void testLoadArrayOfObject() throws Exception {
 		YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
 		factory.setResources(new ByteArrayResource(
-				"foo:\n- bar:\n    spam: crap\n- baz\n- one: two\n  three: four"
-						.getBytes()
+				"foo:\n- bar:\n    spam: crap\n- baz\n- one: two\n  three: four".getBytes()
 		));
 		Properties properties = factory.getObject();
 		assertThat(properties.getProperty("foo[0].bar.spam"), equalTo("crap"));
@@ -239,8 +248,7 @@ public class YamlPropertiesFactoryBeanTests {
 		Yaml yaml = new Yaml();
 		Map map = yaml.loadAs("foo: bar\nspam:\n  foo: baz", Map.class);
 		assertThat(map.get("foo"), equalTo((Object) "bar"));
-		assertThat(((Map) map.get("spam")).get("foo"),
-				equalTo((Object) "baz"));
+		assertThat(((Map) map.get("spam")).get("foo"), equalTo((Object) "baz"));
 	}
 
 }
diff --git a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
index 0a83068ce7b..50c3f0e8ad2 100644
--- a/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
+++ b/spring-core/src/main/java/org/springframework/core/CollectionFactory.java
@@ -29,6 +29,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
 import java.util.NavigableSet;
+import java.util.Properties;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.SortedSet;
@@ -40,12 +41,9 @@ import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 
 /**
- * Factory for collections that is aware of Java 5, Java 6, and Spring
- * collection types.
+ * Factory for collections that is aware of Java 5, Java 6, and Spring collection types.
+ *
  * 

Mainly for internal use within the framework. - *

The goal of this class is to avoid runtime dependencies on a specific - * Java version, while nevertheless using the best collection implementation - * that is available at runtime. * * @author Juergen Hoeller * @author Arjen Poutsma @@ -324,6 +322,23 @@ public abstract class CollectionFactory { } } + /** + * Create a variant of {@code java.util.Properties} that automatically adapts + * non-String values to String representations on {@link Properties#getProperty}. + * @return a new {@code Properties} instance + * @since 4.3.4 + */ + @SuppressWarnings("serial") + public static Properties createStringAdaptingProperties() { + return new Properties() { + @Override + public String getProperty(String key) { + Object value = get(key); + return (value != null ? value.toString() : null); + } + }; + } + /** * Cast the given type to a subtype of {@link Enum}. * @param enumType the enum type, never {@code null} diff --git a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java index ae6d8be36e0..8edb77e9d59 100644 --- a/spring-core/src/main/java/org/springframework/util/CollectionUtils.java +++ b/spring-core/src/main/java/org/springframework/util/CollectionUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -110,10 +110,10 @@ public abstract class CollectionUtils { if (props != null) { for (Enumeration en = props.propertyNames(); en.hasMoreElements();) { String key = (String) en.nextElement(); - Object value = props.getProperty(key); + Object value = props.get(key); if (value == null) { - // Potentially a non-String value... - value = props.get(key); + // Allow for defaults fallback or potentially overridden accessor... + value = props.getProperty(key); } map.put((K) key, (V) value); }