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.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
import java.util.function.IntFunction;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -63,10 +65,14 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
private final CharSequence[] uniformElements;
private String string;
private int hashCode;
private String[] string = new String[ToStringFormat.values().length];
private Boolean hasDashedElement;
private ConfigurationPropertyName systemEnvironmentLegacyName;
private ConfigurationPropertyName(Elements elements) {
this.elements = elements;
this.uniformElements = new CharSequence[elements.getSize()];
@ -525,15 +531,41 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
return hashCode;
}
@Override
public String toString() {
if (this.string == null) {
this.string = buildToString();
ConfigurationPropertyName asSystemEnvironmentLegacyName() {
ConfigurationPropertyName name = this.systemEnvironmentLegacyName;
if (name == null) {
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)) {
return this.elements.getSource().toString();
}
@ -556,6 +588,32 @@ public final class ConfigurationPropertyName implements Comparable<Configuration
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
* 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");
* 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.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
@ -42,44 +42,14 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
@Override
public List<String> map(ConfigurationPropertyName configurationPropertyName) {
String name = convertName(configurationPropertyName);
String legacyName = convertLegacyName(configurationPropertyName);
String name = configurationPropertyName.toString(ToStringFormat.SYSTEM_ENVIRONMENT);
String legacyName = configurationPropertyName.toString(ToStringFormat.LEGACY_SYSTEM_ENVIRONMENT);
if (name.equals(legacyName)) {
return Collections.singletonList(name);
}
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
public ConfigurationPropertyName map(String propertySourceName) {
return convertName(propertySourceName);
@ -113,31 +83,11 @@ final class SystemEnvironmentPropertyMapper implements PropertyMapper {
}
private boolean isLegacyAncestorOf(ConfigurationPropertyName name, ConfigurationPropertyName candidate) {
if (!hasDashedEntries(name)) {
if (!name.hasDashedElement()) {
return false;
}
ConfigurationPropertyName legacyCompatibleName = buildLegacyCompatibleName(name);
ConfigurationPropertyName legacyCompatibleName = name.asSystemEnvironmentLegacyName();
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;
}
}