Cache property mappings in ConfigurationPropertyName

Relocate `SystemEnvironmentPropertyMapper` methods into
`ConfigurationPropertyName` so that they can be cached to
improve performance

Closes gh-44858
This commit is contained in:
Phillip Webb 2025-03-22 16:29:06 -07:00
parent 81dee54137
commit ae6908e4d8
2 changed files with 81 additions and 64 deletions

View File

@ -20,8 +20,10 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.IntFunction;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@ -63,10 +65,14 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
private final CharSequence[] uniformElements; private final CharSequence[] uniformElements;
private String string;
private int hashCode; private int hashCode;
private String[] string = new String[ToStringFormat.values().length];
private Boolean hasDashedElement;
private ConfigurationPropertyName systemEnvironmentLegacyName;
private ConfigurationPropertyName(Elements elements) { private ConfigurationPropertyName(Elements elements) {
this.elements = elements; this.elements = elements;
this.uniformElements = new CharSequence[elements.getSize()]; this.uniformElements = new CharSequence[elements.getSize()];
@ -525,15 +531,41 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
return hashCode; return hashCode;
} }
@Override ConfigurationPropertyName asSystemEnvironmentLegacyName() {
public String toString() { ConfigurationPropertyName name = this.systemEnvironmentLegacyName;
if (this.string == null) { if (name == null) {
this.string = buildToString(); name = ConfigurationPropertyName
.ofIfValid(buildSimpleToString('.', (i) -> getElement(i, Form.DASHED).replace('-', '.')));
this.systemEnvironmentLegacyName = (name != null) ? name : EMPTY;
} }
return this.string; return (name != EMPTY) ? name : null;
} }
private String buildToString() { @Override
public String toString() {
return toString(ToStringFormat.DEFAULT);
}
String toString(ToStringFormat format) {
String string = this.string[format.ordinal()];
if (string == null) {
string = buildToString(format);
this.string[format.ordinal()] = string;
}
return string;
}
private String buildToString(ToStringFormat format) {
return switch (format) {
case DEFAULT -> buildDefaultToString();
case SYSTEM_ENVIRONMENT ->
buildSimpleToString('_', (i) -> getElement(i, Form.UNIFORM).toUpperCase(Locale.ENGLISH));
case LEGACY_SYSTEM_ENVIRONMENT -> buildSimpleToString('_',
(i) -> getElement(i, Form.ORIGINAL).replace('-', '_').toUpperCase(Locale.ENGLISH));
};
}
private String buildDefaultToString() {
if (this.elements.canShortcutWithSource(ElementType.UNIFORM, ElementType.DASHED)) { if (this.elements.canShortcutWithSource(ElementType.UNIFORM, ElementType.DASHED)) {
return this.elements.getSource().toString(); return this.elements.getSource().toString();
} }
@ -556,6 +588,32 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
return result.toString(); return result.toString();
} }
private String buildSimpleToString(char joinChar, IntFunction<String> elementConverter) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < getNumberOfElements(); i++) {
if (!result.isEmpty()) {
result.append(joinChar);
}
result.append(elementConverter.apply(i));
}
return result.toString();
}
boolean hasDashedElement() {
Boolean hasDashedElement = this.hasDashedElement;
if (hasDashedElement != null) {
return hasDashedElement;
}
for (int i = 0; i < getNumberOfElements(); i++) {
if (getElement(i, Form.DASHED).indexOf('-') != -1) {
this.hasDashedElement = true;
return true;
}
}
this.hasDashedElement = false;
return false;
}
/** /**
* Returns if the given name is valid. If this method returns {@code true} then the * Returns if the given name is valid. If this method returns {@code true} then the
* name may be used with {@link #of(CharSequence)} without throwing an exception. * name may be used with {@link #of(CharSequence)} without throwing an exception.
@ -1132,4 +1190,13 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
} }
/**
* Formats for {@code toString}.
*/
enum ToStringFormat {
DEFAULT, SYSTEM_ENVIRONMENT, LEGACY_SYSTEM_ENVIRONMENT
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2023 the original author or authors. * Copyright 2012-2025 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +22,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.function.BiPredicate; import java.util.function.BiPredicate;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form; import org.springframework.boot.context.properties.source.ConfigurationPropertyName.ToStringFormat;
/** /**
* {@link PropertyMapper} for system environment variables. Names are mapped by removing * {@link PropertyMapper} for system environment variables. Names are mapped by removing
@ -42,44 +42,14 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
@Override @Override
public List<String> map(ConfigurationPropertyName configurationPropertyName) { public List<String> map(ConfigurationPropertyName configurationPropertyName) {
String name = convertName(configurationPropertyName); String name = configurationPropertyName.toString(ToStringFormat.SYSTEM_ENVIRONMENT);
String legacyName = convertLegacyName(configurationPropertyName); String legacyName = configurationPropertyName.toString(ToStringFormat.LEGACY_SYSTEM_ENVIRONMENT);
if (name.equals(legacyName)) { if (name.equals(legacyName)) {
return Collections.singletonList(name); return Collections.singletonList(name);
} }
return Arrays.asList(name, legacyName); return Arrays.asList(name, legacyName);
} }
private String convertName(ConfigurationPropertyName name) {
return convertName(name, name.getNumberOfElements());
}
private String convertName(ConfigurationPropertyName name, int numberOfElements) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < numberOfElements; i++) {
if (!result.isEmpty()) {
result.append('_');
}
result.append(name.getElement(i, Form.UNIFORM).toUpperCase(Locale.ENGLISH));
}
return result.toString();
}
private String convertLegacyName(ConfigurationPropertyName name) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < name.getNumberOfElements(); i++) {
if (!result.isEmpty()) {
result.append('_');
}
result.append(convertLegacyNameElement(name.getElement(i, Form.ORIGINAL)));
}
return result.toString();
}
private Object convertLegacyNameElement(String element) {
return element.replace('-', '_').toUpperCase(Locale.ENGLISH);
}
@Override @Override
public ConfigurationPropertyName map(String propertySourceName) { public ConfigurationPropertyName map(String propertySourceName) {
return convertName(propertySourceName); return convertName(propertySourceName);
@ -113,31 +83,11 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
} }
private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) { private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
if (!hasDashedEntries(name)) { if (!name.hasDashedElement()) {
return false; return false;
} }
ConfigurationPropertyName legacyCompatibleName = buildLegacyCompatibleName(name); ConfigurationPropertyName legacyCompatibleName = name.asSystemEnvironmentLegacyName();
return legacyCompatibleName != null && legacyCompatibleName.isAncestorOf(candidate); return legacyCompatibleName != null && legacyCompatibleName.isAncestorOf(candidate);
} }
private ConfigurationPropertyName buildLegacyCompatibleName(ConfigurationPropertyName name) {
StringBuilder legacyCompatibleName = new StringBuilder();
for (int i = 0; i < name.getNumberOfElements(); i++) {
if (i != 0) {
legacyCompatibleName.append('.');
}
legacyCompatibleName.append(name.getElement(i, Form.DASHED).replace('-', '.'));
}
return ConfigurationPropertyName.ofIfValid(legacyCompatibleName);
}
boolean hasDashedEntries(ConfigurationPropertyName name) {
for (int i = 0; i < name.getNumberOfElements(); i++) {
if (name.getElement(i, Form.DASHED).indexOf('-') != -1) {
return true;
}
}
return false;
}
} }