Skip to content

Commit 739b68c

Browse files
authored
Reduce allocations of Environment variables matching (#1359)
1 parent 32c4c59 commit 739b68c

File tree

3 files changed

+130
-41
lines changed

3 files changed

+130
-41
lines changed

implementation/src/main/java/io/smallrye/config/EnvConfigSource.java

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package io.smallrye.config;
1717

18+
import static io.smallrye.config.EnvConfigSource.ResizableIntArray.MINUS_ONE;
19+
import static io.smallrye.config.EnvConfigSource.ResizableIntArray.ZERO;
1820
import static io.smallrye.config.ProfileConfigSourceInterceptor.activeName;
1921
import static io.smallrye.config.common.utils.ConfigSourceUtil.CONFIG_ORDINAL_KEY;
2022
import static io.smallrye.config.common.utils.ConfigSourceUtil.hasProfiledName;
@@ -28,13 +30,12 @@
2830
import java.io.Serializable;
2931
import java.security.PrivilegedAction;
3032
import java.util.ArrayList;
31-
import java.util.Collections;
33+
import java.util.Arrays;
3234
import java.util.HashMap;
3335
import java.util.HashSet;
3436
import java.util.Iterator;
3537
import java.util.List;
3638
import java.util.Map;
37-
import java.util.Optional;
3839
import java.util.Set;
3940
import java.util.function.BiConsumer;
4041
import java.util.function.Supplier;
@@ -150,19 +151,8 @@ void matchEnvWithProperties(final List<Map.Entry<String, Supplier<Iterator<Strin
150151
String prefix = property.getKey();
151152
if (StringUtil.isInPath(prefix, activeEnvName)) {
152153
Iterator<String> names = property.getValue().get();
153-
// Priority to match exact key in case multiple candidates (with map patterns)
154154
while (names.hasNext()) {
155-
String name = names.next();
156-
int exactLength = activeEnvName.length() - prefix.length() - 1;
157-
if (name.length() == exactLength && matchEnvWithProperty(prefix, name, envName, activeEnvName)) {
158-
break match;
159-
}
160-
}
161-
// Check everything else
162-
names = property.getValue().get();
163-
while (names.hasNext()) {
164-
String name = names.next();
165-
if (matchEnvWithProperty(prefix, name, envName, activeEnvName)) {
155+
if (matchEnvWithProperty(prefix, names.next(), envName, activeEnvName)) {
166156
break match;
167157
}
168158
}
@@ -173,26 +163,38 @@ void matchEnvWithProperties(final List<Map.Entry<String, Supplier<Iterator<Strin
173163

174164
private boolean matchEnvWithProperty(final String prefix, final String property, final String envName,
175165
final String activeEnvName) {
176-
Optional<List<Integer>> prefixDashes = indexOfDashes(
177-
prefix, 0, prefix.length(),
178-
activeEnvName, 0, prefix.length());
179-
Optional<List<Integer>> nameDashes = indexOfDashes(
166+
int[] prefixDashes = indexOfDashes(prefix, 0, prefix.length(), activeEnvName, 0, prefix.length());
167+
if (prefixDashes[0] == -1) {
168+
return false;
169+
}
170+
171+
int[] nameDashes = indexOfDashes(
180172
property, 0, property.length(),
181173
activeEnvName, prefix.isEmpty() ? 0 : prefix.length() + 1,
182174
prefix.isEmpty() ? activeEnvName.length() : activeEnvName.length() - prefix.length() - 1);
183-
if (prefixDashes.isPresent() && nameDashes.isPresent()) {
184-
StringBuilder sb = new StringBuilder(activeEnvName);
185-
for (Integer dash : prefixDashes.get()) {
175+
if (nameDashes[0] == -1) {
176+
return false;
177+
}
178+
179+
if (prefixDashes[0] == 0 && nameDashes[0] == 0) {
180+
return false;
181+
}
182+
183+
StringBuilder sb = new StringBuilder(activeEnvName);
184+
if (prefixDashes[0] != 0) {
185+
for (int dash : prefixDashes) {
186186
sb.setCharAt(dash, '-');
187187
}
188-
for (Integer dash : nameDashes.get()) {
188+
}
189+
if (nameDashes[0] != 0) {
190+
for (int dash : nameDashes) {
189191
sb.setCharAt(dash, '-');
190192
}
191-
if (!activeEnvName.contentEquals(sb)) {
192-
envVars.getNames().add(sb.toString());
193-
envVars.getNames().remove(envName);
194-
return true;
195-
}
193+
}
194+
if (!activeEnvName.contentEquals(sb)) {
195+
envVars.getNames().add(sb.toString());
196+
envVars.getNames().remove(envName);
197+
return true;
196198
}
197199
return false;
198200
}
@@ -210,31 +212,36 @@ private boolean matchEnvWithProperty(final String prefix, final String property,
210212
*
211213
* @param property the property name.
212214
* @param envProperty the Environment Variable name.
213-
* @return an Optional List of indexes from the Environment Variable name that can match a <code>-</code> (dash);
214-
* an Optional with an empty list if properties match but no dashes are found;
215-
* an empty Optional if properties don't match.
215+
* @return an array of int of indexes from the Environment Variable name that can match a <code>-</code> (dash);
216+
* an array of int with the int element <code>0</code> if properties match but no dashes are found;
217+
* an array of int with the int element <code>-1</code> if properties don't match.
216218
*/
217-
static Optional<List<Integer>> indexOfDashes(final String property, final int offset, final int len,
219+
static int[] indexOfDashes(final String property, final int offset, final int len,
218220
final String envProperty, final int eoffset, final int elen) {
219221
if (property.isEmpty()) {
220-
return Optional.of(Collections.emptyList());
222+
return ZERO.get();
221223
}
222224

223-
List<Integer> dashesPosition = new ArrayList<>();
225+
ResizableIntArray dashesPosition = ZERO;
224226
int matchPosition = eoffset + elen - 1;
225227
for (int i = offset + len - 1; i >= offset; i--) {
226228
if (matchPosition == -1) {
227-
return Optional.empty();
229+
return MINUS_ONE.get();
228230
}
229231

230232
char c = property.charAt(i);
231233
if (c == '.' || c == '-') {
232234
char p = envProperty.charAt(matchPosition);
233235
if (p != '.' && p != '-') { // a property coming from env can either be . or -
234-
return Optional.empty();
236+
return MINUS_ONE.get();
235237
}
236238
if (c == '-') {
237-
dashesPosition.add(matchPosition);
239+
if (dashesPosition == ZERO) {
240+
dashesPosition = new ResizableIntArray(1);
241+
} else {
242+
dashesPosition.ensureCapacity(dashesPosition.get().length + 1);
243+
}
244+
dashesPosition.get()[dashesPosition.get().length - 1] = matchPosition;
238245
}
239246
matchPosition--;
240247
} else if (c == '*') { // it's a map - skip to next separator
@@ -253,16 +260,46 @@ static Optional<List<Integer>> indexOfDashes(final String property, final int of
253260
matchPosition--;
254261
}
255262
} else if (c != envProperty.charAt(matchPosition)) {
256-
return Optional.empty();
263+
return MINUS_ONE.get();
257264
} else {
258265
matchPosition--;
259266
}
260267
}
261268
if (matchPosition >= eoffset) {
262-
return Optional.empty();
269+
return MINUS_ONE.get();
270+
}
271+
272+
return dashesPosition.get();
273+
}
274+
275+
static class ResizableIntArray {
276+
static final ResizableIntArray ZERO = with(0);
277+
static final ResizableIntArray MINUS_ONE = with(-1);
278+
279+
private int[] array;
280+
281+
ResizableIntArray(int initialSize) {
282+
this.array = new int[initialSize];
263283
}
264284

265-
return Optional.of(dashesPosition);
285+
int[] get() {
286+
return array;
287+
}
288+
289+
void ensureCapacity(int capacity) {
290+
this.array = Arrays.copyOfRange(this.array, 0, capacity);
291+
}
292+
293+
private static ResizableIntArray with(int i) {
294+
ResizableIntArray minusOne = new ResizableIntArray(1) {
295+
@Override
296+
void ensureCapacity(final int capacity) {
297+
throw new UnsupportedOperationException();
298+
}
299+
};
300+
minusOne.array[0] = i;
301+
return minusOne;
302+
}
266303
}
267304

268305
Object writeReplace() {

implementation/src/main/java/io/smallrye/config/SmallRyeConfig.java

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static io.smallrye.config.Converters.newCollectionConverter;
2222
import static io.smallrye.config.Converters.newMapConverter;
2323
import static io.smallrye.config.Converters.newOptionalConverter;
24+
import static io.smallrye.config.ProfileConfigSourceInterceptor.activeName;
2425
import static io.smallrye.config.common.utils.StringUtil.unindexed;
2526
import static io.smallrye.config.common.utils.StringUtil.unquoted;
2627
import static java.util.stream.Collectors.toList;
@@ -45,6 +46,7 @@
4546
import java.util.TreeMap;
4647
import java.util.concurrent.ConcurrentHashMap;
4748
import java.util.function.BiFunction;
49+
import java.util.function.Function;
4850
import java.util.function.IntFunction;
4951
import java.util.function.Supplier;
5052

@@ -867,8 +869,32 @@ private static class ConfigSources implements Serializable {
867869
// Adjust the EnvSources to look for names with dashes instead of dots
868870
List<Entry<String, Supplier<Iterator<String>>>> properties = new ArrayList<>(
869871
builder.getMappingsBuilder().getMappings().size());
872+
870873
// Match dotted properties from other sources with Env with the same semantic meaning
871-
properties.add(Map.entry("", current::iterateNames));
874+
properties.add(Map.entry("", new Supplier<>() {
875+
private final List<String> properties = new ArrayList<>();
876+
{
877+
// Filter out some sources that do not contribute to the matching
878+
for (ConfigSource configSource : configSources) {
879+
if (!(configSource instanceof EnvConfigSource) && configSource != defaultValues) {
880+
Set<String> propertyNames = configSource.getPropertyNames();
881+
if (propertyNames != null) {
882+
properties.addAll(propertyNames.stream().map(new Function<String, String>() {
883+
@Override
884+
public String apply(final String name) {
885+
return activeName(name, profiles);
886+
}
887+
}).toList());
888+
}
889+
}
890+
}
891+
}
892+
893+
@Override
894+
public Iterator<String> get() {
895+
return properties.iterator();
896+
}
897+
}));
872898
// Match mappings properties with Env
873899
for (ConfigMappings.ConfigClass mapping : builder.getMappingsBuilder().getMappings()) {
874900
Class<?> type = getConfigMappingClass(mapping.getType());
@@ -884,7 +910,7 @@ private static class ConfigSources implements Serializable {
884910
this.sources = configSources;
885911
this.defaultValues = defaultValues;
886912
this.interceptorChain = current;
887-
this.propertyNames = new PropertyNames();
913+
this.propertyNames = new PropertyNames(current);
888914
}
889915

890916
private static List<ConfigSource> buildSources(final SmallRyeConfigBuilder builder) {
@@ -1063,9 +1089,15 @@ PropertyNames getPropertyNames() {
10631089
class PropertyNames implements Serializable {
10641090
private static final long serialVersionUID = 4193517748286869745L;
10651091

1092+
private final SmallRyeConfigSourceInterceptorContext interceptorChain;
1093+
10661094
private final Set<String> names = new HashSet<>();
10671095
private final Map<String, Map<Integer, String>> indexed = new HashMap<>();
10681096

1097+
public PropertyNames(final SmallRyeConfigSourceInterceptorContext interceptorChain) {
1098+
this.interceptorChain = interceptorChain;
1099+
}
1100+
10691101
Iterable<String> get() {
10701102
if (names.isEmpty()) {
10711103
return latest();

implementation/src/test/java/io/smallrye/config/EnvConfigSourceTest.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232

3333
import java.util.ArrayList;
3434
import java.util.HashMap;
35+
import java.util.Iterator;
3536
import java.util.List;
3637
import java.util.Map;
3738
import java.util.NoSuchElementException;
3839
import java.util.Optional;
3940
import java.util.Set;
41+
import java.util.function.Supplier;
4042

4143
import org.eclipse.microprofile.config.spi.ConfigSource;
4244
import org.junit.jupiter.api.Test;
@@ -716,6 +718,24 @@ void clashMapKeysWithNames() {
716718

717719
ClashMapKeysWithNames mapping = config.getConfigMapping(ClashMapKeysWithNames.class);
718720
assertTrue(mapping.clashes().get(null).clientId().isPresent());
721+
722+
EnvConfigSource envConfigSource = new EnvConfigSource(Map.of("MAP_CLIENT_ID", "VALUE"), 300);
723+
envConfigSource.matchEnvWithProperties(List.of(Map.entry("", new Supplier<Iterator<String>>() {
724+
@Override
725+
public Iterator<String> get() {
726+
return List.of("map.*.id", "map.client-id").iterator();
727+
}
728+
})), List.of());
729+
assertTrue(envConfigSource.getPropertyNames().contains("map.client-id"));
730+
731+
envConfigSource = new EnvConfigSource(Map.of("MAP_CLIENT_ID", "VALUE"), 300);
732+
envConfigSource.matchEnvWithProperties(List.of(Map.entry("", new Supplier<Iterator<String>>() {
733+
@Override
734+
public Iterator<String> get() {
735+
return List.of("map.client-id", "map.*.id").iterator();
736+
}
737+
})), List.of());
738+
assertTrue(envConfigSource.getPropertyNames().contains("map.client-id"));
719739
}
720740

721741
@ConfigMapping(prefix = "map")

0 commit comments

Comments
 (0)