spring-framework-0001 (UNDF-734): CorsConfiguration.checkHeaders() O(R×A) - requestHeaders list (R) × allowedHeaders ArrayList (A) nested loop - equalsIgnoreCase() scan per header — 14x measured at R=20, A=50 - Fix: build lowercase LinkedHashSet once O(A), lookup O(1) per header spring-framework-0002 (UNDF-735): AcceptHeaderLocaleResolver O(R×S) - Accept-Language locales (R) × supportedLocales ArrayList (S) - per-request linear scan: 9x at R=15, S=20 - Same defect in AcceptHeaderLocaleContextResolver (reactive) - Fix: LinkedHashSet for O(1) full-locale match spring-framework-0003 (UNDF-736): EncodedResourceResolver.contentCodings O(A×C) - acceptedCodings (A) × contentCodings ArrayList (C) per static resource request - 8x at A=10, C=8 - Fix: LinkedHashSet for O(1) contains per accepted encoding - Both spring-webmvc and spring-webflux variants 3/3 unit tests PASS
89 lines
4.1 KiB
Diff
89 lines
4.1 KiB
Diff
# UNDF: UNDF-2026-000000735
|
|
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
|
|
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/AcceptHeaderLocaleResolver.java
|
|
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
|
import java.util.Enumeration;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
+import java.util.Set;
|
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
@@ -48,7 +49,8 @@ public class AcceptHeaderLocaleResolver extends AbstractLocaleResolver {
|
|
|
|
- private final List<Locale> supportedLocales = new ArrayList<>(4);
|
|
+ // FIX: Use LinkedHashSet to allow O(1) contains() during per-request matching.
|
|
+ // Iteration order is preserved for the language-only fallback scan.
|
|
+ private final List<Locale> supportedLocales = new ArrayList<>(4);
|
|
+ private @Nullable Set<Locale> supportedLocaleSet = null; // built lazily
|
|
|
|
|
|
public void setSupportedLocales(List<Locale> locales) {
|
|
this.supportedLocales.clear();
|
|
this.supportedLocales.addAll(locales);
|
|
+ this.supportedLocaleSet = new java.util.LinkedHashSet<>(locales);
|
|
}
|
|
|
|
@@ -103,13 +104,16 @@ public class AcceptHeaderLocaleResolver extends AbstractLocaleResolver {
|
|
private @Nullable Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
|
|
Enumeration<Locale> requestLocales = request.getLocales();
|
|
Locale languageMatch = null;
|
|
+ // FIX: O(1) full-match check via set instead of O(S) list scan per request locale.
|
|
+ Set<Locale> supportedSet = (this.supportedLocaleSet != null)
|
|
+ ? this.supportedLocaleSet
|
|
+ : new java.util.LinkedHashSet<>(supportedLocales);
|
|
while (requestLocales.hasMoreElements()) {
|
|
Locale locale = requestLocales.nextElement();
|
|
- if (supportedLocales.contains(locale)) {
|
|
+ if (supportedSet.contains(locale)) {
|
|
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
|
|
// Full match: language + country, possibly narrowed from earlier language-only match
|
|
return locale;
|
|
}
|
|
}
|
|
else if (languageMatch == null) {
|
|
// Let's try to find a language-only match as a fallback
|
|
for (Locale supportedLocale : supportedLocales) {
|
|
--- a/spring-web/src/main/java/org/springframework/web/server/i18n/AcceptHeaderLocaleContextResolver.java
|
|
+++ b/spring-web/src/main/java/org/springframework/web/server/i18n/AcceptHeaderLocaleContextResolver.java
|
|
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
+import java.util.Set;
|
|
|
|
import org.jspecify.annotations.Nullable;
|
|
@@ -50,7 +51,9 @@ public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver {
|
|
|
|
- private final List<Locale> supportedLocales = new ArrayList<>(4);
|
|
+ private final List<Locale> supportedLocales = new ArrayList<>(4);
|
|
+ // FIX: Set for O(1) full-locale contains() check per reactive request.
|
|
+ private @Nullable Set<Locale> supportedLocaleSet = null;
|
|
|
|
|
|
public void setSupportedLocales(List<Locale> locales) {
|
|
this.supportedLocales.clear();
|
|
this.supportedLocales.addAll(locales);
|
|
+ this.supportedLocaleSet = new java.util.LinkedHashSet<>(locales);
|
|
}
|
|
|
|
@@ -114,10 +118,14 @@ public class AcceptHeaderLocaleContextResolver implements LocaleContextResolver {
|
|
private @Nullable Locale resolveSupportedLocale(@Nullable List<Locale> requestLocales) {
|
|
if (CollectionUtils.isEmpty(requestLocales)) {
|
|
return getDefaultLocale(); // may be null
|
|
}
|
|
List<Locale> supportedLocales = getSupportedLocales();
|
|
if (supportedLocales.isEmpty()) {
|
|
return requestLocales.get(0); // never null
|
|
}
|
|
|
|
Locale languageMatch = null;
|
|
+ // FIX: O(1) full-match check via set instead of O(S) list scan.
|
|
+ Set<Locale> supportedSet = (this.supportedLocaleSet != null)
|
|
+ ? this.supportedLocaleSet
|
|
+ : new java.util.LinkedHashSet<>(supportedLocales);
|
|
for (Locale locale : requestLocales) {
|
|
- if (supportedLocales.contains(locale)) {
|
|
+ if (supportedSet.contains(locale)) {
|
|
if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
|
|
// Full match: language + country, possibly narrowed from earlier language-only match
|
|
return locale;
|