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

lukaszlenart pushed a commit to branch 
WW-5674-isclassbelongstopackages-allocation
in repository https://gitbox.apache.org/repos/asf/struts.git

commit 8237a1fd8bf2338bc1ac36ff032058e73543ef7d
Author: Lukasz Lenart <[email protected]>
AuthorDate: Mon Aug 3 13:05:06 2026 +0200

    WW-5674 perf(ognl): walk package names in place instead of building prefixes
    
    Replaces the split/IntStream/String.join prefix construction with an index 
walk,
    extracted into a pure package-private helper so it can be tested against 
package
    name shapes no real Class can produce. Per call this drops a String[], a 
list
    wrapper, a stream pipeline, N sublist views and N joined strings, leaving 
one
    substring per package level.
    
    Equivalence with the replaced implementation is asserted over a matrix of
    package name shapes and candidate sets.
---
 .../apache/struts2/ognl/SecurityMemberAccess.java  | 33 ++++++++++++++++++----
 .../SecurityMemberAccessPackageMatchingTest.java   | 20 +++++++++++++
 2 files changed, 47 insertions(+), 6 deletions(-)

diff --git 
a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java 
b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java
index 0e22e74ce..e41004a2b 100644
--- a/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java
+++ b/core/src/main/java/org/apache/struts2/ognl/SecurityMemberAccess.java
@@ -32,11 +32,9 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Member;
 import java.lang.reflect.Modifier;
-import java.util.List;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.stream.IntStream;
 
 import static java.text.MessageFormat.format;
 import static java.util.Collections.emptySet;
@@ -393,10 +391,33 @@ public class SecurityMemberAccess implements MemberAccess 
{
     }
 
     public static boolean isClassBelongsToPackages(Class<?> clazz, Set<String> 
matchingPackages) {
-        List<String> packageParts = List.of(toPackageName(clazz).split("\\."));
-        return IntStream.range(0, packageParts.size())
-                .mapToObj(i -> String.join(".", packageParts.subList(0, i + 
1)))
-                .anyMatch(matchingPackages::contains);
+        return isPackageBelongsToPackages(toPackageName(clazz), 
matchingPackages, emptySet());
+    }
+
+    /**
+     * Tests whether the given package name, or any of its parent packages, is 
present in either
+     * set. Walks the name in place rather than building the full prefix list, 
since this runs on
+     * the OGNL member-access path. Shortest prefix first, so broad entries 
such as {@code java.io}
+     * short-circuit earliest.
+     *
+     * @param packageName the package name to test, empty for the default 
package
+     * @param first       the first set of package names to match against
+     * @param second      the second set of package names to match against
+     * @return {@code true} if the package or any parent package is in either 
set
+     */
+    static boolean isPackageBelongsToPackages(String packageName, Set<String> 
first, Set<String> second) {
+        if (first.isEmpty() && second.isEmpty()) {
+            return false;
+        }
+        int idx = packageName.indexOf('.');
+        while (idx != -1) {
+            String prefix = packageName.substring(0, idx);
+            if (first.contains(prefix) || second.contains(prefix)) {
+                return true;
+            }
+            idx = packageName.indexOf('.', idx + 1);
+        }
+        return first.contains(packageName) || second.contains(packageName);
     }
 
     protected boolean isClassExcluded(Class<?> clazz) {
diff --git 
a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
 
b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
index 9f8af5556..04e2da96c 100644
--- 
a/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
+++ 
b/core/src/test/java/org/apache/struts2/ognl/SecurityMemberAccessPackageMatchingTest.java
@@ -175,4 +175,24 @@ public class SecurityMemberAccessPackageMatchingTest {
             }
         }
     }
+
+    @Test
+    public void indexWalkMatchesLegacyAcrossPackageNameShapes() {
+        for (String packageName : PACKAGE_NAMES) {
+            for (Set<String> candidates : CANDIDATE_SETS) {
+                
assertThat(SecurityMemberAccess.isPackageBelongsToPackages(packageName, 
candidates, emptySet()))
+                        .as("packageName=[%s] candidates=%s", packageName, 
candidates)
+                        .isEqualTo(legacyPrefixMatch(packageName, candidates));
+            }
+        }
+    }
+
+    @Test
+    public void bothSetsEmptyShortCircuitsToFalse() {
+        for (String packageName : PACKAGE_NAMES) {
+            
assertThat(SecurityMemberAccess.isPackageBelongsToPackages(packageName, 
emptySet(), emptySet()))
+                    .as("packageName=[%s] with no configured packages", 
packageName)
+                    .isFalse();
+        }
+    }
 }

Reply via email to