diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcher.java b/spring-boot/src/main/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcher.java new file mode 100644 index 0000000000..a14c62ad76 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcher.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.bind; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Default {@link PropertyNamePatternsMatcher} that matches when a property name exactly + * matches one of the given names, or starts with one of the given names followed by '.' + * or '_'. This implementation is optimized for frequent calls. + * + * @author Phillip Webb + * @since 1.2.0 + */ +class DefaultPropertyNamePatternsMatcher implements PropertyNamePatternsMatcher { + + private final String[] names; + + public DefaultPropertyNamePatternsMatcher(String... names) { + this(new HashSet(Arrays.asList(names))); + } + + public DefaultPropertyNamePatternsMatcher(Set names) { + this.names = names.toArray(new String[names.size()]); + } + + @Override + public boolean matches(String propertyName) { + char[] propertNameChars = propertyName.toCharArray(); + boolean[] match = new boolean[this.names.length]; + boolean noneMatched = true; + for (int i = 0; i < this.names.length; i++) { + if (this.names[i].length() <= propertNameChars.length) { + match[i] = true; + noneMatched = false; + } + } + if (noneMatched) { + return false; + } + for (int charIndex = 0; charIndex < propertNameChars.length; charIndex++) { + noneMatched = true; + for (int nameIndex = 0; nameIndex < this.names.length; nameIndex++) { + if (match[nameIndex]) { + if (charIndex < this.names[nameIndex].length()) { + if (this.names[nameIndex].charAt(charIndex) == propertNameChars[charIndex]) { + match[nameIndex] = true; + noneMatched = false; + } + } + else { + char charAfter = propertNameChars[this.names[nameIndex].length()]; + if (charAfter == '.' || charAfter == '_') { + match[nameIndex] = true; + noneMatched = false; + } + } + } + } + if (noneMatched) { + return false; + } + } + for (int i = 0; i < match.length; i++) { + if (match[i]) { + return true; + } + } + return false; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java index 6fea3365ac..841d8ed7b5 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertiesConfigurationFactory.java @@ -249,25 +249,20 @@ public class PropertiesConfigurationFactory implements FactoryBean, customizeBinder(dataBinder); Set names = new HashSet(); - Set patterns = new HashSet(); if (this.target != null) { PropertyDescriptor[] descriptors = BeanUtils .getPropertyDescriptors(this.target.getClass()); String prefix = (this.targetName != null ? this.targetName + "." : ""); - String[] suffixes = new String[] { ".*", "_*" }; for (PropertyDescriptor descriptor : descriptors) { String name = descriptor.getName(); if (!name.equals("class")) { for (String relaxedName : new RelaxedNames(prefix + name)) { names.add(relaxedName); - patterns.add(relaxedName); - for (String suffix : suffixes) { - patterns.add(relaxedName + suffix); - } } } } } + PropertyNamePatternsMatcher patterns = new DefaultPropertyNamePatternsMatcher(names); PropertyValues propertyValues = (this.properties != null ? new MutablePropertyValues( this.properties) : new PropertySourcesPropertyValues( diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertyNamePatternsMatcher.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertyNamePatternsMatcher.java new file mode 100644 index 0000000000..ee5474c4c4 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertyNamePatternsMatcher.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.bind; + +/** + * Strategy interface used to check if a property name matches specific criteria. + * + * @author Phillip Webb + * @since 1.2.0 + */ +interface PropertyNamePatternsMatcher { + + PropertyNamePatternsMatcher NONE = new PropertyNamePatternsMatcher() { + + @Override + public boolean matches(String propertyName) { + return false; + } + + }; + + /** + * Return {@code true} of the property name matches. + * @param propertyName the property name + * @return {@code true} if the property name matches + */ + boolean matches(String propertyName); + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java index 13ee4434c2..6da78b970e 100644 --- a/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java +++ b/spring-boot/src/main/java/org/springframework/boot/bind/PropertySourcesPropertyValues.java @@ -19,6 +19,7 @@ package org.springframework.boot.bind; import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -31,7 +32,6 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.env.PropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; import org.springframework.core.env.StandardEnvironment; -import org.springframework.util.PatternMatchUtils; import org.springframework.util.ReflectionUtils; import org.springframework.validation.DataBinder; @@ -48,7 +48,7 @@ public class PropertySourcesPropertyValues implements PropertyValues { private final PropertySources propertySources; - private static final Collection NON_ENUMERABLE_ENUMERABLES = Arrays.asList( + private static final Collection PATTERN_MATCHED_PROPERTY_SOURCES = Arrays.asList( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME); @@ -57,37 +57,48 @@ public class PropertySourcesPropertyValues implements PropertyValues { * @param propertySources a PropertySources instance */ public PropertySourcesPropertyValues(PropertySources propertySources) { - this(propertySources, null, null); + this(propertySources, (PropertyNamePatternsMatcher) null, + (Collection) null); } /** * Create a new PropertyValues from the given PropertySources * @param propertySources a PropertySources instance - * @param patterns property name patterns to include from system properties and + * @param includePatterns property name patterns to include from system properties and * environment variables * @param names exact property names to include */ public PropertySourcesPropertyValues(PropertySources propertySources, - Collection patterns, Collection names) { + Collection includePatterns, Collection names) { + this(propertySources, new SimplePropertyNamePatternsMatcher(includePatterns), names); + } + + /** + * Create a new PropertyValues from the given PropertySources + * @param propertySources a PropertySources instance + * @param includes property name patterns to include from system properties and + * environment variables + * @param names exact property names to include + */ + PropertySourcesPropertyValues(PropertySources propertySources, + PropertyNamePatternsMatcher includes, Collection names) { this.propertySources = propertySources; + if (includes == null) { + includes = PropertyNamePatternsMatcher.NONE; + } + if (names == null) { + names = Collections.emptySet(); + } PropertySourcesPropertyResolver resolver = new PropertySourcesPropertyResolver( propertySources); - String[] includes = toArray(patterns); - String[] exacts = toArray(names); for (PropertySource source : propertySources) { - processPropertySource(source, resolver, includes, exacts); - } - } - - private String[] toArray(Collection strings) { - if (strings == null) { - return new String[0]; + processPropertySource(source, resolver, includes, names); } - return strings.toArray(new String[strings.size()]); } private void processPropertySource(PropertySource source, - PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { + PropertySourcesPropertyResolver resolver, + PropertyNamePatternsMatcher includes, Collection exacts) { if (source instanceof EnumerablePropertySource) { processEnumerablePropertySource((EnumerablePropertySource) source, resolver, includes, exacts); @@ -104,12 +115,12 @@ public class PropertySourcesPropertyValues implements PropertyValues { } private void processEnumerablePropertySource(EnumerablePropertySource source, - PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { + PropertySourcesPropertyResolver resolver, + PropertyNamePatternsMatcher includes, Collection exacts) { if (source.getPropertyNames().length > 0) { for (String propertyName : source.getPropertyNames()) { - if (PropertySourcesPropertyValues.NON_ENUMERABLE_ENUMERABLES - .contains(source.getName()) - && !PatternMatchUtils.simpleMatch(includes, propertyName)) { + if (PropertySourcesPropertyValues.PATTERN_MATCHED_PROPERTY_SOURCES + .contains(source.getName()) && !includes.matches(propertyName)) { continue; } Object value = source.getProperty(propertyName); @@ -128,7 +139,8 @@ public class PropertySourcesPropertyValues implements PropertyValues { } private void processCompositePropertySource(CompositePropertySource source, - PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { + PropertySourcesPropertyResolver resolver, + PropertyNamePatternsMatcher includes, Collection exacts) { for (PropertySource nested : extractSources(source)) { processPropertySource(nested, resolver, includes, exacts); } @@ -151,7 +163,8 @@ public class PropertySourcesPropertyValues implements PropertyValues { } private void processDefaultPropertySource(PropertySource source, - PropertySourcesPropertyResolver resolver, String[] includes, String[] exacts) { + PropertySourcesPropertyResolver resolver, + PropertyNamePatternsMatcher includes, Collection exacts) { for (String propertyName : exacts) { Object value = null; try { diff --git a/spring-boot/src/main/java/org/springframework/boot/bind/SimplePropertyNamePatternsMatcher.java b/spring-boot/src/main/java/org/springframework/boot/bind/SimplePropertyNamePatternsMatcher.java new file mode 100644 index 0000000000..d3f554d4f6 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/bind/SimplePropertyNamePatternsMatcher.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.bind; + +import java.util.Collection; + +import org.springframework.util.PatternMatchUtils; + +/** + * {@link PropertyNamePatternsMatcher} that delegates to + * {@link PatternMatchUtils#simpleMatch(String[], String)}. + * + * @author Phillip Webb + * @since 1.2.0 + */ +class SimplePropertyNamePatternsMatcher implements PropertyNamePatternsMatcher { + + private final String[] patterns; + + public SimplePropertyNamePatternsMatcher(Collection patterns) { + this.patterns = (patterns == null ? new String[] {} : patterns + .toArray(new String[patterns.size()])); + } + + @Override + public boolean matches(String propertyName) { + return PatternMatchUtils.simpleMatch(this.patterns, propertyName); + } +} diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcherTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcherTests.java new file mode 100644 index 0000000000..869c3a7906 --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/bind/DefaultPropertyNamePatternsMatcherTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012-2014 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.bind; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link DefaultPropertyNamePatternsMatcher}. + * + * @author Phillip Webb + */ +public class DefaultPropertyNamePatternsMatcherTests { + + @Test + public void namesShorter() { + assertFalse(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb") + .matches("zzzzz")); + + } + + @Test + public void namesExactMatch() { + assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc") + .matches("bbbb")); + } + + @Test + public void namesLonger() { + assertFalse(new DefaultPropertyNamePatternsMatcher("aaaaa", "bbbbb", "ccccc") + .matches("bbbb")); + } + + @Test + public void nameWithDot() throws Exception { + assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc") + .matches("bbbb.anything")); + } + + @Test + public void nameWithUnderscore() throws Exception { + assertTrue(new DefaultPropertyNamePatternsMatcher("aaaa", "bbbb", "cccc") + .matches("bbbb_anything")); + } + + @Test + public void namesMatchWithDifferentLengths() throws Exception { + assertTrue(new DefaultPropertyNamePatternsMatcher("aaa", "bbbb", "ccccc") + .matches("bbbb")); + + } + +} diff --git a/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java b/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java index b63afa058d..a4b19af6c2 100644 --- a/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/bind/PropertySourcesPropertyValuesTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.bind; +import java.util.Collection; import java.util.Collections; import org.junit.Before; @@ -98,7 +99,8 @@ public class PropertySourcesPropertyValuesTests { }); PropertySourcesPropertyValues propertyValues = new PropertySourcesPropertyValues( - this.propertySources, null, Collections.singleton("baz")); + this.propertySources, (Collection) null, + Collections.singleton("baz")); assertEquals("bar", propertyValues.getPropertyValue("baz").getValue()); } @@ -123,8 +125,8 @@ public class PropertySourcesPropertyValuesTests { public void testPlaceholdersBindingNonEnumerable() { FooBean target = new FooBean(); DataBinder binder = new DataBinder(target); - binder.bind(new PropertySourcesPropertyValues(this.propertySources, null, - Collections.singleton("foo"))); + binder.bind(new PropertySourcesPropertyValues(this.propertySources, + (Collection) null, Collections.singleton("foo"))); assertEquals("bar", target.getFoo()); } @@ -148,8 +150,8 @@ public class PropertySourcesPropertyValuesTests { return new Object(); } }); - binder.bind(new PropertySourcesPropertyValues(this.propertySources, null, - Collections.singleton("name"))); + binder.bind(new PropertySourcesPropertyValues(this.propertySources, + (Collection) null, Collections.singleton("name"))); assertEquals(null, target.getName()); }