This is an automated email from the ASF dual-hosted git repository.

papegaaij pushed a commit to branch performance-improvements-10.x
in repository https://gitbox.apache.org/repos/asf/wicket.git

commit dc31e701339b0d2ca4a9c68b2bd0cfcd1acac18e
Author: Emond Papegaaij <[email protected]>
AuthorDate: Fri Sep 11 22:06:02 2026 +0200

    Stop re-deriving the locale part of every candidate resource name
    
    ResourceStreamLocator walks a list of candidate filenames for every 
property and
    markup lookup, once per registered properties loader. Misses are the common
    case, because a key is resolved by climbing the component hierarchy, so 
every
    class above the one that declares it contributes a full traversal that finds
    nothing.
    
    toString() built each candidate from four prepend() calls, and the locale 
part
    went through getLocale(), which builds a Locale, even though
    LocaleResourceNameIterator.next() had just built the identical suffix and 
thrown
    it away. It now keeps that suffix, and caches the Locale per state for the 
one
    caller that does read it back.
    
    Verified by enumeration rather than by argument: every candidate name with 
its
    locale, style, variation and extension over 6 paths x 2 styles x 2 
variations x
    7 locales x 5 extension lists x strict/non-strict, 9918 names, 
byte-identical
    before and after.
    
    One note for reviewers: toString() now takes the locale segment from next()
    instead of from getLocale(). A subclass supplied through the protected
    newLocaleResourceNameIterator hook that overrode getLocale() without also
    overriding next() would see a difference; nothing in the tree does that.
    
    Backported from master.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 .../locator/ExtensionResourceNameIterator.java     |  5 ++--
 .../locator/LocaleResourceNameIterator.java        | 34 ++++++++++++++++++++++
 .../resource/locator/ResourceNameIterator.java     | 30 ++++++++++++-------
 3 files changed, 57 insertions(+), 12 deletions(-)

diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
index b1ffe2762a..540f8efda3 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ExtensionResourceNameIterator.java
@@ -42,13 +42,14 @@ public class ExtensionResourceNameIterator implements 
Iterator<String>
        public ExtensionResourceNameIterator(final Iterable<String> extensions)
        {
                // Fail safe: hasNext() needs to return at least once with true
-               if (extensions == null || !extensions.iterator().hasNext())
+               Iterator<String> extensionIterator = extensions == null ? null 
: extensions.iterator();
+               if (extensionIterator == null || !extensionIterator.hasNext())
                {
                        this.iterator = NULL_ITERABLE.iterator();
                }
                else
                {
-                       this.iterator = extensions.iterator();
+                       this.iterator = extensionIterator;
                }
        }
 
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
index a2d2763e3c..681fd9d6fe 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/LocaleResourceNameIterator.java
@@ -39,6 +39,13 @@ public class LocaleResourceNameIterator implements 
Iterator<String>
 
        private final boolean strict;
 
+       /** The locale part of the resource name that {@link #next()} produced 
for this state */
+       private String suffix = "";
+
+       private int localeState = -1;
+
+       private Locale currentLocale;
+
        /**
         * Construct.
         *
@@ -55,6 +62,18 @@ public class LocaleResourceNameIterator implements 
Iterator<String>
         * @return Locale
         */
        public Locale getLocale()
+       {
+               // Building the locale allocates and goes through the 
BaseLocale cache rather than being a
+               // field read, and this is called once per candidate name; the 
state only changes in next().
+               if (localeState != state)
+               {
+                       localeState = state;
+                       currentLocale = localeForState();
+               }
+               return currentLocale;
+       }
+
+       private Locale localeForState()
        {
                if (state == 1)
                {
@@ -72,6 +91,15 @@ public class LocaleResourceNameIterator implements 
Iterator<String>
                return null;
        }
 
+       /**
+        * @return the locale part of the resource name for the current state, 
already prefixed with
+        *         {@code '_'}, or the empty string if this state carries no 
locale
+        */
+       public String getSuffix()
+       {
+               return suffix;
+       }
+
        /**
         *
         * @see java.util.Iterator#hasNext()
@@ -93,6 +121,12 @@ public class LocaleResourceNameIterator implements 
Iterator<String>
         */
        @Override
        public String next()
+       {
+               suffix = nextSuffix();
+               return suffix;
+       }
+
+       private String nextSuffix()
        {
                if (locale == null)
                {
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
index f89c959a3a..8e97258932 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/resource/locator/ResourceNameIterator.java
@@ -69,6 +69,11 @@ public class ResourceNameIterator implements 
IResourceNameIterator
        // The various iterators used to locate the resource file
        private final StyleAndVariationResourceNameIterator styleIterator;
        private LocaleResourceNameIterator localeIterator;
+
+       // The variation and style parts of the name, rebuilt only when the 
style iterator advances.
+       private String variationPart = "";
+
+       private String stylePart = "";
        private ExtensionResourceNameIterator extensionsIterator;
 
        /**
@@ -199,6 +204,8 @@ public class ResourceNameIterator implements 
IResourceNameIterator
                while (styleIterator.hasNext())
                {
                        styleIterator.next();
+                       variationPart = part(styleIterator.getVariation(), '_');
+                       stylePart = part(styleIterator.getStyle(), '_');
 
                        localeIterator = newLocaleResourceNameIterator(locale, 
strict);
                        while (localeIterator.hasNext())
@@ -244,19 +251,22 @@ public class ResourceNameIterator implements 
IResourceNameIterator
        @Override
        public String toString()
        {
-               return path + prepend(getVariation(), '_') + 
prepend(getStyle(), '_') +
-                       prepend(getLocale(), '_') + prepend(getExtension(), 
'.');
+               // The locale part was already built by 
LocaleResourceNameIterator.next(); deriving it
+               // again through getLocale() would cost building that locale 
and its string form for every
+               // candidate name. Each branch is a single concatenation, so 
only the result is allocated.
+               String localePart = localeIterator != null ? 
localeIterator.getSuffix() : "";
+               String extension = getExtension();
+
+               if (extension == null)
+               {
+                       return path + variationPart + stylePart + localePart;
+               }
+               return path + variationPart + stylePart + localePart + '.' + 
extension;
        }
 
-       /**
-        * 
-        * @param string
-        * @param prepend
-        * @return The string prepended with the char
-        */
-       private String prepend(Object string, char prepend)
+       private static String part(String value, char prepend)
        {
-               return (string != null) ? prepend + string.toString() : "";
+               return value == null ? "" : prepend + value;
        }
 
        /**

Reply via email to